diff --git a/src/main/kotlin/asmble/compile/jvm/AstToAsm.kt b/src/main/kotlin/asmble/compile/jvm/AstToAsm.kt index 22b9e0b..0d0b23a 100644 --- a/src/main/kotlin/asmble/compile/jvm/AstToAsm.kt +++ b/src/main/kotlin/asmble/compile/jvm/AstToAsm.kt @@ -373,6 +373,11 @@ open class AstToAsm { } fun addExports(ctx: ClsContext) { + // Make sure there are no dupes + ctx.mod.exports.fold(emptySet()) { prev, exp -> + if (prev.contains(exp.field)) throw CompileErr.DuplicateExport(exp.field) + prev + exp.field + } // Export all functions as named methods that delegate ctx.mod.exports.forEach { when (it.kind) { @@ -459,7 +464,7 @@ open class AstToAsm { fun addExportMemory(ctx: ClsContext, export: Node.Export) { // Create simple getter for the memory - require(export.index == 0) { "Only memory at index 0 supported" } + if (export.index != 0) throw CompileErr.UnknownMemory(export.index) val method = MethodNode(Opcodes.ACC_PUBLIC, "get" + export.field.javaIdent.capitalize(), "()" + ctx.mem.memType.asmDesc, null, null) method.addInsns( @@ -472,7 +477,7 @@ open class AstToAsm { fun addExportTable(ctx: ClsContext, export: Node.Export) { // Create simple getter for the table - require(export.index == 0) { "Only table at index 0 supported" } + if (export.index != 0) throw CompileErr.UnknownTable(export.index) val method = MethodNode(Opcodes.ACC_PUBLIC, "get" + export.field.javaIdent.capitalize(), "()" + Array::class.ref.asmDesc, null, null) method.addInsns( diff --git a/src/main/kotlin/asmble/compile/jvm/ClsContext.kt b/src/main/kotlin/asmble/compile/jvm/ClsContext.kt index 47caf05..c01f40d 100644 --- a/src/main/kotlin/asmble/compile/jvm/ClsContext.kt +++ b/src/main/kotlin/asmble/compile/jvm/ClsContext.kt @@ -36,7 +36,7 @@ data class ClsContext( mod.tables.isNotEmpty() || mod.imports.any { it.kind is Node.Import.Kind.Table } } - fun assertHasMemory() { if (!hasMemory) throw CompileErr.UnknownMemory() } + fun assertHasMemory() { if (!hasMemory) throw CompileErr.UnknownMemory(0) } fun typeAtIndex(index: Int) = mod.types.getOrNull(index) ?: throw CompileErr.UnknownType(index) diff --git a/src/main/kotlin/asmble/compile/jvm/CompileErr.kt b/src/main/kotlin/asmble/compile/jvm/CompileErr.kt index 089f3d8..47ff21e 100644 --- a/src/main/kotlin/asmble/compile/jvm/CompileErr.kt +++ b/src/main/kotlin/asmble/compile/jvm/CompileErr.kt @@ -82,11 +82,11 @@ sealed class CompileErr(message: String, cause: Throwable? = null) : RuntimeExce override val asmErrString get() = "unknown global" } - class UnknownMemory : CompileErr("No memory present") { + class UnknownMemory(val index: Int) : CompileErr("No memory present at index $index") { override val asmErrString get() = "unknown memory" } - class UnknownTable : CompileErr("No table present") { + class UnknownTable(val index: Int) : CompileErr("No table present at index $index") { override val asmErrString get() = "unknown table" } @@ -127,4 +127,8 @@ sealed class CompileErr(message: String, cause: Throwable? = null) : RuntimeExce ) : CompileErr("Start function at $index must take no params and return nothing") { override val asmErrString get() = "start function" } + + class DuplicateExport(val name: String) : CompileErr("Duplicate export '$name'") { + override val asmErrString get() = "duplicate export name" + } } \ No newline at end of file diff --git a/src/main/kotlin/asmble/compile/jvm/Func.kt b/src/main/kotlin/asmble/compile/jvm/Func.kt index 3cab64d..fa5b7b9 100644 --- a/src/main/kotlin/asmble/compile/jvm/Func.kt +++ b/src/main/kotlin/asmble/compile/jvm/Func.kt @@ -65,6 +65,9 @@ data class Func( if (isStackEmptyForBlock(currBlock)) { // Just fake it if dead if (currBlock.unreachable) return this to TypeRef.Unknown + if (currBlock.insn is Node.Instr.If && !currBlock.hasElse && currBlock.unreachableInIf) + return this to TypeRef.Unknown + if (currBlock.hasElse && currBlock.unreachableInElse) return this to TypeRef.Unknown throw CompileErr.StackMismatch(emptyArray(), null) } return copy(stack = stack.dropLast(1)) to stack.last() diff --git a/src/main/kotlin/asmble/compile/jvm/FuncBuilder.kt b/src/main/kotlin/asmble/compile/jvm/FuncBuilder.kt index baf4af7..f8da1c9 100644 --- a/src/main/kotlin/asmble/compile/jvm/FuncBuilder.kt +++ b/src/main/kotlin/asmble/compile/jvm/FuncBuilder.kt @@ -1,6 +1,8 @@ package asmble.compile.jvm import asmble.ast.Node +import asmble.io.AstToSExpr +import asmble.io.SExprToStr import asmble.util.Either import asmble.util.add import org.objectweb.asm.Handle @@ -16,6 +18,7 @@ open class FuncBuilder { // TODO: validate local size? // TODO: initialize non-param locals? ctx.debug { "Building function ${ctx.funcName(index)}" } + ctx.trace { "Function ast:\n${SExprToStr.fromSExpr(AstToSExpr.fromFunc(f))}" } var func = Func( access = Opcodes.ACC_PRIVATE, name = ctx.funcName(index), @@ -582,7 +585,7 @@ open class FuncBuilder { if (block.endTypes.isNotEmpty() && !block.hasElse) throw CompileErr.IfThenValueWithoutElse() // If the block was an if/then w/ a stack but the else doesn't match it - if (block.hasElse && block.thenStackOnIf != fn.stack) + if (block.hasElse && !block.unreachableInIf && !block.unreachableInElse && block.thenStackOnIf != fn.stack) throw CompileErr.BlockEndMismatch(block.thenStackOnIf, fn.stack) } // Put the stack where it should be @@ -1182,7 +1185,7 @@ open class FuncBuilder { } fun applyCallIndirectInsn(ctx: FuncContext, fn: Func, index: Int): Func { - if (!ctx.cls.hasTable) throw CompileErr.UnknownTable() + if (!ctx.cls.hasTable) throw CompileErr.UnknownTable(0) // For this we do an invokedynamic which calls the bootstrap method. The // bootstrap method is a synthetic method embedded into this module. The // resulting method handle accepts all method params THEN "this" THEN @@ -1213,7 +1216,9 @@ open class FuncBuilder { } fun applyReturnInsn(ctx: FuncContext, fn: Func): Func { - val block = fn.blockStack.first() + // If the current stakc is unreachable, we consider that our block since it + // will pop properly. + val block = if (fn.currentBlock.unreachable) fn.currentBlock else fn.blockStack.first() popForBlockEscape(ctx, fn, block).let { fn -> return when (ctx.node.type.ret) { null -> diff --git a/src/main/kotlin/asmble/run/jvm/ScriptContext.kt b/src/main/kotlin/asmble/run/jvm/ScriptContext.kt index 4bb8524..da8d31b 100644 --- a/src/main/kotlin/asmble/run/jvm/ScriptContext.kt +++ b/src/main/kotlin/asmble/run/jvm/ScriptContext.kt @@ -180,7 +180,11 @@ data class ScriptContext( } fun doGet(cmd: Script.Cmd.Action.Get): Pair { - TODO() + // Grab last module or named one + val module = if (cmd.name == null) modules.last() else modules.first { it.name == cmd.name } + // Just call the getter + val getter = module.cls.getDeclaredMethod("get" + cmd.string.javaIdent.capitalize()) + return getter.returnType.valueType!! to getter.invoke(module.instance) } fun doInvoke(cmd: Script.Cmd.Action.Invoke): Pair { diff --git a/src/test/kotlin/asmble/SpecTestUnit.kt b/src/test/kotlin/asmble/SpecTestUnit.kt index 1acb475..460e109 100644 --- a/src/test/kotlin/asmble/SpecTestUnit.kt +++ b/src/test/kotlin/asmble/SpecTestUnit.kt @@ -41,16 +41,14 @@ class SpecTestUnit(val name: String, val wast: String, val expectedOutput: Strin /* TODO: We are going down in order. One's we have not yet handled: - - br.wast - Not handling tables yet - - br_table.wast - Not handling tables yet - - call_indirect.wast - Not handling tables yet - - exports.wast - Not handling tables yet + - br_table.wast - Table issues on jumps - func.wast - Not handling tables yet - func_ptrs.wast - Not handling tables yet - imports.wast - No memory exports yet - left-to-right.wast - Not handling tables yet - linking.wast - Not handling tables yet - return.wast - Not handling tables yet + - switch.wast - Table issues on jumps (ref "argument switch") - typecheck.wast - Not handling tables yet - unreachable.wast - Not handling tables yet */ @@ -62,6 +60,7 @@ class SpecTestUnit(val name: String, val wast: String, val expectedOutput: Strin "block.wast", "block-end-label-mismatch.fail.wast", "block-end-label-superfluous.wast", + "br.wast", "br_if.wast", "break-drop.wast", "call.wast", @@ -70,6 +69,7 @@ class SpecTestUnit(val name: String, val wast: String, val expectedOutput: Strin "conversions.wast", "custom_section.wast", "endianness.wast", + "exports.wast", "f32.load32.fail.wast", "f32.load64.fail.wast", "f32.store32.fail.wast", @@ -146,7 +146,6 @@ class SpecTestUnit(val name: String, val wast: String, val expectedOutput: Strin "store-align-0.fail.wast", "store-align-odd.fail.wast", "store_retval.wast", - // "switch.wast" TODO: we are in trouble here on the "argument switch" "tee_local.wast", "traps.wast", "unreached-invalid.wast", @@ -159,11 +158,12 @@ class SpecTestUnit(val name: String, val wast: String, val expectedOutput: Strin "float_exprs" to this::isNanMismatch ) - fun isNanMismatch(t: Throwable) = - (((t as? ScriptAssertionError)?. - assertion as? Script.Cmd.Assertion.Return)?. - action as? Script.Cmd.Action.Invoke)?. - string?.contains("nan") ?: false + fun isNanMismatch(t: Throwable) = t is ScriptAssertionError && ( + t.assertion is Script.Cmd.Assertion.ReturnNan || + (t.assertion is Script.Cmd.Assertion.Return && (t.assertion as Script.Cmd.Assertion.Return).let { + (it.action as? Script.Cmd.Action.Invoke)?.string?.contains("nan") ?: false + }) + ) val unitsPath = "/spec/test/core"