mirror of
https://github.com/fluencelabs/asmble
synced 2025-04-25 06:42:22 +00:00
Work on globals and binary oddities
This commit is contained in:
parent
5a6a68aa61
commit
e60c344a01
@ -8,5 +8,6 @@ sealed class SExpr {
|
||||
}
|
||||
data class Symbol(val contents: String = "", val quoted: Boolean = false) : SExpr() {
|
||||
override fun toString() = SExprToStr.Compact.fromSExpr(this)
|
||||
fun rawContentCharsToBytes() = contents.toCharArray().map(Char::toByte)
|
||||
}
|
||||
}
|
@ -31,33 +31,16 @@ open class AstToAsm {
|
||||
MethodHandle::class.ref.asmDesc, null, null)
|
||||
})
|
||||
// Now all import globals as getter (and maybe setter) method handles
|
||||
ctx.cls.fields.addAll(ctx.importGlobals.withIndex().flatMap { (index, import) ->
|
||||
val ret = listOf(FieldNode(Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL, ctx.importGlobalGetterFieldName(index),
|
||||
MethodHandle::class.ref.asmDesc, null, null))
|
||||
if (!(import.kind as Node.Import.Kind.Global).type.mutable) ret else {
|
||||
ret + FieldNode(Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL, ctx.importGlobalSetterFieldName(index),
|
||||
ctx.cls.fields.addAll(ctx.importGlobals.mapIndexed { index, import ->
|
||||
if ((import.kind as Node.Import.Kind.Global).type.mutable) throw CompileErr.MutableGlobalImport(index)
|
||||
FieldNode(Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL, ctx.importGlobalGetterFieldName(index),
|
||||
MethodHandle::class.ref.asmDesc, null, null)
|
||||
}
|
||||
})
|
||||
// Now all non-import globals
|
||||
ctx.cls.fields.addAll(ctx.mod.globals.withIndex().map { (index, global) ->
|
||||
// In the MVP, we can trust the init is constant stuff and a single instr
|
||||
require(global.init.size <= 1) { "Global init has more than 1 insn" }
|
||||
val init: Number = global.init.firstOrNull().let {
|
||||
when (it) {
|
||||
is Node.Instr.Args.Const<*> -> it.value
|
||||
null -> when (global.type.contentType) {
|
||||
is Node.Type.Value.I32 -> 0
|
||||
is Node.Type.Value.I64 -> 0L
|
||||
is Node.Type.Value.F32 -> 0F
|
||||
is Node.Type.Value.F64 -> 0.0
|
||||
}
|
||||
else -> throw RuntimeException("Unsupported init insn: $it")
|
||||
}
|
||||
}
|
||||
ctx.cls.fields.addAll(ctx.mod.globals.mapIndexed { index, global ->
|
||||
val access = Opcodes.ACC_PRIVATE + if (!global.type.mutable) Opcodes.ACC_FINAL else 0
|
||||
FieldNode(access, ctx.globalName(ctx.importGlobals.size + index),
|
||||
global.type.contentType.typeRef.asmDesc, null, init)
|
||||
global.type.contentType.typeRef.asmDesc, null, null)
|
||||
})
|
||||
}
|
||||
|
||||
@ -69,14 +52,13 @@ open class AstToAsm {
|
||||
// <init>(imports...)
|
||||
// TODO: what happens w/ more than 254 imports?
|
||||
|
||||
val importTypes = ctx.mod.imports.map {
|
||||
when (it.kind) {
|
||||
is Node.Import.Kind.Func -> MethodHandle::class.ref
|
||||
else -> TODO()
|
||||
}
|
||||
}
|
||||
val importTypes =
|
||||
ctx.importFuncs.map { MethodHandle::class.ref } +
|
||||
// We know it's only getters
|
||||
ctx.importGlobals.map { MethodHandle::class.ref }
|
||||
|
||||
// <init>(MemClass maxMemory, imports...)
|
||||
// This is the one that actually does everything and is always deferred to
|
||||
// Set the mem field then call init then put it back on the stack
|
||||
var memCon = Func("<init>", listOf(ctx.mem.memType) + importTypes).addInsns(
|
||||
VarInsnNode(Opcodes.ALOAD, 0),
|
||||
@ -116,7 +98,78 @@ open class AstToAsm {
|
||||
FieldInsnNode(Opcodes.PUTFIELD, ctx.thisRef.asmName,
|
||||
ctx.funcName(importIndex), MethodHandle::class.ref.asmDesc)
|
||||
)
|
||||
}.addInsns(
|
||||
}
|
||||
|
||||
// Set all import globals
|
||||
memCon = ctx.importGlobals.indices.fold(memCon) { memCon, importIndex ->
|
||||
memCon.addInsns(
|
||||
VarInsnNode(Opcodes.ALOAD, 0),
|
||||
VarInsnNode(Opcodes.ALOAD, ctx.importFuncs.size + importIndex + 2),
|
||||
FieldInsnNode(Opcodes.PUTFIELD, ctx.thisRef.asmName,
|
||||
ctx.globalName(importIndex), MethodHandle::class.ref.asmDesc)
|
||||
)
|
||||
}
|
||||
|
||||
// Initialize all globals
|
||||
memCon = ctx.mod.globals.foldIndexed(memCon) { index, memCon, global ->
|
||||
// In the MVP, we can trust the init is constant stuff and a single instr
|
||||
if (global.init.size > 1) throw CompileErr.GlobalInitNonConstant(index)
|
||||
memCon.addInsns(VarInsnNode(Opcodes.ALOAD, 0)).
|
||||
addInsns(
|
||||
global.init.firstOrNull().let {
|
||||
when (it) {
|
||||
is Node.Instr.Args.Const<*> -> {
|
||||
if (it.value::class.javaPrimitiveType?.ref != global.type.contentType.typeRef)
|
||||
throw CompileErr.GlobalConstantMismatch(
|
||||
index,
|
||||
global.type.contentType.typeRef,
|
||||
it.value::class.ref
|
||||
)
|
||||
listOf(LdcInsnNode(it.value))
|
||||
}
|
||||
// Initialize from an import global means we'll just grab the MH as from the param and call
|
||||
is Node.Instr.GetGlobal -> {
|
||||
val refGlobal = ctx.globalAtIndex(it.index)
|
||||
when (refGlobal) {
|
||||
is Either.Right -> throw CompileErr.UnknownGlobal(it.index)
|
||||
is Either.Left -> (refGlobal.v.kind as Node.Import.Kind.Global).let { refGlobalKind ->
|
||||
if (refGlobalKind.type.contentType != global.type.contentType)
|
||||
throw CompileErr.GlobalConstantMismatch(
|
||||
index,
|
||||
global.type.contentType.typeRef,
|
||||
refGlobalKind.type.contentType.typeRef
|
||||
)
|
||||
listOf(
|
||||
VarInsnNode(Opcodes.ALOAD, ctx.importFuncs.size + it.index + 2),
|
||||
MethodInsnNode(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
MethodHandle::class.ref.asmName,
|
||||
"invokeExact",
|
||||
"()" + refGlobalKind.type.contentType.typeRef.asmDesc,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise, global constant
|
||||
null -> listOf(when (global.type.contentType) {
|
||||
is Node.Type.Value.I32 -> 0.const
|
||||
is Node.Type.Value.I64 -> 0L.const
|
||||
is Node.Type.Value.F32 -> 0F.const
|
||||
is Node.Type.Value.F64 -> 0.0.const
|
||||
})
|
||||
else -> throw CompileErr.GlobalInitNonConstant(index)
|
||||
}
|
||||
}
|
||||
).addInsns(
|
||||
FieldInsnNode(Opcodes.PUTFIELD, ctx.thisRef.asmName,
|
||||
ctx.globalName(ctx.importGlobals.size + index), global.type.contentType.typeRef.asmDesc)
|
||||
)
|
||||
}
|
||||
|
||||
// Call object init
|
||||
memCon = memCon.addInsns(
|
||||
// Gotta call super()
|
||||
VarInsnNode(Opcodes.ALOAD, 0),
|
||||
MethodInsnNode(Opcodes.INVOKESPECIAL, Object::class.ref.asmName, "<init>", "()V", false),
|
||||
@ -165,6 +218,7 @@ open class AstToAsm {
|
||||
ctx.mod.exports.forEach {
|
||||
when (it.kind) {
|
||||
Node.ExternalKind.FUNCTION -> addExportFunc(ctx, it)
|
||||
Node.ExternalKind.GLOBAL -> addExportGlobal(ctx, it)
|
||||
else -> TODO()
|
||||
}
|
||||
}
|
||||
@ -215,6 +269,21 @@ open class AstToAsm {
|
||||
ctx.cls.methods.plusAssign(method)
|
||||
}
|
||||
|
||||
fun addExportGlobal(ctx: ClsContext, export: Node.Export) {
|
||||
val global = ctx.globalAtIndex(export.index)
|
||||
when (global) {
|
||||
is Either.Left -> {
|
||||
if ((global.v.kind as Node.Import.Kind.Global).type.mutable)
|
||||
throw CompileErr.MutableGlobalExport(export.index)
|
||||
}
|
||||
is Either.Right -> {
|
||||
if (global.v.type.mutable)
|
||||
throw CompileErr.MutableGlobalExport(export.index)
|
||||
}
|
||||
}
|
||||
TODO()
|
||||
}
|
||||
|
||||
fun addFuncs(ctx: ClsContext) {
|
||||
ctx.cls.methods.addAll(ctx.mod.funcs.mapIndexed { index, func ->
|
||||
ctx.funcBuilder.fromFunc(ctx, func, ctx.importFuncs.size + index).toMethodNode()
|
||||
|
@ -47,7 +47,7 @@ data class ClsContext(
|
||||
fun globalAtIndex(index: Int) = importGlobals.getOrNull(index).let {
|
||||
when (it) {
|
||||
null ->
|
||||
Either.Right(mod.globals.getOrNull(importGlobals.size - index) ?:
|
||||
Either.Right(mod.globals.getOrNull(index - importGlobals.size) ?:
|
||||
throw CompileErr.UnknownGlobal(index))
|
||||
else ->
|
||||
Either.Left(it)
|
||||
|
@ -40,6 +40,14 @@ sealed class CompileErr(message: String, cause: Throwable? = null) : RuntimeExce
|
||||
override val asmErrString get() = "type mismatch"
|
||||
}
|
||||
|
||||
class GlobalConstantMismatch(
|
||||
val index: Int,
|
||||
val expected: TypeRef,
|
||||
val actual: TypeRef
|
||||
) : CompileErr("Global $index expected const of type $expected, got $actual") {
|
||||
override val asmErrString get() = "type mismatch"
|
||||
}
|
||||
|
||||
class IfThenValueWithoutElse() : CompileErr("If has value but no else clause") {
|
||||
override val asmErrString get() = "type mismatch"
|
||||
}
|
||||
@ -73,4 +81,28 @@ sealed class CompileErr(message: String, cause: Throwable? = null) : RuntimeExce
|
||||
) : CompileErr("Unknown global at index $index") {
|
||||
override val asmErrString get() = "unknown global"
|
||||
}
|
||||
|
||||
class SetImmutableGlobal(
|
||||
val index: Int
|
||||
) : CompileErr("Attempting to set global $index which is immutable") {
|
||||
override val asmErrString get() = "global is immutable"
|
||||
}
|
||||
|
||||
class MutableGlobalImport(
|
||||
val index: Int
|
||||
) : CompileErr("Attempted to import mutable global at index $index") {
|
||||
override val asmErrString get() = "mutable globals cannot be imported"
|
||||
}
|
||||
|
||||
class MutableGlobalExport(
|
||||
val index: Int
|
||||
) : CompileErr("Attempted to export global $index which is mutable") {
|
||||
override val asmErrString get() = "mutable globals cannot be exported"
|
||||
}
|
||||
|
||||
class GlobalInitNonConstant(
|
||||
val index: Int
|
||||
) : CompileErr("Expected init for global $index to be constant") {
|
||||
override val asmErrString get() = "constant expression required"
|
||||
}
|
||||
}
|
@ -1054,25 +1054,29 @@ open class FuncBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
fun applySelfSetGlobal(ctx: FuncContext, fn: Func, index: Int, global: Node.Global) =
|
||||
fun applySelfSetGlobal(ctx: FuncContext, fn: Func, index: Int, global: Node.Global): Func {
|
||||
if (!global.type.mutable) throw CompileErr.SetImmutableGlobal(index)
|
||||
// Just call putfield
|
||||
// Note, this is special and "this" has already been injected on the stack for us
|
||||
fn.popExpecting(global.type.contentType.typeRef).
|
||||
popExpecting(MethodHandle::class.ref).
|
||||
return fn.popExpecting(global.type.contentType.typeRef).
|
||||
popExpecting(ctx.cls.thisRef).
|
||||
addInsns(
|
||||
FieldInsnNode(Opcodes.PUTFIELD, ctx.cls.thisRef.asmName, ctx.cls.globalName(index),
|
||||
global.type.contentType.typeRef.asmDesc)
|
||||
)
|
||||
}
|
||||
|
||||
fun applyImportSetGlobal(ctx: FuncContext, fn: Func, index: Int, import: Node.Import.Kind.Global) =
|
||||
fun applyImportSetGlobal(ctx: FuncContext, fn: Func, index: Int, import: Node.Import.Kind.Global): Func {
|
||||
if (!import.type.mutable) throw CompileErr.SetImmutableGlobal(index)
|
||||
// Load the setter method handle field, then invoke it with stack val
|
||||
// Note, this is special and the method handle has already been injected on the stack for us
|
||||
fn.popExpecting(import.type.contentType.typeRef).
|
||||
return fn.popExpecting(import.type.contentType.typeRef).
|
||||
popExpecting(MethodHandle::class.ref).
|
||||
addInsns(
|
||||
MethodInsnNode(Opcodes.INVOKEVIRTUAL, MethodHandle::class.ref.asmName, "invokeExact",
|
||||
"(${import.type.contentType.typeRef.asmDesc})V", false)
|
||||
)
|
||||
}
|
||||
|
||||
fun applyGetGlobal(ctx: FuncContext, fn: Func, index: Int) = ctx.cls.globalAtIndex(index).let {
|
||||
when (it) {
|
||||
|
@ -75,7 +75,7 @@ open class BinaryToAst(
|
||||
|
||||
fun toGlobalType(b: ByteReader) = Node.Type.Global(
|
||||
contentType = toValueType(b),
|
||||
mutable = b.readVarUInt1()
|
||||
mutable = try { b.readVarUInt1() } catch (_: Exception) { throw IoErr.InvalidMutability() }
|
||||
)
|
||||
|
||||
fun toImport(b: ByteReader) = Node.Import(
|
||||
|
@ -5,7 +5,6 @@ import asmble.util.toUnsignedBigInt
|
||||
import asmble.util.toUnsignedLong
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.EOFException
|
||||
import java.math.BigInteger
|
||||
|
||||
abstract class ByteReader {
|
||||
@ -58,7 +57,7 @@ abstract class ByteReader {
|
||||
protected fun readUnsignedLeb128(): Int {
|
||||
// Taken from Android source, Apache licensed
|
||||
var result = 0
|
||||
var cur = 0
|
||||
var cur: Int
|
||||
var count = 0
|
||||
do {
|
||||
cur = readByte().toInt() and 0xff
|
||||
@ -72,7 +71,7 @@ abstract class ByteReader {
|
||||
private fun readSignedLeb128(): Long {
|
||||
// Taken from Android source, Apache licensed
|
||||
var result = 0L
|
||||
var cur = 0
|
||||
var cur: Int
|
||||
var count = 0
|
||||
var signBits = -1L
|
||||
do {
|
||||
|
@ -22,4 +22,8 @@ sealed class IoErr(message: String, cause: Throwable? = null) : RuntimeException
|
||||
class InvalidCodeLength(funcLen: Int, codeLen: Int) : IoErr("Got $funcLen funcs but only $codeLen bodies") {
|
||||
override val asmErrString get() = "function and code section have inconsistent lengths"
|
||||
}
|
||||
|
||||
class InvalidMutability : IoErr("Invalid mutability boolean") {
|
||||
override val asmErrString = "invalid mutability"
|
||||
}
|
||||
}
|
@ -422,15 +422,16 @@ open class SExprToAst {
|
||||
exp.vals.drop(if (name == null) 1 else 2).also { otherVals ->
|
||||
if (otherVals.isNotEmpty() && otherVals.find { it !is SExpr.Symbol || !it.quoted } == null)
|
||||
return name to toModuleFromBytes(otherVals.fold(byteArrayOf()) { bytes, strVal ->
|
||||
bytes + (strVal as SExpr.Symbol).contents.toByteArray()
|
||||
bytes + (strVal as SExpr.Symbol).rawContentCharsToBytes()
|
||||
})
|
||||
}
|
||||
|
||||
var mod = Node.Module()
|
||||
// Go over each module element and apply to the module
|
||||
// But we have to do all imports first before anything else, so we separate them
|
||||
var foundNonImport = false
|
||||
val (importExps, nonImportExps) = exp.vals.mapNotNull { it as? SExpr.Multi }.partition {
|
||||
when(it.vals.firstOrNull()?.symbolStr()) {
|
||||
val isImport = when(it.vals.firstOrNull()?.symbolStr()) {
|
||||
"import" -> true
|
||||
"func", "global", "table", "memory" -> {
|
||||
val possibleImportIndex = if (it.maybeName(1) == null) 1 else 2
|
||||
@ -438,6 +439,9 @@ open class SExprToAst {
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
if (!isImport && !foundNonImport) foundNonImport = true
|
||||
if (isImport && foundNonImport) error("Import after non-import")
|
||||
isImport
|
||||
}
|
||||
|
||||
// Eagerly build the names (for forward decls)
|
||||
|
@ -4,6 +4,10 @@ import java.io.PrintWriter
|
||||
|
||||
open class Harness(val out: PrintWriter) {
|
||||
|
||||
// We're not as evil as WASM:
|
||||
// https://github.com/WebAssembly/spec/blob/7c62b17f547b80c9c717cc6ef3a8aba1e04e4bcb/test/harness/index.js#L84
|
||||
val global get() = 555
|
||||
|
||||
fun print(arg0: Int) { out.println("$arg0 : i32") }
|
||||
|
||||
companion object : Harness(PrintWriter(System.out, true))
|
||||
|
@ -225,6 +225,16 @@ data class ScriptContext(
|
||||
)
|
||||
}
|
||||
|
||||
fun resolveImportGlobal(import: Node.Import, kind: Node.Type.Global): MethodHandle {
|
||||
// Find a getter that matches the name
|
||||
val module = registrations[import.module] ?: error("Unable to find module ${import.module}")
|
||||
return MethodHandles.lookup().bind(
|
||||
module.instance,
|
||||
"get" + import.field.javaIdent.capitalize(),
|
||||
MethodType.methodType(kind.contentType.jclass)
|
||||
)
|
||||
}
|
||||
|
||||
interface Module {
|
||||
val cls: Class<*>
|
||||
val instance: Any
|
||||
@ -251,14 +261,19 @@ data class ScriptContext(
|
||||
it.parameterTypes.firstOrNull() == Int::class.java
|
||||
} ?: error("Unable to find no-arg or mem-accepting construtor")
|
||||
}
|
||||
|
||||
// Now resolve the imports
|
||||
constructorParams += mod.imports.map {
|
||||
val kind = it.kind
|
||||
when (kind) {
|
||||
is Node.Import.Kind.Func -> resolveImportFunc(it, mod.types[kind.typeIndex])
|
||||
else -> TODO()
|
||||
// First, the function imports
|
||||
constructorParams += mod.imports.mapNotNull {
|
||||
if (it.kind is Node.Import.Kind.Func) resolveImportFunc(it, mod.types[it.kind.typeIndex])
|
||||
else null
|
||||
}
|
||||
// Then the global imports
|
||||
constructorParams += mod.imports.mapNotNull {
|
||||
if (it.kind is Node.Import.Kind.Global) resolveImportGlobal(it, it.kind.type)
|
||||
else null
|
||||
}
|
||||
|
||||
// Construct
|
||||
constructor!!.newInstance(*constructorParams.toTypedArray())
|
||||
}
|
||||
|
@ -49,7 +49,6 @@ class SpecTestUnit(val name: String, val wast: String, val expectedOutput: Strin
|
||||
- float_memory.wast - Not handling mem data strings yet
|
||||
- func.wast - Not handling tables yet
|
||||
- func_ptrs.wast - Not handling tables yet
|
||||
- globals.wast - No binary yet
|
||||
- imports.wast - No memory exports yet
|
||||
- left-to-right.wast - Not handling tables yet
|
||||
- linking.wast - Not handling tables yet
|
||||
@ -97,6 +96,7 @@ class SpecTestUnit(val name: String, val wast: String, val expectedOutput: Strin
|
||||
"func-result-after-body.fail.wast",
|
||||
"func-result-before-param.fail.wast",
|
||||
"get_local.wast",
|
||||
"globals.wast",
|
||||
"i32.load32_s.fail.wast",
|
||||
"i32.load32_u.fail.wast",
|
||||
"i32.load64_s.fail.wast",
|
||||
|
Loading…
x
Reference in New Issue
Block a user