mirror of
https://github.com/fluencelabs/asmble
synced 2025-04-25 06:42:22 +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
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun MethodNode.addInsns(vararg insn: AbstractInsnNode): MethodNode {
|
||||||
|
insn.forEach(this.instructions::add)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
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())
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ open class AstToAsm {
|
|||||||
fun addConstructors(ctx: ClsContext) {
|
fun addConstructors(ctx: ClsContext) {
|
||||||
// We have at least two constructors:
|
// We have at least two constructors:
|
||||||
// <init>(int maxMemory, imports...)
|
// <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
|
// If the max memory was supplied in the mem section, we also have
|
||||||
// <init>(imports...)
|
// <init>(imports...)
|
||||||
// TODO: what happens w/ more than 254 imports?
|
// TODO: what happens w/ more than 254 imports?
|
||||||
@ -147,7 +147,7 @@ open class AstToAsm {
|
|||||||
val regCon = Func("<init>", importTypes).
|
val regCon = Func("<init>", importTypes).
|
||||||
addInsns(
|
addInsns(
|
||||||
VarInsnNode(Opcodes.ALOAD, 0),
|
VarInsnNode(Opcodes.ALOAD, 0),
|
||||||
it.const
|
(it * Mem.PAGE_SIZE).const
|
||||||
).
|
).
|
||||||
addInsns(importTypes.indices.map { VarInsnNode(Opcodes.ALOAD, it + 1) }).
|
addInsns(importTypes.indices.map { VarInsnNode(Opcodes.ALOAD, it + 1) }).
|
||||||
addInsns(
|
addInsns(
|
||||||
|
@ -3,10 +3,7 @@ package asmble.compile.jvm
|
|||||||
import asmble.ast.Node
|
import asmble.ast.Node
|
||||||
import org.objectweb.asm.Opcodes
|
import org.objectweb.asm.Opcodes
|
||||||
import org.objectweb.asm.Type
|
import org.objectweb.asm.Type
|
||||||
import org.objectweb.asm.tree.InsnNode
|
import org.objectweb.asm.tree.*
|
||||||
import org.objectweb.asm.tree.IntInsnNode
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode
|
|
||||||
import org.objectweb.asm.tree.TypeInsnNode
|
|
||||||
import java.nio.Buffer
|
import java.nio.Buffer
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
@ -56,13 +53,47 @@ open class ByteBufferMem(val direct: Boolean = true) : Mem {
|
|||||||
InsnNode(Opcodes.IDIV)
|
InsnNode(Opcodes.IDIV)
|
||||||
).push(Int::class.ref)
|
).push(Int::class.ref)
|
||||||
|
|
||||||
override fun growMemory(ctx: FuncContext, func: Func) =
|
override fun growMemory(ctx: FuncContext, func: Func) = getOrCreateGrowMemoryMethod(ctx, func).let { method ->
|
||||||
func.popExpecting(memType).popExpecting(Int::class.ref).addInsns(
|
func.popExpecting(Int::class.ref).popExpecting(memType).addInsns(
|
||||||
Mem.PAGE_SIZE.const,
|
// This is complicated enough to need a synthetic method
|
||||||
// TODO: overflow check, e.g. Math.multiplyExact
|
MethodInsnNode(Opcodes.INVOKESTATIC, ctx.cls.thisRef.asmName, method.name, method.desc, false)
|
||||||
InsnNode(Opcodes.IMUL),
|
).push(Int::class.ref)
|
||||||
forceFnType<ByteBuffer.(Int) -> Buffer>(ByteBuffer::limit).invokeVirtual()
|
}
|
||||||
).push(ByteBuffer::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 {
|
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
|
// 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 ->
|
is Node.Instr.CurrentMemory ->
|
||||||
applyCurrentMemory(ctx, fn)
|
applyCurrentMemory(ctx, fn)
|
||||||
is Node.Instr.GrowMemory ->
|
is Node.Instr.GrowMemory ->
|
||||||
applyGrowMemory(ctx, fn, index)
|
applyGrowMemory(ctx, fn)
|
||||||
is Node.Instr.I32Const ->
|
is Node.Instr.I32Const ->
|
||||||
fn.addInsns(i.value.const).push(Int::class.ref)
|
fn.addInsns(i.value.const).push(Int::class.ref)
|
||||||
is Node.Instr.I64Const ->
|
is Node.Instr.I64Const ->
|
||||||
@ -1029,13 +1029,10 @@ open class FuncBuilder {
|
|||||||
).push(Int::class.ref)
|
).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
|
// 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
|
// the stack before this call. Result is an int.
|
||||||
// so we pop it if we need to
|
ctx.cls.mem.growMemory(ctx, fn)
|
||||||
ctx.cls.mem.growMemory(ctx, fn).let { fn ->
|
|
||||||
popMemoryIfNecessary(ctx, fn, ctx.insns.getOrNull(insnIndex + 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun applyCurrentMemory(ctx: FuncContext, fn: Func) =
|
fun applyCurrentMemory(ctx: FuncContext, fn: Func) =
|
||||||
// Curr mem is not specially injected, so we have to put the memory on the
|
// 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.End, is Node.Instr.Br, is Node.Instr.BrIf,
|
||||||
is Node.Instr.BrTable, is Node.Instr.Return -> NOP
|
is Node.Instr.BrTable, is Node.Instr.Return -> NOP
|
||||||
is Node.Instr.Call -> ctx.funcTypeAtIndex(insn.index).let {
|
is Node.Instr.Call -> ctx.funcTypeAtIndex(insn.index).let {
|
||||||
// All calls pop "this" + params, and any return is a push
|
// All calls pop params and any return is a push
|
||||||
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.CallIndirect -> ctx.mod.types[insn.index].let {
|
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.Drop -> POP_PARAM
|
||||||
is Node.Instr.Select -> (POP_PARAM * 3) + PUSH_RESULT
|
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.I32Store8, is Node.Instr.I32Store16, is Node.Instr.I64Store8, is Node.Instr.I64Store16,
|
||||||
is Node.Instr.I64Store32 -> POP_PARAM
|
is Node.Instr.I64Store32 -> POP_PARAM
|
||||||
is Node.Instr.CurrentMemory -> PUSH_RESULT
|
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.I32Const, is Node.Instr.I64Const,
|
||||||
is Node.Instr.F32Const, is Node.Instr.F64Const -> PUSH_RESULT
|
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,
|
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.
|
// Caller can trust the mem instance is on the stack.
|
||||||
fun currentMemory(ctx: FuncContext, func: Func): Func
|
fun currentMemory(ctx: FuncContext, func: Func): Func
|
||||||
|
|
||||||
// Caller can trust the mem instance and then i32 page count is on the stack.
|
// Caller can trust the mem instance and then i32 delta page count is on the
|
||||||
// If it's already there after call anyways, this can leave the mem inst on
|
// stack. Per the spec, the result needs to be the previous page count or -1
|
||||||
// the stack and it will be reused or popped.
|
// on failure (i.e. do not throw).
|
||||||
fun growMemory(ctx: FuncContext, func: Func): Func
|
fun growMemory(ctx: FuncContext, func: Func): Func
|
||||||
|
|
||||||
// Caller can trust the mem instance is on the stack
|
// 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(
|
fun MethodNode.throwArith(msg: String) = this.addInsns(
|
||||||
TypeInsnNode(Opcodes.NEW, ArithmeticException::class.ref.asmName),
|
TypeInsnNode(Opcodes.NEW, ArithmeticException::class.ref.asmName),
|
||||||
InsnNode(Opcodes.DUP),
|
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
|
- func_ptrs.wast - Not handling tables yet
|
||||||
- globals.wast - No binary yet
|
- globals.wast - No binary yet
|
||||||
- imports.wast - No memory exports 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(
|
val knownGoodTests = arrayOf(
|
||||||
@ -104,7 +107,14 @@ class CoreTestUnit(val name: String, val wast: String, val expectedOutput: Strin
|
|||||||
"import-after-table.fail.wast",
|
"import-after-table.fail.wast",
|
||||||
"int_exprs.wast",
|
"int_exprs.wast",
|
||||||
"int_literals.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"
|
val unitsPath = "/spec/test/core"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user