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' version '1.0-SNAPSHOT'
buildscript { buildscript {
ext.kotlin_version = '1.1.0' ext.kotlin_version = '1.1.1'
ext.asm_version = '5.2'
repositories { repositories {
mavenCentral() mavenCentral()
@ -21,6 +22,9 @@ repositories {
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 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" 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 Index : Args { val index: Int }
interface Reserved : Args { val reserved: Boolean } interface Reserved : Args { val reserved: Boolean }
interface ReservedIndex : Index, Reserved 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 } 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 data class SetGlobal(override val index: Int) : Instr(), Args.Index
// Memory operators // Memory operators
data class I32Load(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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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: Int) : 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 CurrentMemory(override val reserved: Boolean) : Instr(), Args.Reserved
data class GrowMemory(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>() { 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>() data class ReservedArg(override val name: String, val create: (Boolean) -> Instr) : MemOp<Instr.Args.Reserved>()
} }

View File

@ -1,6 +1,12 @@
package asmble.ast package asmble.ast
import asmble.io.SExprToStr
sealed class SExpr { sealed class SExpr {
data class Multi(val vals: List<SExpr> = emptyList()) : SExpr() data class Multi(val vals: List<SExpr> = emptyList()) : SExpr() {
data class Symbol(val contents: String = "", val quoted: Boolean = false) : 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.Node
import asmble.ast.SExpr import asmble.ast.SExpr
import asmble.ast.Script
open class AstToSExpr { 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) = fun fromData(v: Node.Data) =
(newMulti("data") + v.index) + (newMulti("offset") + fromInstrs(v.offset)) + v.data.toString(Charsets.UTF_8) (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 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 { fun fromModule(v: Node.Module, name: String? = null): SExpr.Multi {
var ret = newMulti("module", name) var ret = newMulti("module", name)
// We only want types that are not referenced in import // We only want types that are not referenced in import
val ignoreTypeIndices = v.imports.mapNotNull { val ignoreTypeIndices = v.imports.mapNotNull { (it.kind as? Node.Import.Kind.Func)?.typeIndex }.toSet()
(it.kind as? Node.Import.Kind.Func)?.let { it.typeIndex }
}.toSet()
ret += v.types.filterIndexed { i, _ -> ignoreTypeIndices.contains(i) }.map { fromTypeDef(it) } 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.imports.map { fromImport(it, v.types) }
ret += v.exports.map(this::fromExport) ret += v.exports.map(this::fromExport)
ret += v.tables.map { fromTable(it) } ret += v.tables.map { fromTable(it) }
@ -118,13 +146,18 @@ open class AstToSExpr {
ret += v.elems.map(this::fromElem) ret += v.elems.map(this::fromElem)
ret += v.data.map(this::fromData) ret += v.data.map(this::fromData)
ret += v.startFuncIndex?.let(this::fromStart) ret += v.startFuncIndex?.let(this::fromStart)
ret += v.funcs.map { fromFunc(it) }
return ret return ret
} }
fun fromNum(v: Number) = fromString(v.toString()) 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 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 fromStart(v: Int) = newMulti("start") + v
fun fromString(v: String, quoted: Boolean = false) = SExpr.Symbol(v, quoted) 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) val name = exp.maybeName(currIndex)
if (name != null) currIndex++ if (name != null) currIndex++
val maybeImpExp = toImportOrExportMaybe(exp, currIndex) val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
if (maybeImpExp == null) currIndex++ if (maybeImpExp != null) currIndex++
val sig = toGlobalSig(exp.vals[currIndex]) val sig = toGlobalSig(exp.vals[currIndex])
currIndex++ currIndex++
val (instrs, _) = toInstrs(exp, currIndex, ExprContext(nameMap)) 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 runningOffset = 0
var ret = emptyList<Node.Instr>() var ret = emptyList<Node.Instr>()
while (offset + runningOffset < exp.vals.size) { while (offset + runningOffset < exp.vals.size) {
@ -277,13 +282,16 @@ open class SExprToAst {
ret += maybeInstrAndOffset.first ret += maybeInstrAndOffset.first
runningOffset += maybeInstrAndOffset.second runningOffset += maybeInstrAndOffset.second
} }
if (mustCompleteExp) require(offset + runningOffset == exp.vals.size) {
"Unrecognized instruction: ${exp.vals[offset + runningOffset]}"
}
return Pair(ret, runningOffset) return Pair(ret, runningOffset)
} }
fun toInstrMaybe(exp: SExpr.Multi, offset: Int, ctx: ExprContext): Pair<List<Node.Instr>, Int> { fun toInstrMaybe(exp: SExpr.Multi, offset: Int, ctx: ExprContext): Pair<List<Node.Instr>, Int> {
// <expr> // <expr>
if (exp.vals[offset] is SExpr.Multi) { 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) return Pair(exprs, if (exprs.isEmpty()) 0 else 1)
} }
// <op> // <op>
@ -364,7 +372,7 @@ open class SExprToAst {
val name = exp.maybeName(currIndex) val name = exp.maybeName(currIndex)
if (name != null) currIndex++ if (name != null) currIndex++
val maybeImpExp = toImportOrExportMaybe(exp, currIndex) val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
if (maybeImpExp == null) currIndex++ if (maybeImpExp != null) currIndex++
// Try data approach // Try data approach
if (exp.vals[currIndex] is SExpr.Multi) throw Exception("Data string not yet supported for memory") if (exp.vals[currIndex] is SExpr.Multi) throw Exception("Data string not yet supported for memory")
return Triple(name, toMemorySig(exp, currIndex), maybeImpExp) 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.VarOp.IndexArg -> Pair(op.create(oneVar()), 2)
is InstrOp.MemOp.AlignOffsetArg -> { is InstrOp.MemOp.AlignOffsetArg -> {
var count = 1 var count = 1
var instrOffset = 0 var instrOffset = 0L
var instrAlign = 0 var instrAlign = 0
if (exp.vals.size > offset + count) exp.vals[offset + count].symbolStr().also { if (exp.vals.size > offset + count) exp.vals[offset + count].symbolStr().also {
if (it != null && it.startsWith("offset=")) { if (it != null && it.startsWith("offset=")) {
instrOffset = it.substring(7).toInt() instrOffset = it.substring(7).toLong()
count++ count++
} }
} }
@ -605,7 +613,7 @@ open class SExprToAst {
val name = exp.maybeName(currIndex) val name = exp.maybeName(currIndex)
if (name != null) currIndex++ if (name != null) currIndex++
val maybeImpExp = toImportOrExportMaybe(exp, currIndex) val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
if (maybeImpExp == null) currIndex++ if (maybeImpExp != null) currIndex++
// Try elem type approach // Try elem type approach
val elemType = toElemTypeMaybe(exp, currIndex) val elemType = toElemTypeMaybe(exp, currIndex)
if (elemType != null) { if (elemType != null) {

View File

@ -2,7 +2,11 @@ package asmble.io
import asmble.ast.SExpr 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? @Suppress("UNCHECKED_CAST") // TODO: why?
fun <T : Appendable> append(exp: SExpr, sb: T = StringBuilder() as T, indentLevel: Int = 0) = when(exp) { fun <T : Appendable> append(exp: SExpr, sb: T = StringBuilder() as T, indentLevel: Int = 0) = when(exp) {
is SExpr.Symbol -> appendSymbol(exp, sb) is SExpr.Symbol -> appendSymbol(exp, sb)
@ -19,6 +23,7 @@ open class SExprToStr(val depthBeforeNewline: Int, val indent: String) {
if (it in "'\"\\") "\\$it" if (it in "'\"\\") "\\$it"
else if (it == '\n') "\\n" else if (it == '\n') "\\n"
else if (it == '\t') "\\t" else if (it == '\t') "\\t"
else if (it == ' ') " "
else if (!it.requiresQuote) it.toString() else if (!it.requiresQuote) it.toString()
else "\\" + (it.toInt() and 0xFF).toString(16) 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? @Suppress("UNCHECKED_CAST") // TODO: why?
fun <T : Appendable> appendMulti(exp: SExpr.Multi, sb: T = StringBuilder() as T, indentLevel: Int = 0): T { fun <T : Appendable> appendMulti(exp: SExpr.Multi, sb: T = StringBuilder() as T, indentLevel: Int = 0): T {
sb.append('(') sb.append('(')
exp.vals.forEachIndexed { index, sub -> appendAll(exp.vals, sb, indentLevel)
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))
}
}
sb.append(')') sb.append(')')
return sb 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() = val String.requiresQuote: Boolean get() =
this.find { it.requiresQuote } != null this.find { it.requiresQuote } != null
val Char.requiresQuote: Boolean get() = val Char.requiresQuote: Boolean get() =
this > '~' || (!this.isLetterOrDigit() && this !in "_.+-*/^~=<>!?@#$%&|:'`") 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) { fun SExpr.maxDepth(): Int = when(this) {
is SExpr.Symbol -> 0 is SExpr.Symbol -> 0
is SExpr.Multi -> 1 + (this.vals.map { it.maxDepth() }.max() ?: 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 package asmble.io
import asmble.ast.SExpr import asmble.ast.SExpr
import asmble.util.Either import asmble.util.IdentityMap
import asmble.util.plus
open class StrToSExpr { 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) val state = ParseState(str)
var ret = emptyList<SExpr>() var ret = emptyList<SExpr>()
while (!state.isEof) { while (!state.isEof) {
ret += state.nextSExpr() ?: break ret += state.nextSExpr() ?: break
if (state.err != null) { if (state.err != null) return ParseResult.Error(str.posFromOffset(state.offset), state.err!!)
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!!))
} }
} val retVals = if (ret.size == 1 && ret[0] is SExpr.Multi) (ret[0] as SExpr.Multi).vals else ret
if (ret.size == 1 && ret[0] is SExpr.Multi) return Either.Left(ret[0] as SExpr.Multi) return ParseResult.Success(retVals, state.exprOffsetMap)
else return Either.Left(SExpr.Multi(ret))
} }
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? { fun nextSExpr(): SExpr? {
skipWhitespace() skipWhitespace()
if (isEof) return null if (isEof) return null
// What type of expr do we have here? // What type of expr do we have here?
val origOffset = offset
when (str[offset]) { when (str[offset]) {
'(' -> { '(' -> {
offset++ offset++
@ -37,6 +44,7 @@ open class StrToSExpr {
if (err == null) { if (err == null) {
if (str[offset] == ')') offset++ else err = "EOF when expected ')'" if (str[offset] == ')') offset++ else err = "EOF when expected ')'"
} }
exprOffsetMap += ret to str.posFromOffset(origOffset)
return ret return ret
} }
'"' -> { '"' -> {
@ -74,13 +82,17 @@ open class StrToSExpr {
} }
if (err == null && str[offset] != '"') err = "EOF when expected '\"'" if (err == null && str[offset] != '"') err = "EOF when expected '\"'"
else if (err == null) offset++ 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 -> { else -> {
// Go until next quote or whitespace or parens // Go until next quote or whitespace or parens
val origOffset = offset
while (!isEof && !"();\"".contains(str[offset]) && !str[offset].isWhitespace()) 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 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 { fun CharSequence.substringCount(str: String): Int {
var lastIndex = 0 var lastIndex = 0
var count = 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 @Test
fun testName() { 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 { companion object {

View File

@ -1,36 +1,49 @@
package asmble.io package asmble.io
import asmble.ast.SExpr import asmble.ast.SExpr
import asmble.util.Either import asmble.ast.Script
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Paths import java.nio.file.Paths
import java.util.stream.Collectors 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" 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 { companion object {
val unitsPath = "/spec/test/core" val unitsPath = "/spec/test/core"
fun loadAll(): List<CoreTestUnit> { 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 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 val fs = if (uri.scheme == "jar") FileSystems.newFileSystem(uri, emptyMap<String, Any>()) else null
fs.use { fs -> fs.use { fs ->
val path = fs?.getPath(unitsPath) ?: Paths.get(uri) val path = fs?.getPath(basePath) ?: Paths.get(uri)
return Files.walk(path, 1).filter { it.toString().endsWith(".wast") }.map { return Files.walk(path, 1).filter { it.toString().endsWith("address.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 name = it.fileName.toString().substringBeforeLast(".wast") val name = it.fileName.toString().substringBeforeLast(".wast")
val expectedOutput = CoreTestUnit(
jcls.getResource("/spec/test/core/expected-output/$name.wast.log")?.readText() name = name,
CoreTestUnit(name, parseResult.v, expectedOutput) wast = it.toUri().toURL().readText(),
} expectedOutput = jcls.getResource("$basePath/expected-output/$name.wast.log")?.readText()
} )
}.collect(Collectors.toList()) }.collect(Collectors.toList())
} }
} }