Support memory data byte array constants

This commit is contained in:
Chad Retz 2017-04-12 00:36:14 -05:00
parent e60c344a01
commit c2f04b6474
7 changed files with 84 additions and 51 deletions

View File

@ -8,6 +8,7 @@ sealed class SExpr {
} }
data class Symbol(val contents: String = "", val quoted: Boolean = false) : SExpr() { data class Symbol(val contents: String = "", val quoted: Boolean = false) : SExpr() {
override fun toString() = SExprToStr.Compact.fromSExpr(this) 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) fun rawContentCharsToBytes() = contents.toCharArray().map(Char::toByte)
} }
} }

View File

@ -161,13 +161,17 @@ fun MethodNode.addInsns(vararg insn: AbstractInsnNode): MethodNode {
return this 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() = val Node.Type.Func.asmDesc: String get() =
(this.ret?.typeRef ?: Void::class.ref).asMethodRetDesc(*this.params.map { it.typeRef }.toTypedArray()) (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 { fun ClassNode.withComputedFramesAndMaxs(): ByteArray {
// TODO: compute maxs adds a bunch of NOPs for unreachable code // TODO: compute maxs adds a bunch of NOPs for unreachable code
// See $func12 of block.wast. Is removing these worth the extra visit cycle? // See $func12 of block.wast. Is removing these worth the extra visit cycle?

View File

@ -59,20 +59,25 @@ data class ClsContext(
fun globalName(index: Int) = "\$global$index" fun globalName(index: Int) = "\$global$index"
fun funcName(index: Int) = "\$func$index" fun funcName(index: Int) = "\$func$index"
private fun syntheticAssertion(fn: SyntheticAssertionBuilder.(ClsContext) -> MethodNode) = private fun syntheticAssertion(
fn(syntheticAssertionBuilder, this).let { method -> nameSuffix: String,
cls.addMethodIfNotPresent(method) fn: SyntheticAssertionBuilder.(ClsContext, String) -> MethodNode
MethodInsnNode(Opcodes.INVOKESTATIC, thisRef.asmName, method.name, method.desc, false) ): 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 truncAssertF2SI get() = syntheticAssertion("assertF2SI", SyntheticAssertionBuilder::buildF2SIAssertion)
val truncAssertF2UI by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildF2UIAssertion) } val truncAssertF2UI get() = syntheticAssertion("assertF2UI", SyntheticAssertionBuilder::buildF2UIAssertion)
val truncAssertF2SL by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildF2SLAssertion) } val truncAssertF2SL get() = syntheticAssertion("assertF2SL", SyntheticAssertionBuilder::buildF2SLAssertion)
val truncAssertF2UL by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildF2ULAssertion) } val truncAssertF2UL get() = syntheticAssertion("assertF2UL", SyntheticAssertionBuilder::buildF2ULAssertion)
val truncAssertD2SI by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildD2SIAssertion) } val truncAssertD2SI get() = syntheticAssertion("assertD2SI", SyntheticAssertionBuilder::buildD2SIAssertion)
val truncAssertD2UI by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildD2UIAssertion) } val truncAssertD2UI get() = syntheticAssertion("assertD2UI", SyntheticAssertionBuilder::buildD2UIAssertion)
val truncAssertD2SL by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildD2SLAssertion) } val truncAssertD2SL get() = syntheticAssertion("assertD2SL", SyntheticAssertionBuilder::buildD2SLAssertion)
val truncAssertD2UL by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildD2ULAssertion) } val truncAssertD2UL get() = syntheticAssertion("assertD2UL", SyntheticAssertionBuilder::buildD2ULAssertion)
val divAssertI by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildIDivAssertion) } val divAssertI get() = syntheticAssertion("assertIDiv", SyntheticAssertionBuilder::buildIDivAssertion)
val divAssertL by lazy { syntheticAssertion(SyntheticAssertionBuilder::buildLDivAssertion) } val divAssertL get() = syntheticAssertion("assertLDiv", SyntheticAssertionBuilder::buildLDivAssertion)
} }

View File

@ -77,7 +77,7 @@ data class Func(
fun toMethodNode(): MethodNode { fun toMethodNode(): MethodNode {
if (stack.isNotEmpty()) throw CompileErr.UnusedStackOnReturn(stack) 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) val ret = MethodNode(access, name, desc, null, null)
insns.forEach(ret.instructions::add) insns.forEach(ret.instructions::add)
return ret return ret

View File

@ -5,12 +5,12 @@ import org.objectweb.asm.tree.*
open class SyntheticAssertionBuilder { open class SyntheticAssertionBuilder {
fun buildIDivAssertion(ctx: ClsContext) = fun buildIDivAssertion(ctx: ClsContext, name: String) =
LabelNode().let { safeLabel -> LabelNode().let { safeLabel ->
LabelNode().let { overflowLabel -> LabelNode().let { overflowLabel ->
MethodNode( MethodNode(
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
"\$\$assertIDiv", "(II)V", null, null name, "(II)V", null, null
).addInsns( ).addInsns(
VarInsnNode(Opcodes.ILOAD, 0), VarInsnNode(Opcodes.ILOAD, 0),
Int.MIN_VALUE.const, 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 { safeLabel ->
LabelNode().let { overflowLabel -> LabelNode().let { overflowLabel ->
MethodNode( MethodNode(
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
"\$\$assertLDiv", "(JJ)V", null, null name, "(JJ)V", null, null
).addInsns( ).addInsns(
VarInsnNode(Opcodes.LLOAD, 0), VarInsnNode(Opcodes.LLOAD, 0),
Long.MIN_VALUE.const, Long.MIN_VALUE.const,
@ -49,54 +49,54 @@ open class SyntheticAssertionBuilder {
// TODO: add tests for +- 4 near overflow for each combo compared with spec // TODO: add tests for +- 4 near overflow for each combo compared with spec
fun buildF2SIAssertion(ctx: ClsContext) = fun buildF2SIAssertion(ctx: ClsContext, name: String) =
MethodNode( MethodNode(
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, 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)) ).floatNanCheck().floatRangeCheck(2147483648f, -2147483648f).addInsns(InsnNode(Opcodes.RETURN))
fun buildF2UIAssertion(ctx: ClsContext) = fun buildF2UIAssertion(ctx: ClsContext, name: String) =
MethodNode( MethodNode(
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, 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)) ).floatNanCheck().floatUnsignedRangeCheck(4294967296f).addInsns(InsnNode(Opcodes.RETURN))
fun buildF2SLAssertion(ctx: ClsContext) = fun buildF2SLAssertion(ctx: ClsContext, name: String) =
MethodNode( MethodNode(
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
"\$\$assertF2SL", "(F)V", null, null name, "(F)V", null, null
).floatNanCheck().floatRangeCheck(9223372036854775807f, -9223372036854775807f). ).floatNanCheck().floatRangeCheck(9223372036854775807f, -9223372036854775807f).
addInsns(InsnNode(Opcodes.RETURN)) addInsns(InsnNode(Opcodes.RETURN))
fun buildF2ULAssertion(ctx: ClsContext) = fun buildF2ULAssertion(ctx: ClsContext, name: String) =
MethodNode( MethodNode(
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, 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)) ).floatNanCheck().floatUnsignedRangeCheck(18446744073709551616f).addInsns(InsnNode(Opcodes.RETURN))
fun buildD2SIAssertion(ctx: ClsContext) = fun buildD2SIAssertion(ctx: ClsContext, name: String) =
MethodNode( MethodNode(
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, 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)) ).doubleNanCheck().doubleRangeCheck(2147483648.0, -2147483648.0).addInsns(InsnNode(Opcodes.RETURN))
fun buildD2UIAssertion(ctx: ClsContext) = fun buildD2UIAssertion(ctx: ClsContext, name: String) =
MethodNode( MethodNode(
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, 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)) ).doubleNanCheck().doubleUnsignedRangeCheck(4294967296.0).addInsns(InsnNode(Opcodes.RETURN))
fun buildD2SLAssertion(ctx: ClsContext) = fun buildD2SLAssertion(ctx: ClsContext, name: String) =
MethodNode( MethodNode(
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, 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). ).doubleNanCheck().doubleRangeCheck(9223372036854775807.0, -9223372036854775807.0).
addInsns(InsnNode(Opcodes.RETURN)) addInsns(InsnNode(Opcodes.RETURN))
fun buildD2ULAssertion(ctx: ClsContext) = fun buildD2ULAssertion(ctx: ClsContext, name: String) =
MethodNode( MethodNode(
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, 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)) ).doubleNanCheck().doubleUnsignedRangeCheck(18446744073709551616.0).addInsns(InsnNode(Opcodes.RETURN))
fun MethodNode.floatNanCheck() = LabelNode().let { okLabel -> fun MethodNode.floatNanCheck() = LabelNode().let { okLabel ->

View File

@ -7,7 +7,6 @@ import asmble.ast.Script
import asmble.util.* import asmble.util.*
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.math.BigInteger import java.math.BigInteger
import java.nio.ByteBuffer
typealias NameMap = Map<String, Int> typealias NameMap = Map<String, Int>
@ -101,8 +100,10 @@ open class SExprToAst {
toInstrs(offsetMulti, 1, ExprContext(nameMap)).first toInstrs(offsetMulti, 1, ExprContext(nameMap)).first
} else toExprMaybe(offsetMulti, ExprContext(nameMap)) } else toExprMaybe(offsetMulti, ExprContext(nameMap))
currIndex++ currIndex++
val strs = exp.vals.drop(currIndex).fold("") { str, sym -> str + (sym as SExpr.Symbol).contents } val bytes = exp.vals.drop(currIndex).fold(byteArrayOf()) { bytes, sym ->
return Node.Data(index ?: 0, instrs, strs.toByteArray(Charsets.UTF_8)) bytes + (sym as SExpr.Symbol).rawContentCharsToBytes()
}
return Node.Data(index ?: 0, instrs, bytes)
} }
fun toElem(exp: SExpr.Multi, nameMap: NameMap): Node.Elem { 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()!!) }) 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") exp.requireFirstSymbol("memory")
var currIndex = 1 var currIndex = 1
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 // If it's a multi we assume "data", otherwise assume sig
if (exp.vals[currIndex] is SExpr.Multi) throw Exception("Data string not yet supported for memory") val memOrData = exp.vals[currIndex].let {
return Triple(name, toMemorySig(exp, currIndex), maybeImpExp) 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 { fun toMemorySig(exp: SExpr.Multi, offset: Int): Node.Type.Memory {
@ -465,7 +478,8 @@ open class SExprToAst {
Triple(impExp!!.importModule!!, impExp.field, tbl) Triple(impExp!!.importModule!!, impExp.field, tbl)
} }
"memory" -> toMemory(it).let { (_, mem, impExp) -> "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") else -> throw Exception("Unknown import exp: $it")
} }
@ -503,7 +517,15 @@ open class SExprToAst {
} }
"memory" -> toMemory(it).also { (_, mem, impExp) -> "memory" -> toMemory(it).also { (_, mem, impExp) ->
addMaybeExport(impExp, Node.ExternalKind.MEMORY, memoryCount++) 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)) "elem" -> mod = mod.copy(elems = mod.elems + toElem(it, nameMap))
"data" -> mod = mod.copy(data = mod.data + toData(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") else -> throw Exception("Unknown non-import exp $exp")
} }
} }
require(mod.memories.size <= 1) { "Only single memory allowed" }
return name to mod return name to mod
} }

View File

@ -45,8 +45,6 @@ class SpecTestUnit(val name: String, val wast: String, val expectedOutput: Strin
- br_table.wast - Not handling tables yet - br_table.wast - Not handling tables yet
- call_indirect.wast - Not handling tables yet - call_indirect.wast - Not handling tables yet
- exports.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.wast - Not handling tables yet
- func_ptrs.wast - Not handling tables yet - func_ptrs.wast - Not handling tables yet
- imports.wast - No memory exports 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.wast",
"f64_cmp.wast", "f64_cmp.wast",
"fac.wast", "fac.wast",
"float_exprs.wast",
"float_literals.wast", "float_literals.wast",
"float_memory.wast",
"float_misc.wast", "float_misc.wast",
"forward.wast", "forward.wast",
"func-local-after-body.fail.wast", "func-local-after-body.fail.wast",