More work on compiler

This commit is contained in:
Chad Retz 2017-03-21 17:53:32 -05:00
parent 62ddcbc9a6
commit d310acde44
15 changed files with 482 additions and 84 deletions

View File

@ -2,7 +2,8 @@ group 'asmble'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.1.0'
ext.kotlin_version = '1.1.1'
ext.asm_version = '5.2'
repositories {
mavenCentral()
@ -21,6 +22,9 @@ repositories {
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile "junit:junit:4.12"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "org.ow2.asm:asm-tree:$asm_version"
compile "org.ow2.asm:asm-util:$asm_version"
testCompile 'junit:junit:4.12'
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
}

View File

@ -120,7 +120,7 @@ sealed class Node {
interface Index : Args { val index: Int }
interface Reserved : Args { val reserved: Boolean }
interface ReservedIndex : Index, Reserved
interface AlignOffset : Args { val align: Int; val offset: Int }
interface AlignOffset : Args { val align: Int; val offset: Long }
interface Const<out T : Number> : Args { val value: T }
}
@ -159,29 +159,29 @@ sealed class Node {
data class SetGlobal(override val index: Int) : Instr(), Args.Index
// Memory operators
data class I32Load(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I64Load(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class F32Load(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class F64Load(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I32Load8S(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I32Load8U(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I32Load16S(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I32Load16U(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I64Load8S(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I64Load8U(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I64Load16S(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I64Load16U(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I64Load32S(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I64Load32U(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I32Store(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I64Store(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class F32Store(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class F64Store(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I32Store8(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I32Store16(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I64Store8(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I64Store16(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I64Store32(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
data class I32Load(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I64Load(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class F32Load(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class F64Load(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I32Load8S(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I32Load8U(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I32Load16S(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I32Load16U(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I64Load8S(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I64Load8U(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I64Load16S(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I64Load16U(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I64Load32S(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I64Load32U(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I32Store(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I64Store(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class F32Store(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class F64Store(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I32Store8(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I32Store16(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I64Store8(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I64Store16(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I64Store32(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class CurrentMemory(override val reserved: Boolean) : Instr(), Args.Reserved
data class GrowMemory(override val reserved: Boolean) : Instr(), Args.Reserved
@ -351,7 +351,7 @@ sealed class Node {
}
sealed class MemOp<out A : Instr.Args> : InstrOp<A>() {
data class AlignOffsetArg(override val name: String, val create: (Int, Int) -> Instr) : MemOp<Instr.Args.AlignOffset>()
data class AlignOffsetArg(override val name: String, val create: (Int, Long) -> Instr) : MemOp<Instr.Args.AlignOffset>()
data class ReservedArg(override val name: String, val create: (Boolean) -> Instr) : MemOp<Instr.Args.Reserved>()
}

View File

@ -1,6 +1,12 @@
package asmble.ast
import asmble.io.SExprToStr
sealed class SExpr {
data class Multi(val vals: List<SExpr> = emptyList()) : SExpr()
data class Symbol(val contents: String = "", val quoted: Boolean = false) : SExpr()
data class Multi(val vals: List<SExpr> = emptyList()) : SExpr() {
override fun toString() = SExprToStr.Compact.fromSExpr(this)
}
data class Symbol(val contents: String = "", val quoted: Boolean = false) : SExpr() {
override fun toString() = SExprToStr.Compact.fromSExpr(this)
}
}

View File

@ -0,0 +1,47 @@
package asmble.compile.jvm
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.tree.*
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KProperty
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaMethod
import kotlin.reflect.jvm.reflect
val <R> KFunction<R>.asmDesc: String get() = Type.getMethodDescriptor(this.javaMethod)
val <R> KFunction<R>.declarer: Class<*> get() = this.javaMethod!!.declaringClass
fun KFunction<*>.invokeStatic() =
MethodInsnNode(Opcodes.INVOKESTATIC, this.declarer.asmName, this.name, this.asmDesc, false)
fun KFunction<*>.invokeVirtual() =
MethodInsnNode(Opcodes.INVOKEVIRTUAL, this.declarer.asmName, this.name, this.asmDesc, false)
inline fun <T> forceType(fn: T) = fn
inline fun <T : Function<*>> forceFnType(fn: T) = fn.reflect()!!
val <T : Any> KClass<T>.asmName: String get() = this.java.asmName
val <T : Any> Class<T>.asmName: String get() = Type.getInternalName(this)
val KProperty<*>.declarer: Class<*> get() = this.javaField!!.declaringClass
val KProperty<*>.asmDesc: String get() = Type.getDescriptor(this.javaField!!.type)
fun KProperty<*>.getStatic() =
FieldInsnNode(Opcodes.GETSTATIC, this.declarer.asmName, this.name, this.asmDesc)
val Int.const: AbstractInsnNode get() = when (this) {
-1 -> InsnNode(Opcodes.ICONST_M1)
0 -> InsnNode(Opcodes.ICONST_0)
1 -> InsnNode(Opcodes.ICONST_1)
2 -> InsnNode(Opcodes.ICONST_2)
3 -> InsnNode(Opcodes.ICONST_3)
4 -> InsnNode(Opcodes.ICONST_4)
5 -> InsnNode(Opcodes.ICONST_5)
in 6..Byte.MAX_VALUE -> IntInsnNode(Opcodes.BIPUSH, this)
in (Byte.MAX_VALUE + 1)..Short.MAX_VALUE -> IntInsnNode(Opcodes.SIPUSH, this)
else -> LdcInsnNode(this)
}

View File

@ -0,0 +1,10 @@
package asmble.compile.jvm
import asmble.ast.Node
import org.objectweb.asm.tree.ClassNode
open class AstToAsm {
fun fromModule(n: Node.Module): ClassNode {
TODO()
}
}

View File

@ -0,0 +1,153 @@
package asmble.compile.jvm
import asmble.ast.Node
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.InsnNode
import org.objectweb.asm.tree.IntInsnNode
import org.objectweb.asm.tree.TypeInsnNode
import java.nio.Buffer
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.jvm.reflect
open class ByteBufferMem(val direct: Boolean = true, val defaultMax: Int = 5 * Mem.PAGE_SIZE) : Mem<ByteBuffer> {
override val memClass = ByteBuffer::class.java
override fun create(func: Func, maximum: Int?) = func.addInsns(
(maximum ?: defaultMax).const,
(if (direct) ByteBuffer::allocateDirect else ByteBuffer::allocate).invokeStatic()
).push(memClass)
override fun init(func: Func, initial: Int) = func.popExpecting(memClass).addInsns(
// Set the limit to initial
initial.const,
forceFnType<ByteBuffer.(Int) -> Buffer>(ByteBuffer::limit).invokeVirtual(),
TypeInsnNode(Opcodes.CHECKCAST, ByteBuffer::class.asmName),
// Set it to use little endian
ByteOrder::LITTLE_ENDIAN.getStatic(),
forceFnType<ByteBuffer.(ByteOrder) -> ByteBuffer>(ByteBuffer::order).invokeVirtual()
).push(ByteBuffer::class.java)
override fun data(func: Func, bytes: ByteArray, buildOffset: (Func) -> Func) = func.
popExpecting(memClass).
addInsns(
bytes.size.const,
IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_BYTE),
*bytes.withIndex().flatMap { (index, byte) ->
listOf(InsnNode(Opcodes.DUP), index.const, byte.toInt().const)
}.toTypedArray()
).
apply(buildOffset).popExpecting(Int::class.java).
// BOO! https://discuss.kotlinlang.org/t/overload-resolution-ambiguity-function-reference-requiring-local-var/2425
addInsns(forceFnType<ByteBuffer.(ByteArray, Int, Int) -> ByteBuffer>(ByteBuffer::put).invokeVirtual()).
push(memClass)
override fun currentMemory(func: Func) = func.popExpecting(memClass).addInsns(
forceFnType<ByteBuffer.() -> Int>(ByteBuffer::limit).invokeVirtual(),
Mem.PAGE_SIZE.const,
InsnNode(Opcodes.IDIV)
).push(Int::class.java)
override fun growMemory(func: Func) = func.popExpecting(memClass).popExpecting(Int::class.java).addInsns(
Mem.PAGE_SIZE.const,
// TODO: overflow check, e.g. Math.multiplyExact
InsnNode(Opcodes.IMUL),
forceFnType<ByteBuffer.(Int) -> Buffer>(ByteBuffer::limit).invokeVirtual()
).push(ByteBuffer::class.java)
override fun loadOp(func: Func, insn: Node.Instr.Args.AlignOffset) = func.popExpecting(memClass).let { func ->
require(insn.offset <= Int.MAX_VALUE, { "Offsets > ${Int.MAX_VALUE} unsupported" }).let { this }
fun Func.load(fn: ByteBuffer.(Int) -> Any, retClass: KClass<*>) =
this.addInsns(insn.offset.toInt().const, fn.reflect()!!.invokeVirtual()).push(retClass.java)
fun Func.loadI32(fn: ByteBuffer.(Int) -> Any) =
this.load(fn, Int::class)
fun Func.loadI64(fn: ByteBuffer.(Int) -> Any) =
this.load(fn, Long::class)
fun Func.toUnsigned(fn: KFunction<*>) =
this.addInsns(fn.invokeVirtual())
fun Func.toUnsigned64(fn: KFunction<*>) =
this.popExpecting(Int::class.java).toUnsigned(fn).push(Long::class.java)
fun Func.i32ToI64() =
this.popExpecting(Int::class.java).addInsns(InsnNode(Opcodes.I2L)).push(Long::class.java)
when (insn) {
is Node.Instr.I32Load ->
func.loadI32(ByteBuffer::getInt)
is Node.Instr.I64Load ->
func.loadI64(ByteBuffer::getLong)
is Node.Instr.F32Load ->
func.load(ByteBuffer::getFloat, Float::class)
is Node.Instr.F64Load ->
func.load(ByteBuffer::getDouble, Double::class)
is Node.Instr.I32Load8S ->
func.loadI32(ByteBuffer::get)
is Node.Instr.I32Load8U ->
func.loadI32(ByteBuffer::get).toUnsigned(java.lang.Byte::toUnsignedInt)
is Node.Instr.I32Load16S ->
func.loadI32(ByteBuffer::getShort)
is Node.Instr.I32Load16U ->
func.loadI32(ByteBuffer::getShort).toUnsigned(java.lang.Short::toUnsignedInt)
is Node.Instr.I64Load8S ->
func.loadI32(ByteBuffer::get).i32ToI64()
is Node.Instr.I64Load8U ->
func.loadI32(ByteBuffer::get).toUnsigned64(java.lang.Byte::toUnsignedLong)
is Node.Instr.I64Load16S ->
func.loadI32(ByteBuffer::getShort).i32ToI64()
is Node.Instr.I64Load16U ->
func.loadI32(ByteBuffer::getShort).toUnsigned64(java.lang.Short::toUnsignedLong)
is Node.Instr.I64Load32S ->
func.loadI32(ByteBuffer::getInt).i32ToI64()
is Node.Instr.I64Load32U ->
func.loadI32(ByteBuffer::getInt).toUnsigned64(java.lang.Integer::toUnsignedLong)
else -> throw IllegalArgumentException("Unknown load op $insn")
}
}
override fun storeOp(func: Func, insn: Node.Instr.Args.AlignOffset) = func.popExpecting(memClass).let { func ->
require(insn.offset <= Int.MAX_VALUE, { "Offsets > ${Int.MAX_VALUE} unsupported" }).let { this }
fun <T> Func.store32(fn: ByteBuffer.(Int, T) -> ByteBuffer, inClass: KClass<*>) =
// We add the index and then swap with the value already on the stack
this.popExpecting(inClass.java).addInsns(
insn.offset.toInt().const,
InsnNode(Opcodes.SWAP),
fn.reflect()!!.invokeVirtual()
).push(ByteBuffer::class.java)
fun <T> Func.store64(fn: ByteBuffer.(Int, T) -> ByteBuffer, inClass: KClass<*>) =
// We add the offset, dup_x2 to swap, and pop to get rid of the dup offset
this.popExpecting(inClass.java).addInsns(
insn.offset.toInt().const,
InsnNode(Opcodes.DUP_X2),
InsnNode(Opcodes.POP),
fn.reflect()!!.invokeVirtual()
).push(ByteBuffer::class.java)
fun Func.changeI64ToI32() =
this.popExpecting(Long::class.java).push(Int::class.java)
when (insn) {
is Node.Instr.I32Store ->
func.store32(ByteBuffer::putInt, Int::class)
is Node.Instr.I64Store ->
func.store64(ByteBuffer::putLong, Long::class)
is Node.Instr.F32Store ->
func.store32(ByteBuffer::putFloat, Float::class)
is Node.Instr.F64Store ->
func.store64(ByteBuffer::putDouble, Double::class)
is Node.Instr.I32Store8 ->
func.addInsns(InsnNode(Opcodes.I2B)).store32(ByteBuffer::put, Int::class)
is Node.Instr.I32Store16 ->
func.addInsns(InsnNode(Opcodes.I2S)).store32(ByteBuffer::putShort, Int::class)
is Node.Instr.I64Store8 ->
func.addInsns(InsnNode(Opcodes.L2I), InsnNode(Opcodes.I2B)).
changeI64ToI32().store32(ByteBuffer::put, Int::class)
is Node.Instr.I64Store16 ->
func.addInsns(InsnNode(Opcodes.L2I), InsnNode(Opcodes.I2S)).
changeI64ToI32().store32(ByteBuffer::putShort, Int::class)
is Node.Instr.I64Store32 ->
func.addInsns(InsnNode(Opcodes.L2I)).
changeI64ToI32().store32(ByteBuffer::putInt, Int::class)
else -> throw IllegalArgumentException("Unknown store op $insn")
}
}
companion object : ByteBufferMem()
}

View File

@ -0,0 +1,23 @@
package asmble.compile.jvm
import asmble.ast.Node
import org.objectweb.asm.tree.AbstractInsnNode
data class Func(val insns: List<AbstractInsnNode>, val stack: List<Class<*>>) {
fun addInsns(vararg insns: AbstractInsnNode) = copy(insns = this.insns + insns)
fun apply(fn: (Func) -> Func) = fn(this)
fun push(vararg types: Class<*>) = copy(stack = stack + types)
fun popExpectingNum() = popExpecting(Int::class.java, Long::class.java, Float::class.java, Double::class.java)
fun popExpecting(vararg types: Class<*>) = popExpectingAny(types::contains)
fun popExpectingAny(pred: (Class<*>) -> Boolean): Func {
require(stack.isNotEmpty(), { "Stack is empty" })
require(pred(stack.last()), { "Stack var type ${stack.last()} unexpected" })
return copy(stack = stack.dropLast(1))
}
}

View File

@ -0,0 +1,40 @@
package asmble.compile.jvm
import asmble.ast.Node
interface Mem<T : Any> {
// The class to accept as "memory"
val memClass: Class<T>
fun create(func: Func, maximum: Int?): Func
// Caller can trust the mem instance is on the stack and must handle it. If
// it's already there after call anyways, this can leave the mem inst on the
// stack and it will be reused or popped.
fun init(func: Func, initial: Int): Func
// Caller can trust the mem instance is on the stack, buildOffset puts
// offset on the stack. If it's already there after call anyways, this can
// leave the mem inst on the stack and it will be reused or popped.
fun data(func: Func, bytes: ByteArray, buildOffset: (Func) -> Func): Func
// Caller can trust the mem instance is on the stack.
fun currentMemory(func: Func): Func
// Caller can trust the mem instance and then i32 page count is on the stack.
// If it's already there after call anyways, this can leave the mem inst on
// the stack and it will be reused or popped.
fun growMemory(func: Func): Func
// Caller can trust the mem instance is on the stack
fun loadOp(func: Func, insn: Node.Instr.Args.AlignOffset): Func
// Caller can trust the mem instance is on the stack followed
// by the value. If it's already there after call anyways, this can
// leave the mem inst on the stack and it will be reused or popped.
fun storeOp(func: Func, insn: Node.Instr.Args.AlignOffset): Func
companion object {
const val PAGE_SIZE = 65536
}
}

View File

@ -2,9 +2,34 @@ package asmble.io
import asmble.ast.Node
import asmble.ast.SExpr
import asmble.ast.Script
open class AstToSExpr {
fun fromAction(v: Script.Cmd.Action) = when(v) {
is Script.Cmd.Action.Invoke -> newMulti("invoke", v.name) + v.string + fromInstrs(v.exprs)
is Script.Cmd.Action.Get -> newMulti("get", v.name) + v.string
}
fun fromAssertion(v: Script.Cmd.Assertion) = when(v) {
is Script.Cmd.Assertion.Return -> newMulti("assert_return") + fromAction(v.action) + fromInstrs(v.exprs)
is Script.Cmd.Assertion.ReturnNan -> newMulti("assert_return_nan") + fromAction(v.action)
is Script.Cmd.Assertion.Trap -> newMulti("assert_trap") + fromAction(v.action) + v.failure
is Script.Cmd.Assertion.Malformed -> newMulti("assert_malformed") + fromModule(v.module) + v.failure
is Script.Cmd.Assertion.Invalid -> newMulti("assert_invalid") + fromModule(v.module) + v.failure
is Script.Cmd.Assertion.SoftInvalid -> newMulti("assert_soft_invalid") + fromModule(v.module) + v.failure
is Script.Cmd.Assertion.Unlinkable -> newMulti("assert_unlinkable") + fromModule(v.module) + v.failure
is Script.Cmd.Assertion.TrapModule -> newMulti("assert_trap") + fromModule(v.module) + v.failure
}
fun fromCmd(v: Script.Cmd): SExpr.Multi = when(v) {
is Script.Cmd.Module -> fromModule(v.module)
is Script.Cmd.Register -> fromRegister(v)
is Script.Cmd.Action -> fromAction(v)
is Script.Cmd.Assertion -> fromAssertion(v)
is Script.Cmd.Meta -> fromMeta(v)
}
fun fromData(v: Node.Data) =
(newMulti("data") + v.index) + (newMulti("offset") + fromInstrs(v.offset)) + v.data.toString(Charsets.UTF_8)
@ -100,16 +125,19 @@ open class AstToSExpr {
fun fromMemorySig(v: Node.Type.Memory) = fromResizableLimits(v.limits)
fun fromMeta(v: Script.Cmd.Meta) = when(v) {
is Script.Cmd.Meta.Script -> newMulti("script", v.name) + fromScript(v.script)
is Script.Cmd.Meta.Input -> newMulti("input", v.name) + v.str
is Script.Cmd.Meta.Output -> newMulti("output", v.name) + v.str
}
fun fromModule(v: Node.Module, name: String? = null): SExpr.Multi {
var ret = newMulti("module", name)
// We only want types that are not referenced in import
val ignoreTypeIndices = v.imports.mapNotNull {
(it.kind as? Node.Import.Kind.Func)?.let { it.typeIndex }
}.toSet()
val ignoreTypeIndices = v.imports.mapNotNull { (it.kind as? Node.Import.Kind.Func)?.typeIndex }.toSet()
ret += v.types.filterIndexed { i, _ -> ignoreTypeIndices.contains(i) }.map { fromTypeDef(it) }
ret += v.funcs.map { fromFunc(it) }
ret += v.imports.map { fromImport(it, v.types) }
ret += v.exports.map(this::fromExport)
ret += v.tables.map { fromTable(it) }
@ -118,13 +146,18 @@ open class AstToSExpr {
ret += v.elems.map(this::fromElem)
ret += v.data.map(this::fromData)
ret += v.startFuncIndex?.let(this::fromStart)
ret += v.funcs.map { fromFunc(it) }
return ret
}
fun fromNum(v: Number) = fromString(v.toString())
fun fromRegister(v: Script.Cmd.Register) = (newMulti("register") + v.string) + v.name
fun fromResizableLimits(v: Node.ResizableLimits) = listOfNotNull(fromNum(v.initial), v.maximum?.let(this::fromNum))
fun fromScript(v: Script) = v.commands.map(this::fromCmd)
fun fromStart(v: Int) = newMulti("start") + v
fun fromString(v: String, quoted: Boolean = false) = SExpr.Symbol(v, quoted)

View File

@ -224,7 +224,7 @@ open class SExprToAst {
val name = exp.maybeName(currIndex)
if (name != null) currIndex++
val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
if (maybeImpExp == null) currIndex++
if (maybeImpExp != null) currIndex++
val sig = toGlobalSig(exp.vals[currIndex])
currIndex++
val (instrs, _) = toInstrs(exp, currIndex, ExprContext(nameMap))
@ -268,7 +268,12 @@ open class SExprToAst {
}
}
fun toInstrs(exp: SExpr.Multi, offset: Int, ctx: ExprContext): Pair<List<Node.Instr>, Int> {
fun toInstrs(
exp: SExpr.Multi,
offset: Int,
ctx: ExprContext,
mustCompleteExp: Boolean = true
): Pair<List<Node.Instr>, Int> {
var runningOffset = 0
var ret = emptyList<Node.Instr>()
while (offset + runningOffset < exp.vals.size) {
@ -277,13 +282,16 @@ open class SExprToAst {
ret += maybeInstrAndOffset.first
runningOffset += maybeInstrAndOffset.second
}
if (mustCompleteExp) require(offset + runningOffset == exp.vals.size) {
"Unrecognized instruction: ${exp.vals[offset + runningOffset]}"
}
return Pair(ret, runningOffset)
}
fun toInstrMaybe(exp: SExpr.Multi, offset: Int, ctx: ExprContext): Pair<List<Node.Instr>, Int> {
// <expr>
if (exp.vals[offset] is SExpr.Multi) {
val exprs = toExprMaybe(exp, ctx)
val exprs = toExprMaybe(exp.vals[offset] as SExpr.Multi, ctx)
return Pair(exprs, if (exprs.isEmpty()) 0 else 1)
}
// <op>
@ -364,7 +372,7 @@ open class SExprToAst {
val name = exp.maybeName(currIndex)
if (name != null) currIndex++
val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
if (maybeImpExp == null) currIndex++
if (maybeImpExp != null) currIndex++
// Try data approach
if (exp.vals[currIndex] is SExpr.Multi) throw Exception("Data string not yet supported for memory")
return Triple(name, toMemorySig(exp, currIndex), maybeImpExp)
@ -537,11 +545,11 @@ open class SExprToAst {
is InstrOp.VarOp.IndexArg -> Pair(op.create(oneVar()), 2)
is InstrOp.MemOp.AlignOffsetArg -> {
var count = 1
var instrOffset = 0
var instrOffset = 0L
var instrAlign = 0
if (exp.vals.size > offset + count) exp.vals[offset + count].symbolStr().also {
if (it != null && it.startsWith("offset=")) {
instrOffset = it.substring(7).toInt()
instrOffset = it.substring(7).toLong()
count++
}
}
@ -605,7 +613,7 @@ open class SExprToAst {
val name = exp.maybeName(currIndex)
if (name != null) currIndex++
val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
if (maybeImpExp == null) currIndex++
if (maybeImpExp != null) currIndex++
// Try elem type approach
val elemType = toElemTypeMaybe(exp, currIndex)
if (elemType != null) {

View File

@ -2,7 +2,11 @@ package asmble.io
import asmble.ast.SExpr
open class SExprToStr(val depthBeforeNewline: Int, val indent: String) {
open class SExprToStr(val depthBeforeNewline: Int, val countBeforeNewlineAll: Int, val indent: String) {
@Suppress("UNCHECKED_CAST") // TODO: why?
fun fromSExpr(vararg exp: SExpr): String = appendAll(exp.asList(), StringBuilder()).trim().toString()
@Suppress("UNCHECKED_CAST") // TODO: why?
fun <T : Appendable> append(exp: SExpr, sb: T = StringBuilder() as T, indentLevel: Int = 0) = when(exp) {
is SExpr.Symbol -> appendSymbol(exp, sb)
@ -19,6 +23,7 @@ open class SExprToStr(val depthBeforeNewline: Int, val indent: String) {
if (it in "'\"\\") "\\$it"
else if (it == '\n') "\\n"
else if (it == '\t') "\\t"
else if (it == ' ') " "
else if (!it.requiresQuote) it.toString()
else "\\" + (it.toInt() and 0xFF).toString(16)
)
@ -31,29 +36,51 @@ open class SExprToStr(val depthBeforeNewline: Int, val indent: String) {
@Suppress("UNCHECKED_CAST") // TODO: why?
fun <T : Appendable> appendMulti(exp: SExpr.Multi, sb: T = StringBuilder() as T, indentLevel: Int = 0): T {
sb.append('(')
exp.vals.forEachIndexed { index, sub ->
if (sub.maxDepth() <= depthBeforeNewline) {
if (index > 0) sb.append(' ')
append(sub, sb, indentLevel)
} else {
sb.append("\n").append(indent.repeat(indentLevel + 1))
append(sub, sb, indentLevel + 1)
sb.append("\n").append(indent.repeat(indentLevel))
}
}
appendAll(exp.vals, sb, indentLevel)
sb.append(')')
return sb
}
@Suppress("UNCHECKED_CAST") // TODO: why?
fun <T : Appendable> appendAll(exps: List<SExpr>, sb: T = StringBuilder() as T, indentLevel: Int = 0): T {
val newlineAll = exps.sumBy { it.count() } >= countBeforeNewlineAll
var wasLastNewline = false
exps.forEachIndexed { index, sub ->
// No matter what, if the first is a symbol
val isFirstAsSymbol = index == 0 && sub is SExpr.Symbol
val shouldNewline = !isFirstAsSymbol && (newlineAll || sub.maxDepth() > depthBeforeNewline)
if (!shouldNewline) {
if (index > 0 && !wasLastNewline) sb.append(' ')
append(sub, sb, indentLevel)
wasLastNewline = false
} else {
if (!wasLastNewline) sb.append("\n").append(indent.repeat(indentLevel))
append(sub, sb, indentLevel + 1)
sb.append("\n")
if (index < exps.size - 1) sb.append(indent.repeat(indentLevel))
else if (indentLevel > 0) sb.append(indent.repeat(indentLevel - 1))
wasLastNewline = true
}
}
return sb
}
val String.requiresQuote: Boolean get() =
this.find { it.requiresQuote } != null
val Char.requiresQuote: Boolean get() =
this > '~' || (!this.isLetterOrDigit() && this !in "_.+-*/^~=<>!?@#$%&|:'`")
fun SExpr.count(): Int = when(this) {
is SExpr.Symbol -> 1
is SExpr.Multi -> this.vals.sumBy { it.count() }
}
fun SExpr.maxDepth(): Int = when(this) {
is SExpr.Symbol -> 0
is SExpr.Multi -> 1 + (this.vals.map { it.maxDepth() }.max() ?: 0)
}
companion object : SExprToStr(depthBeforeNewline = 3, indent = " ")
companion object : SExprToStr(depthBeforeNewline = 3, countBeforeNewlineAll = 10, indent = " ") {
val Compact = SExprToStr(depthBeforeNewline = Int.MAX_VALUE, countBeforeNewlineAll = Int.MAX_VALUE, indent = "")
}
}

View File

@ -1,31 +1,38 @@
package asmble.io
import asmble.ast.SExpr
import asmble.util.Either
import asmble.util.IdentityMap
import asmble.util.plus
open class StrToSExpr {
data class ParseError(val line: Int, val char: Int, val msg: String)
sealed class ParseResult {
data class Pos(val line: Int, val char: Int)
data class Success(val vals: List<SExpr>, val exprOffsetMap: IdentityMap<SExpr, Pos>) : ParseResult()
data class Error(val pos: Pos, val msg: String) : ParseResult()
}
fun parse(str: CharSequence): Either<SExpr.Multi, ParseError> {
fun parse(str: CharSequence): ParseResult {
val state = ParseState(str)
var ret = emptyList<SExpr>()
while (!state.isEof) {
ret += state.nextSExpr() ?: break
if (state.err != null) {
val line = str.substring(0, state.offset).substringCount("\n") + 1
val char = state.offset - str.lastIndexOf('\n', state.offset)
return Either.Right(ParseError(line, char, state.err!!))
if (state.err != null) return ParseResult.Error(str.posFromOffset(state.offset), state.err!!)
}
}
if (ret.size == 1 && ret[0] is SExpr.Multi) return Either.Left(ret[0] as SExpr.Multi)
else return Either.Left(SExpr.Multi(ret))
val retVals = if (ret.size == 1 && ret[0] is SExpr.Multi) (ret[0] as SExpr.Multi).vals else ret
return ParseResult.Success(retVals, state.exprOffsetMap)
}
private class ParseState(val str: CharSequence, var offset: Int = 0, var err: String? = null) {
private class ParseState(
val str: CharSequence,
var exprOffsetMap: IdentityMap<SExpr, ParseResult.Pos> = IdentityMap(),
var offset: Int = 0,
var err: String? = null
) {
fun nextSExpr(): SExpr? {
skipWhitespace()
if (isEof) return null
// What type of expr do we have here?
val origOffset = offset
when (str[offset]) {
'(' -> {
offset++
@ -37,6 +44,7 @@ open class StrToSExpr {
if (err == null) {
if (str[offset] == ')') offset++ else err = "EOF when expected ')'"
}
exprOffsetMap += ret to str.posFromOffset(origOffset)
return ret
}
'"' -> {
@ -74,13 +82,17 @@ open class StrToSExpr {
}
if (err == null && str[offset] != '"') err = "EOF when expected '\"'"
else if (err == null) offset++
return SExpr.Symbol(retStr, true)
val ret = SExpr.Symbol(retStr, true)
exprOffsetMap += ret to str.posFromOffset(origOffset)
return ret
}
else -> {
// Go until next quote or whitespace or parens
val origOffset = offset
while (!isEof && !"();\"".contains(str[offset]) && !str[offset].isWhitespace()) offset++
return if (origOffset == offset) null else SExpr.Symbol(str.substring(origOffset, offset))
if (origOffset == offset) return null
val ret = SExpr.Symbol(str.substring(origOffset, offset))
exprOffsetMap += ret to str.posFromOffset(origOffset)
return ret
}
}
}
@ -115,6 +127,11 @@ open class StrToSExpr {
val isEof: Boolean inline get() = offset >= str.length
}
fun CharSequence.posFromOffset(offset: Int) = ParseResult.Pos(
line = this.substring(0, offset).substringCount("\n") + 1,
char = offset - this.lastIndexOf('\n', offset)
)
fun CharSequence.substringCount(str: String): Int {
var lastIndex = 0
var count = 0

View File

@ -0,0 +1,13 @@
package asmble.util
class IdentityMap<K, V>(override val entries: Set<Map.Entry<K, V>> = emptySet()) : AbstractMap<K, V>() {
override fun containsKey(key: K) = entries.find { it.key === key } != null
override operator fun get(key: K) = entries.find { it.key === key }?.value
}
public operator fun <K, V> IdentityMap<K, V>.plus(pair: Pair<K, V>): IdentityMap<K, V> {
return IdentityMap(this.entries + object : Map.Entry<K, V> {
override val key: K get() = pair.first
override val value: V get() = pair.second
})
}

View File

@ -9,7 +9,11 @@ class CoreTest(val unit: CoreTestUnit) {
@Test
fun testName() {
println("\nYay: ${unit.name}: ${unit.ast}")
println()
println("AST SExpr: " + unit.ast)
println("AST Str: " + SExprToStr.fromSExpr(*unit.ast.toTypedArray()))
println("AST: " + unit.script)
println("AST Str: " + SExprToStr.fromSExpr(*AstToSExpr.fromScript(unit.script).toTypedArray()))
}
companion object {

View File

@ -1,36 +1,49 @@
package asmble.io
import asmble.ast.SExpr
import asmble.util.Either
import asmble.ast.Script
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Paths
import java.util.stream.Collectors
class CoreTestUnit(val name: String, val ast: SExpr.Multi, val expectedOutput: String?) {
class CoreTestUnit(val name: String, val wast: String, val expectedOutput: String?) {
override fun toString() = "Spec unit: $name"
val parseResult: StrToSExpr.ParseResult.Success by lazy {
StrToSExpr.parse(wast).let {
when (it) {
is StrToSExpr.ParseResult.Error -> throw Exception("$name[${it.pos}] Parse fail: ${it.msg}")
is StrToSExpr.ParseResult.Success -> it
}
}
}
val ast: List<SExpr> get() = parseResult.vals
val script: Script by lazy { SExprToAst.toScript(SExpr.Multi(ast)) }
companion object {
val unitsPath = "/spec/test/core"
fun loadAll(): List<CoreTestUnit> {
return loadFromResourcePath("/local-spec") + loadFromResourcePath(unitsPath)
}
fun loadFromResourcePath(basePath: String): List<CoreTestUnit> {
require(basePath.last() != '/')
val jcls = CoreTestUnit::class.java
val uri = jcls.getResource(unitsPath).toURI()
val uri = jcls.getResource(basePath).toURI()
val fs = if (uri.scheme == "jar") FileSystems.newFileSystem(uri, emptyMap<String, Any>()) else null
fs.use { fs ->
val path = fs?.getPath(unitsPath) ?: Paths.get(uri)
return Files.walk(path, 1).filter { it.toString().endsWith(".wast") }.map {
println("Loading $it")
val parseResult = StrToSExpr.parse(it.toUri().toURL().readText())
when(parseResult) {
is Either.Right -> throw Exception("Error loading $it: ${parseResult.v}")
is Either.Left -> {
val path = fs?.getPath(basePath) ?: Paths.get(uri)
return Files.walk(path, 1).filter { it.toString().endsWith("address.wast") }.map {
val name = it.fileName.toString().substringBeforeLast(".wast")
val expectedOutput =
jcls.getResource("/spec/test/core/expected-output/$name.wast.log")?.readText()
CoreTestUnit(name, parseResult.v, expectedOutput)
}
}
CoreTestUnit(
name = name,
wast = it.toUri().toURL().readText(),
expectedOutput = jcls.getResource("$basePath/expected-output/$name.wast.log")?.readText()
)
}.collect(Collectors.toList())
}
}