Fix grow_memory

This commit is contained in:
Chad Retz 2017-04-02 19:37:17 -05:00
parent 19ed90163b
commit 1b31925a5a
8 changed files with 71 additions and 33 deletions

View File

@ -131,6 +131,11 @@ val AbstractInsnNode.isUnconditionalJump: Boolean get() = when (this.opcode) {
else -> false
}
fun MethodNode.addInsns(vararg insn: AbstractInsnNode): MethodNode {
insn.forEach(this.instructions::add)
return this
}
val Node.Type.Func.asmDesc: String get() =
(this.ret?.typeRef ?: Void::class.ref).asMethodRetDesc(*this.params.map { it.typeRef }.toTypedArray())

View File

@ -64,7 +64,7 @@ open class AstToAsm {
fun addConstructors(ctx: ClsContext) {
// We have at least two constructors:
// <init>(int maxMemory, imports...)
// <init>(MemClass maxMemory, imports...)
// <init>(MemClass mem, imports...)
// If the max memory was supplied in the mem section, we also have
// <init>(imports...)
// TODO: what happens w/ more than 254 imports?
@ -147,7 +147,7 @@ open class AstToAsm {
val regCon = Func("<init>", importTypes).
addInsns(
VarInsnNode(Opcodes.ALOAD, 0),
it.const
(it * Mem.PAGE_SIZE).const
).
addInsns(importTypes.indices.map { VarInsnNode(Opcodes.ALOAD, it + 1) }).
addInsns(

View File

@ -3,10 +3,7 @@ package asmble.compile.jvm
import asmble.ast.Node
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.tree.InsnNode
import org.objectweb.asm.tree.IntInsnNode
import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.TypeInsnNode
import org.objectweb.asm.tree.*
import java.nio.Buffer
import java.nio.ByteBuffer
import java.nio.ByteOrder
@ -56,13 +53,47 @@ open class ByteBufferMem(val direct: Boolean = true) : Mem {
InsnNode(Opcodes.IDIV)
).push(Int::class.ref)
override fun growMemory(ctx: FuncContext, func: Func) =
func.popExpecting(memType).popExpecting(Int::class.ref).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.ref)
override fun growMemory(ctx: FuncContext, func: Func) = getOrCreateGrowMemoryMethod(ctx, func).let { method ->
func.popExpecting(Int::class.ref).popExpecting(memType).addInsns(
// This is complicated enough to need a synthetic method
MethodInsnNode(Opcodes.INVOKESTATIC, ctx.cls.thisRef.asmName, method.name, method.desc, false)
).push(Int::class.ref)
}
fun getOrCreateGrowMemoryMethod(ctx: FuncContext, func: Func): MethodNode =
ctx.cls.cls.methods.find { (it as? MethodNode)?.name == "\$\$growMemory" }?.let { it as MethodNode } ?: run {
val okLim = LabelNode()
val node = MethodNode(
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
"\$\$growMemory", "(Ljava/nio/ByteBuffer;I)I", null, null
).addInsns(
VarInsnNode(Opcodes.ALOAD, 0), // [mem]
forceFnType<ByteBuffer.() -> Int>(ByteBuffer::limit).invokeVirtual(), // [lim]
InsnNode(Opcodes.DUP), // [lim, lim]
VarInsnNode(Opcodes.ALOAD, 0), // [lim, lim, mem]
InsnNode(Opcodes.SWAP), // [lim, mem, lim]
VarInsnNode(Opcodes.ILOAD, 1), // [lim, mem, lim, pagedelt]
Mem.PAGE_SIZE.const, // [lim, mem, lim, pagedelt, pagesize]
// TODO: overflow check w/ Math.multiplyExact?
InsnNode(Opcodes.IMUL), // [lim, mem, lim, memdelt]
InsnNode(Opcodes.IADD), // [lim, mem, newlim]
InsnNode(Opcodes.DUP), // [lim, mem, newlim, newlim]
VarInsnNode(Opcodes.ALOAD, 0), // [lim, mem, newlim, newlim, mem]
ByteBuffer::capacity.invokeVirtual(), // [lim, mem, newlim, newlim, cap]
JumpInsnNode(Opcodes.IF_ICMPLE, okLim), // [lim, mem, newlim]
InsnNode(Opcodes.POP2), InsnNode(Opcodes.POP),
(-1).const,
InsnNode(Opcodes.IRETURN),
okLim, // [lim, mem, newlim]
forceFnType<ByteBuffer.(Int) -> Buffer>(ByteBuffer::limit).invokeVirtual(), // [lim, mem]
InsnNode(Opcodes.POP), // [lim]
Mem.PAGE_SIZE.const, // [lim, pagesize]
InsnNode(Opcodes.IDIV), // [limpages]
InsnNode(Opcodes.IRETURN)
)
ctx.cls.cls.methods.add(node)
node
}
override fun loadOp(ctx: FuncContext, func: Func, insn: Node.Instr.Args.AlignOffset): Func {
// Ug, some tests expect this to be a runtime failure so we feature flagged it

View File

@ -145,7 +145,7 @@ open class FuncBuilder {
is Node.Instr.CurrentMemory ->
applyCurrentMemory(ctx, fn)
is Node.Instr.GrowMemory ->
applyGrowMemory(ctx, fn, index)
applyGrowMemory(ctx, fn)
is Node.Instr.I32Const ->
fn.addInsns(i.value.const).push(Int::class.ref)
is Node.Instr.I64Const ->
@ -1029,13 +1029,10 @@ open class FuncBuilder {
).push(Int::class.ref)
}
fun applyGrowMemory(ctx: FuncContext, fn: Func, insnIndex: Int) =
fun applyGrowMemory(ctx: FuncContext, fn: Func) =
// Grow mem is a special case where the memory ref is already pre-injected on
// the stack before this call. But it can have a memory leftover on the stack
// so we pop it if we need to
ctx.cls.mem.growMemory(ctx, fn).let { fn ->
popMemoryIfNecessary(ctx, fn, ctx.insns.getOrNull(insnIndex + 1))
}
// the stack before this call. Result is an int.
ctx.cls.mem.growMemory(ctx, fn)
fun applyCurrentMemory(ctx: FuncContext, fn: Func) =
// Curr mem is not specially injected, so we have to put the memory on the

View File

@ -180,11 +180,11 @@ open class InsnReworker {
is Node.Instr.End, is Node.Instr.Br, is Node.Instr.BrIf,
is Node.Instr.BrTable, is Node.Instr.Return -> NOP
is Node.Instr.Call -> ctx.funcTypeAtIndex(insn.index).let {
// All calls pop "this" + params, and any return is a push
POP_THIS + (POP_PARAM + it.params.size) + (if (it.ret == null) NOP else PUSH_RESULT)
// All calls pop params and any return is a push
(POP_PARAM * it.params.size) + (if (it.ret == null) NOP else PUSH_RESULT)
}
is Node.Instr.CallIndirect -> ctx.mod.types[insn.index].let {
POP_THIS + (POP_PARAM + it.params.size) + (if (it.ret == null) NOP else PUSH_RESULT)
(POP_PARAM * it.params.size) + (if (it.ret == null) NOP else PUSH_RESULT)
}
is Node.Instr.Drop -> POP_PARAM
is Node.Instr.Select -> (POP_PARAM * 3) + PUSH_RESULT
@ -201,7 +201,7 @@ open class InsnReworker {
is Node.Instr.I32Store8, is Node.Instr.I32Store16, is Node.Instr.I64Store8, is Node.Instr.I64Store16,
is Node.Instr.I64Store32 -> POP_PARAM
is Node.Instr.CurrentMemory -> PUSH_RESULT
is Node.Instr.GrowMemory -> POP_PARAM
is Node.Instr.GrowMemory -> POP_PARAM + PUSH_RESULT
is Node.Instr.I32Const, is Node.Instr.I64Const,
is Node.Instr.F32Const, is Node.Instr.F64Const -> PUSH_RESULT
is Node.Instr.I32Add, is Node.Instr.I32Sub, is Node.Instr.I32Mul, is Node.Instr.I32DivS,

View File

@ -23,9 +23,9 @@ interface Mem {
// Caller can trust the mem instance is on the stack.
fun currentMemory(ctx: FuncContext, 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.
// Caller can trust the mem instance and then i32 delta page count is on the
// stack. Per the spec, the result needs to be the previous page count or -1
// on failure (i.e. do not throw).
fun growMemory(ctx: FuncContext, func: Func): Func
// Caller can trust the mem instance is on the stack

View File

@ -177,11 +177,6 @@ open class SyntheticAssertionBuilder {
}
}
fun MethodNode.addInsns(vararg insn: AbstractInsnNode): MethodNode {
insn.forEach(this.instructions::add)
return this
}
fun MethodNode.throwArith(msg: String) = this.addInsns(
TypeInsnNode(Opcodes.NEW, ArithmeticException::class.ref.asmName),
InsnNode(Opcodes.DUP),

View File

@ -42,6 +42,9 @@ class CoreTestUnit(val name: String, val wast: String, val expectedOutput: Strin
- 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
- memory.wast - Not handling mem data strings yet
*/
val knownGoodTests = arrayOf(
@ -104,7 +107,14 @@ class CoreTestUnit(val name: String, val wast: String, val expectedOutput: Strin
"import-after-table.fail.wast",
"int_exprs.wast",
"int_literals.wast",
"labels.wast"
"labels.wast",
"load-align-0.fail.wast",
"load-align-odd.fail.wast",
"loop.wast",
"loop-end-label-mismatch.fail.wast",
"loop-end-label-superfluous.fail.wast",
"memory_redundancy.wast",
"memory_trap.wast"
)
val unitsPath = "/spec/test/core"