mirror of
https://github.com/fluencelabs/asmble
synced 2025-04-24 22:32:19 +00:00
Fix grow_memory
This commit is contained in:
parent
19ed90163b
commit
1b31925a5a
@ -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())
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user