mirror of
https://github.com/fluencelabs/asmble
synced 2025-04-25 14:52:21 +00:00
Support memory data byte array constants
This commit is contained in:
parent
e60c344a01
commit
c2f04b6474
@ -8,6 +8,7 @@ sealed class SExpr {
|
||||
}
|
||||
data class Symbol(val contents: String = "", val quoted: Boolean = false) : SExpr() {
|
||||
override fun toString() = SExprToStr.Compact.fromSExpr(this)
|
||||
// This is basically the same as the deprecated java.lang.String#getBytes
|
||||
fun rawContentCharsToBytes() = contents.toCharArray().map(Char::toByte)
|
||||
}
|
||||
}
|
@ -161,13 +161,17 @@ fun MethodNode.addInsns(vararg insn: AbstractInsnNode): MethodNode {
|
||||
return this
|
||||
}
|
||||
|
||||
fun MethodNode.toAsmString(): String {
|
||||
val stringWriter = StringWriter()
|
||||
val cv = TraceClassVisitor(PrintWriter(stringWriter))
|
||||
this.accept(cv)
|
||||
cv.p.print(PrintWriter(stringWriter))
|
||||
return stringWriter.toString()
|
||||
}
|
||||
|
||||
val Node.Type.Func.asmDesc: String get() =
|
||||
(this.ret?.typeRef ?: Void::class.ref).asMethodRetDesc(*this.params.map { it.typeRef }.toTypedArray())
|
||||
|
||||
fun ClassNode.addMethodIfNotPresent(m: MethodNode) {
|
||||
this.methods.find { (it as MethodNode).let { it.name == m.name && it.desc == it.desc } } ?: this.methods.add(m)
|
||||
}
|
||||
|
||||
fun ClassNode.withComputedFramesAndMaxs(): ByteArray {
|
||||
// TODO: compute maxs adds a bunch of NOPs for unreachable code
|
||||
// See $func12 of block.wast. Is removing these worth the extra visit cycle?
|
||||
|
@ -59,20 +59,25 @@ data class ClsContext(
|
||||
fun globalName(index: Int) = "\$global$index"
|
||||
fun funcName(index: Int) = "\$func$index"
|
||||
|
||||
private fun syntheticAssertion(fn: SyntheticAssertionBuilder.(ClsContext) -> MethodNode) =
|
||||
fn(syntheticAssertionBuilder, this).let { method ->
|
||||
cls.addMethodIfNotPresent(method)
|
||||
MethodInsnNode(Opcodes.INVOKESTATIC, thisRef.asmName, method.name, method.desc, false)
|
||||
}
|
||||
private fun syntheticAssertion(
|
||||
nameSuffix: String,
|
||||
fn: SyntheticAssertionBuilder.(ClsContext, String) -> MethodNode
|
||||
): MethodInsnNode {
|
||||
val name = "\$\$$nameSuffix"
|
||||
val method =
|
||||
cls.methods.find { (it as MethodNode).name == name }?.let { it as MethodNode } ?:
|
||||
fn(syntheticAssertionBuilder, this, name).also { cls.methods.add(it) }
|
||||
return MethodInsnNode(Opcodes.INVOKESTATIC, thisRef.asmName, method.name, method.desc, false)
|
||||
}
|
||||
|
||||
val truncAssertF2SI by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildF2SIAssertion) }
|
||||
val truncAssertF2UI by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildF2UIAssertion) }
|
||||
val truncAssertF2SL by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildF2SLAssertion) }
|
||||
val truncAssertF2UL by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildF2ULAssertion) }
|
||||
val truncAssertD2SI by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildD2SIAssertion) }
|
||||
val truncAssertD2UI by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildD2UIAssertion) }
|
||||
val truncAssertD2SL by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildD2SLAssertion) }
|
||||
val truncAssertD2UL by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildD2ULAssertion) }
|
||||
val divAssertI by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildIDivAssertion) }
|
||||
val divAssertL by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildLDivAssertion) }
|
||||
val truncAssertF2SI get() = syntheticAssertion("assertF2SI", SyntheticAssertionBuilder::buildF2SIAssertion)
|
||||
val truncAssertF2UI get() = syntheticAssertion("assertF2UI", SyntheticAssertionBuilder::buildF2UIAssertion)
|
||||
val truncAssertF2SL get() = syntheticAssertion("assertF2SL", SyntheticAssertionBuilder::buildF2SLAssertion)
|
||||
val truncAssertF2UL get() = syntheticAssertion("assertF2UL", SyntheticAssertionBuilder::buildF2ULAssertion)
|
||||
val truncAssertD2SI get() = syntheticAssertion("assertD2SI", SyntheticAssertionBuilder::buildD2SIAssertion)
|
||||
val truncAssertD2UI get() = syntheticAssertion("assertD2UI", SyntheticAssertionBuilder::buildD2UIAssertion)
|
||||
val truncAssertD2SL get() = syntheticAssertion("assertD2SL", SyntheticAssertionBuilder::buildD2SLAssertion)
|
||||
val truncAssertD2UL get() = syntheticAssertion("assertD2UL", SyntheticAssertionBuilder::buildD2ULAssertion)
|
||||
val divAssertI get() = syntheticAssertion("assertIDiv", SyntheticAssertionBuilder::buildIDivAssertion)
|
||||
val divAssertL get() = syntheticAssertion("assertLDiv", SyntheticAssertionBuilder::buildLDivAssertion)
|
||||
}
|
@ -77,7 +77,7 @@ data class Func(
|
||||
|
||||
fun toMethodNode(): MethodNode {
|
||||
if (stack.isNotEmpty()) throw CompileErr.UnusedStackOnReturn(stack)
|
||||
require(insns.lastOrNull()?.isTerminating ?: false, { "Last insn for $name$desc is not terminating" })
|
||||
require(insns.lastOrNull()?.isTerminating ?: false) { "Last insn for $name$desc is not terminating" }
|
||||
val ret = MethodNode(access, name, desc, null, null)
|
||||
insns.forEach(ret.instructions::add)
|
||||
return ret
|
||||
|
@ -5,12 +5,12 @@ import org.objectweb.asm.tree.*
|
||||
|
||||
open class SyntheticAssertionBuilder {
|
||||
|
||||
fun buildIDivAssertion(ctx: ClsContext) =
|
||||
fun buildIDivAssertion(ctx: ClsContext, name: String) =
|
||||
LabelNode().let { safeLabel ->
|
||||
LabelNode().let { overflowLabel ->
|
||||
MethodNode(
|
||||
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
|
||||
"\$\$assertIDiv", "(II)V", null, null
|
||||
name, "(II)V", null, null
|
||||
).addInsns(
|
||||
VarInsnNode(Opcodes.ILOAD, 0),
|
||||
Int.MIN_VALUE.const,
|
||||
@ -25,12 +25,12 @@ open class SyntheticAssertionBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
fun buildLDivAssertion(ctx: ClsContext) =
|
||||
fun buildLDivAssertion(ctx: ClsContext, name: String) =
|
||||
LabelNode().let { safeLabel ->
|
||||
LabelNode().let { overflowLabel ->
|
||||
MethodNode(
|
||||
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
|
||||
"\$\$assertLDiv", "(JJ)V", null, null
|
||||
name, "(JJ)V", null, null
|
||||
).addInsns(
|
||||
VarInsnNode(Opcodes.LLOAD, 0),
|
||||
Long.MIN_VALUE.const,
|
||||
@ -49,54 +49,54 @@ open class SyntheticAssertionBuilder {
|
||||
|
||||
// TODO: add tests for +- 4 near overflow for each combo compared with spec
|
||||
|
||||
fun buildF2SIAssertion(ctx: ClsContext) =
|
||||
fun buildF2SIAssertion(ctx: ClsContext, name: String) =
|
||||
MethodNode(
|
||||
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
|
||||
"\$\$assertF2SI", "(F)V", null, null
|
||||
name, "(F)V", null, null
|
||||
).floatNanCheck().floatRangeCheck(2147483648f, -2147483648f).addInsns(InsnNode(Opcodes.RETURN))
|
||||
|
||||
fun buildF2UIAssertion(ctx: ClsContext) =
|
||||
fun buildF2UIAssertion(ctx: ClsContext, name: String) =
|
||||
MethodNode(
|
||||
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
|
||||
"\$\$assertF2UI", "(F)V", null, null
|
||||
name, "(F)V", null, null
|
||||
).floatNanCheck().floatUnsignedRangeCheck(4294967296f).addInsns(InsnNode(Opcodes.RETURN))
|
||||
|
||||
fun buildF2SLAssertion(ctx: ClsContext) =
|
||||
fun buildF2SLAssertion(ctx: ClsContext, name: String) =
|
||||
MethodNode(
|
||||
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
|
||||
"\$\$assertF2SL", "(F)V", null, null
|
||||
name, "(F)V", null, null
|
||||
).floatNanCheck().floatRangeCheck(9223372036854775807f, -9223372036854775807f).
|
||||
addInsns(InsnNode(Opcodes.RETURN))
|
||||
|
||||
fun buildF2ULAssertion(ctx: ClsContext) =
|
||||
fun buildF2ULAssertion(ctx: ClsContext, name: String) =
|
||||
MethodNode(
|
||||
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
|
||||
"\$\$assertF2UL", "(F)V", null, null
|
||||
name, "(F)V", null, null
|
||||
).floatNanCheck().floatUnsignedRangeCheck(18446744073709551616f).addInsns(InsnNode(Opcodes.RETURN))
|
||||
|
||||
fun buildD2SIAssertion(ctx: ClsContext) =
|
||||
fun buildD2SIAssertion(ctx: ClsContext, name: String) =
|
||||
MethodNode(
|
||||
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
|
||||
"\$\$assertD2SI", "(D)V", null, null
|
||||
name, "(D)V", null, null
|
||||
).doubleNanCheck().doubleRangeCheck(2147483648.0, -2147483648.0).addInsns(InsnNode(Opcodes.RETURN))
|
||||
|
||||
fun buildD2UIAssertion(ctx: ClsContext) =
|
||||
fun buildD2UIAssertion(ctx: ClsContext, name: String) =
|
||||
MethodNode(
|
||||
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
|
||||
"\$\$assertD2UI", "(D)V", null, null
|
||||
name, "(D)V", null, null
|
||||
).doubleNanCheck().doubleUnsignedRangeCheck(4294967296.0).addInsns(InsnNode(Opcodes.RETURN))
|
||||
|
||||
fun buildD2SLAssertion(ctx: ClsContext) =
|
||||
fun buildD2SLAssertion(ctx: ClsContext, name: String) =
|
||||
MethodNode(
|
||||
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
|
||||
"\$\$assertD2SL", "(D)V", null, null
|
||||
name, "(D)V", null, null
|
||||
).doubleNanCheck().doubleRangeCheck(9223372036854775807.0, -9223372036854775807.0).
|
||||
addInsns(InsnNode(Opcodes.RETURN))
|
||||
|
||||
fun buildD2ULAssertion(ctx: ClsContext) =
|
||||
fun buildD2ULAssertion(ctx: ClsContext, name: String) =
|
||||
MethodNode(
|
||||
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
|
||||
"\$\$assertD2UL", "(D)V", null, null
|
||||
name, "(D)V", null, null
|
||||
).doubleNanCheck().doubleUnsignedRangeCheck(18446744073709551616.0).addInsns(InsnNode(Opcodes.RETURN))
|
||||
|
||||
fun MethodNode.floatNanCheck() = LabelNode().let { okLabel ->
|
||||
|
@ -7,7 +7,6 @@ import asmble.ast.Script
|
||||
import asmble.util.*
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.math.BigInteger
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
typealias NameMap = Map<String, Int>
|
||||
|
||||
@ -101,8 +100,10 @@ open class SExprToAst {
|
||||
toInstrs(offsetMulti, 1, ExprContext(nameMap)).first
|
||||
} else toExprMaybe(offsetMulti, ExprContext(nameMap))
|
||||
currIndex++
|
||||
val strs = exp.vals.drop(currIndex).fold("") { str, sym -> str + (sym as SExpr.Symbol).contents }
|
||||
return Node.Data(index ?: 0, instrs, strs.toByteArray(Charsets.UTF_8))
|
||||
val bytes = exp.vals.drop(currIndex).fold(byteArrayOf()) { bytes, sym ->
|
||||
bytes + (sym as SExpr.Symbol).rawContentCharsToBytes()
|
||||
}
|
||||
return Node.Data(index ?: 0, instrs, bytes)
|
||||
}
|
||||
|
||||
fun toElem(exp: SExpr.Multi, nameMap: NameMap): Node.Elem {
|
||||
@ -386,16 +387,28 @@ open class SExprToAst {
|
||||
return Pair(null, exp.vals.drop(1).map { toType(it.symbol()!!) })
|
||||
}
|
||||
|
||||
fun toMemory(exp: SExpr.Multi): Triple<String?, Node.Type.Memory, ImportOrExport?> {
|
||||
fun toMemory(exp: SExpr.Multi): Triple<String?, Either<Node.Type.Memory, Node.Data>, ImportOrExport?> {
|
||||
exp.requireFirstSymbol("memory")
|
||||
var currIndex = 1
|
||||
val name = exp.maybeName(currIndex)
|
||||
if (name != null) currIndex++
|
||||
val maybeImpExp = toImportOrExportMaybe(exp, 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)
|
||||
// If it's a multi we assume "data", otherwise assume sig
|
||||
val memOrData = exp.vals[currIndex].let {
|
||||
when (it) {
|
||||
is SExpr.Multi -> {
|
||||
it.requireFirstSymbol("data")
|
||||
Either.Right(Node.Data(
|
||||
0,
|
||||
listOf(Node.Instr.I32Const(0)),
|
||||
it.vals.drop(1).fold(byteArrayOf()) { b, exp -> b + exp.symbol()!!.rawContentCharsToBytes() }
|
||||
))
|
||||
}
|
||||
else -> Either.Left(toMemorySig(exp, currIndex))
|
||||
}
|
||||
}
|
||||
return Triple(name, memOrData, maybeImpExp)
|
||||
}
|
||||
|
||||
fun toMemorySig(exp: SExpr.Multi, offset: Int): Node.Type.Memory {
|
||||
@ -465,7 +478,8 @@ open class SExprToAst {
|
||||
Triple(impExp!!.importModule!!, impExp.field, tbl)
|
||||
}
|
||||
"memory" -> toMemory(it).let { (_, mem, impExp) ->
|
||||
Triple(impExp!!.importModule!!, impExp.field, mem)
|
||||
if (mem !is Either.Left) error("Data segment on import mem")
|
||||
Triple(impExp!!.importModule!!, impExp.field, mem.v)
|
||||
}
|
||||
else -> throw Exception("Unknown import exp: $it")
|
||||
}
|
||||
@ -503,7 +517,15 @@ open class SExprToAst {
|
||||
}
|
||||
"memory" -> toMemory(it).also { (_, mem, impExp) ->
|
||||
addMaybeExport(impExp, Node.ExternalKind.MEMORY, memoryCount++)
|
||||
mod = mod.copy(memories = mod.memories + mem)
|
||||
when (mem) {
|
||||
is Either.Left -> mod = mod.copy(memories = mod.memories + mem.v)
|
||||
is Either.Right -> mod = mod.copy(
|
||||
memories = mod.memories + Node.Type.Memory(
|
||||
Node.ResizableLimits(mem.v.data.size, mem.v.data.size)
|
||||
),
|
||||
data = mod.data + mem.v
|
||||
)
|
||||
}
|
||||
}
|
||||
"elem" -> mod = mod.copy(elems = mod.elems + toElem(it, nameMap))
|
||||
"data" -> mod = mod.copy(data = mod.data + toData(it, nameMap))
|
||||
@ -511,6 +533,7 @@ open class SExprToAst {
|
||||
else -> throw Exception("Unknown non-import exp $exp")
|
||||
}
|
||||
}
|
||||
require(mod.memories.size <= 1) { "Only single memory allowed" }
|
||||
|
||||
return name to mod
|
||||
}
|
||||
|
@ -45,8 +45,6 @@ class SpecTestUnit(val name: String, val wast: String, val expectedOutput: Strin
|
||||
- br_table.wast - Not handling tables yet
|
||||
- call_indirect.wast - Not handling tables yet
|
||||
- exports.wast - Not handling tables yet
|
||||
- float_exprs.wast - Not handling mem data strings yet
|
||||
- float_memory.wast - Not handling mem data strings yet
|
||||
- func.wast - Not handling tables yet
|
||||
- func_ptrs.wast - Not handling tables yet
|
||||
- imports.wast - No memory exports yet
|
||||
@ -86,7 +84,9 @@ class SpecTestUnit(val name: String, val wast: String, val expectedOutput: Strin
|
||||
"f64.wast",
|
||||
"f64_cmp.wast",
|
||||
"fac.wast",
|
||||
"float_exprs.wast",
|
||||
"float_literals.wast",
|
||||
"float_memory.wast",
|
||||
"float_misc.wast",
|
||||
"forward.wast",
|
||||
"func-local-after-body.fail.wast",
|
||||
|
Loading…
x
Reference in New Issue
Block a user