mirror of
https://github.com/fluencelabs/asmble
synced 2025-04-24 22:32:19 +00:00
Add invokedynamic instruction for call_indirect
This commit is contained in:
parent
a1d02602cf
commit
edb7d64136
48
src/main/java/asmble/compile/jvm/RuntimeHelpers.java
Normal file
48
src/main/java/asmble/compile/jvm/RuntimeHelpers.java
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package asmble.compile.jvm;
|
||||||
|
|
||||||
|
import java.lang.invoke.*;
|
||||||
|
|
||||||
|
class RuntimeHelpers {
|
||||||
|
|
||||||
|
/** Gets a call site that accepts params THEN this THEN index of the table */
|
||||||
|
static CallSite bootstrapIndirect(MethodHandles.Lookup caller, String name, MethodType type) throws Exception {
|
||||||
|
MethodType withoutIndexOrThis = type.dropParameterTypes(type.parameterCount() - 2, type.parameterCount());
|
||||||
|
// Handle with mh at the beginning
|
||||||
|
MethodHandle mhFirst = MethodHandles.exactInvoker(withoutIndexOrThis);
|
||||||
|
// Last index first, then move each up 1
|
||||||
|
int[] reorder = new int[mhFirst.type().parameterCount()];
|
||||||
|
for (int i = 0; i < reorder.length; i++) {
|
||||||
|
if (i == 0) reorder[0] = reorder.length - 1;
|
||||||
|
else reorder[i] = i - 1;
|
||||||
|
}
|
||||||
|
// Method handle that moves the method handle to the end
|
||||||
|
MethodHandle mhAtEnd = MethodHandles.permuteArguments(mhFirst,
|
||||||
|
mhFirst.type().dropParameterTypes(0, 1).appendParameterTypes(MethodHandle.class), reorder );
|
||||||
|
// Method handle that changes an ending table + index to a method handle using the table
|
||||||
|
MethodHandle tableAndIndexAtEnd = MethodHandles.collectArguments(mhAtEnd, mhAtEnd.type().parameterCount() - 1,
|
||||||
|
MethodHandles.arrayElementGetter(MethodHandle[].class));
|
||||||
|
// Method handle that changes second-to-last this to table
|
||||||
|
MethodHandle thisAndIndexAtEnd = MethodHandles.filterArguments(tableAndIndexAtEnd,
|
||||||
|
tableAndIndexAtEnd.type().parameterCount() - 2,
|
||||||
|
caller.findGetter(caller.lookupClass(), "table", MethodHandle[].class));
|
||||||
|
return new ConstantCallSite(thisAndIndexAtEnd);
|
||||||
|
}
|
||||||
|
// static CallSite bootstrapIndirect(MethodHandles.Lookup caller, String name, MethodType type, MethodHandle table) {
|
||||||
|
// MethodType withoutIndexOrThis = type.dropParameterTypes(type.parameterCount() - 1, type.parameterCount());
|
||||||
|
// // Handle with mh at the beginning
|
||||||
|
// MethodHandle mhFirst = MethodHandles.exactInvoker(withoutIndex);
|
||||||
|
// // Last index first, then move each up 1
|
||||||
|
// int[] reorder = new int[type.parameterCount()];
|
||||||
|
// for (int i = 0; i < reorder.length; i++) {
|
||||||
|
// if (i == 0) reorder[0] = reorder.length - 1;
|
||||||
|
// else reorder[i] = i - 1;
|
||||||
|
// }
|
||||||
|
// // Method handle that moves the method handle to the end
|
||||||
|
// MethodHandle mhAtEnd = MethodHandles.permuteArguments(mhFirst,
|
||||||
|
// mhFirst.type().dropParameterTypes(0, 1).appendParameterTypes(MethodHandle.class), reorder );
|
||||||
|
// // Method handle that changes an ending index to a method handle using the table
|
||||||
|
// MethodHandle indexAtEnd = MethodHandles.filterArguments(mhAtEnd, type.parameterCount() - 1,
|
||||||
|
// MethodHandles.arrayElementGetter(MethodHandle[].class).bindTo(table));
|
||||||
|
// return new ConstantCallSite(indexAtEnd);
|
||||||
|
// }
|
||||||
|
}
|
@ -27,6 +27,8 @@ fun KFunction<*>.invokeVirtual() =
|
|||||||
|
|
||||||
inline fun <T : Function<*>> forceFnType(fn: T) = fn as KFunction<*>
|
inline fun <T : Function<*>> forceFnType(fn: T) = fn as KFunction<*>
|
||||||
|
|
||||||
|
val KClass<*>.const: LdcInsnNode get() = (if (this == Void::class) Void.TYPE else this.java).const
|
||||||
|
val KClass<*>.asmType: Type get() = (if (this == Void::class) Void.TYPE else this.java).asmType
|
||||||
val KClass<*>.ref: TypeRef get() = (if (this == Void::class) Void.TYPE else this.java).ref
|
val KClass<*>.ref: TypeRef get() = (if (this == Void::class) Void.TYPE else this.java).ref
|
||||||
|
|
||||||
fun <T : Exception> KClass<T>.athrow(msg: String) = listOf(
|
fun <T : Exception> KClass<T>.athrow(msg: String) = listOf(
|
||||||
@ -43,7 +45,11 @@ fun KClass<*>.invokeStatic(name: String, retType: KClass<*>, vararg params: KCla
|
|||||||
MethodInsnNode(Opcodes.INVOKESTATIC, this.javaObjectType.ref.asmName, name,
|
MethodInsnNode(Opcodes.INVOKESTATIC, this.javaObjectType.ref.asmName, name,
|
||||||
retType.ref.asMethodRetDesc(*params.map { it.ref }.toTypedArray()), false)
|
retType.ref.asMethodRetDesc(*params.map { it.ref }.toTypedArray()), false)
|
||||||
|
|
||||||
val Class<*>.ref: TypeRef get() = TypeRef(Type.getType(this))
|
val Class<*>.const: LdcInsnNode get() = LdcInsnNode(this)
|
||||||
|
|
||||||
|
val Class<*>.asmType: Type get() = Type.getType(this)
|
||||||
|
|
||||||
|
val Class<*>.ref: TypeRef get() = TypeRef(this.asmType)
|
||||||
|
|
||||||
val Class<*>.valueType: Node.Type.Value? get() = when (this) {
|
val Class<*>.valueType: Node.Type.Value? get() = when (this) {
|
||||||
Void.TYPE -> null
|
Void.TYPE -> null
|
||||||
|
@ -313,11 +313,13 @@ open class AstToAsm {
|
|||||||
ctx.funcName(funcIndex).const,
|
ctx.funcName(funcIndex).const,
|
||||||
// Method type const, yay
|
// Method type const, yay
|
||||||
LdcInsnNode(Type.getMethodType(funcType.asmDesc)),
|
LdcInsnNode(Type.getMethodType(funcType.asmDesc)),
|
||||||
MethodHandles.Lookup::bind.invokeStatic()
|
MethodHandles.Lookup::bind.invokeVirtual()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
).addInsns(
|
||||||
|
InsnNode(Opcodes.AASTORE)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ data class ClsContext(
|
|||||||
val reworker: InsnReworker = InsnReworker,
|
val reworker: InsnReworker = InsnReworker,
|
||||||
val logger: Logger = Logger.Print(Logger.Level.OFF),
|
val logger: Logger = Logger.Print(Logger.Level.OFF),
|
||||||
val funcBuilder: FuncBuilder = FuncBuilder,
|
val funcBuilder: FuncBuilder = FuncBuilder,
|
||||||
val syntheticAssertionBuilder: SyntheticAssertionBuilder = SyntheticAssertionBuilder,
|
val syntheticFuncBuilder: SyntheticFuncBuilder = SyntheticFuncBuilder,
|
||||||
val checkTruncOverflow: Boolean = true,
|
val checkTruncOverflow: Boolean = true,
|
||||||
val nonAdjacentMemAccessesRequiringLocalVar: Int = 3,
|
val nonAdjacentMemAccessesRequiringLocalVar: Int = 3,
|
||||||
val eagerFailLargeMemOffset: Boolean = true,
|
val eagerFailLargeMemOffset: Boolean = true,
|
||||||
@ -67,25 +67,27 @@ 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(
|
private fun syntheticFunc(
|
||||||
nameSuffix: String,
|
nameSuffix: String,
|
||||||
fn: SyntheticAssertionBuilder.(ClsContext, String) -> MethodNode
|
fn: SyntheticFuncBuilder.(ClsContext, String) -> MethodNode
|
||||||
): MethodInsnNode {
|
): MethodInsnNode {
|
||||||
val name = "\$\$$nameSuffix"
|
val name = "\$\$$nameSuffix"
|
||||||
val method =
|
val method =
|
||||||
cls.methods.find { (it as MethodNode).name == name }?.let { it as MethodNode } ?:
|
cls.methods.find { (it as MethodNode).name == name }?.let { it as MethodNode } ?:
|
||||||
fn(syntheticAssertionBuilder, this, name).also { cls.methods.add(it) }
|
fn(syntheticFuncBuilder, this, name).also { cls.methods.add(it) }
|
||||||
return MethodInsnNode(Opcodes.INVOKESTATIC, thisRef.asmName, method.name, method.desc, false)
|
return MethodInsnNode(Opcodes.INVOKESTATIC, thisRef.asmName, method.name, method.desc, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
val truncAssertF2SI get() = syntheticAssertion("assertF2SI", SyntheticAssertionBuilder::buildF2SIAssertion)
|
val truncAssertF2SI get() = syntheticFunc("assertF2SI", SyntheticFuncBuilder::buildF2SIAssertion)
|
||||||
val truncAssertF2UI get() = syntheticAssertion("assertF2UI", SyntheticAssertionBuilder::buildF2UIAssertion)
|
val truncAssertF2UI get() = syntheticFunc("assertF2UI", SyntheticFuncBuilder::buildF2UIAssertion)
|
||||||
val truncAssertF2SL get() = syntheticAssertion("assertF2SL", SyntheticAssertionBuilder::buildF2SLAssertion)
|
val truncAssertF2SL get() = syntheticFunc("assertF2SL", SyntheticFuncBuilder::buildF2SLAssertion)
|
||||||
val truncAssertF2UL get() = syntheticAssertion("assertF2UL", SyntheticAssertionBuilder::buildF2ULAssertion)
|
val truncAssertF2UL get() = syntheticFunc("assertF2UL", SyntheticFuncBuilder::buildF2ULAssertion)
|
||||||
val truncAssertD2SI get() = syntheticAssertion("assertD2SI", SyntheticAssertionBuilder::buildD2SIAssertion)
|
val truncAssertD2SI get() = syntheticFunc("assertD2SI", SyntheticFuncBuilder::buildD2SIAssertion)
|
||||||
val truncAssertD2UI get() = syntheticAssertion("assertD2UI", SyntheticAssertionBuilder::buildD2UIAssertion)
|
val truncAssertD2UI get() = syntheticFunc("assertD2UI", SyntheticFuncBuilder::buildD2UIAssertion)
|
||||||
val truncAssertD2SL get() = syntheticAssertion("assertD2SL", SyntheticAssertionBuilder::buildD2SLAssertion)
|
val truncAssertD2SL get() = syntheticFunc("assertD2SL", SyntheticFuncBuilder::buildD2SLAssertion)
|
||||||
val truncAssertD2UL get() = syntheticAssertion("assertD2UL", SyntheticAssertionBuilder::buildD2ULAssertion)
|
val truncAssertD2UL get() = syntheticFunc("assertD2UL", SyntheticFuncBuilder::buildD2ULAssertion)
|
||||||
val divAssertI get() = syntheticAssertion("assertIDiv", SyntheticAssertionBuilder::buildIDivAssertion)
|
val divAssertI get() = syntheticFunc("assertIDiv", SyntheticFuncBuilder::buildIDivAssertion)
|
||||||
val divAssertL get() = syntheticAssertion("assertLDiv", SyntheticAssertionBuilder::buildLDivAssertion)
|
val divAssertL get() = syntheticFunc("assertLDiv", SyntheticFuncBuilder::buildLDivAssertion)
|
||||||
|
|
||||||
|
val indirectBootstrap get() = syntheticFunc("indirectBootstrap", SyntheticFuncBuilder::buildIndirectBootstrap)
|
||||||
}
|
}
|
@ -3,7 +3,9 @@ package asmble.compile.jvm
|
|||||||
import asmble.ast.Node
|
import asmble.ast.Node
|
||||||
import asmble.util.Either
|
import asmble.util.Either
|
||||||
import asmble.util.add
|
import asmble.util.add
|
||||||
|
import org.objectweb.asm.Handle
|
||||||
import org.objectweb.asm.Opcodes
|
import org.objectweb.asm.Opcodes
|
||||||
|
import org.objectweb.asm.Type
|
||||||
import org.objectweb.asm.tree.*
|
import org.objectweb.asm.tree.*
|
||||||
import java.lang.invoke.MethodHandle
|
import java.lang.invoke.MethodHandle
|
||||||
|
|
||||||
@ -1181,19 +1183,33 @@ open class FuncBuilder {
|
|||||||
|
|
||||||
fun applyCallIndirectInsn(ctx: FuncContext, fn: Func, index: Int): Func {
|
fun applyCallIndirectInsn(ctx: FuncContext, fn: Func, index: Int): Func {
|
||||||
if (!ctx.cls.hasTable) throw CompileErr.UnknownTable()
|
if (!ctx.cls.hasTable) throw CompileErr.UnknownTable()
|
||||||
// The index param is a type index, the on-stack value is the table index.
|
// For this we do an invokedynamic which calls the bootstrap method. The
|
||||||
// So we'll put the table array on the stack, the swap, then aaload, then invoke exact
|
// bootstrap method is a synthetic method embedded into this module. The
|
||||||
val funcType = ctx.cls.mod.types[index]
|
// resulting method handle accepts all method params THEN "this" THEN
|
||||||
return fn.popExpecting(Int::class.ref).
|
// the table index. Stack manip prior to this has ensured "this" is on
|
||||||
addInsns(
|
// the stack before the index.
|
||||||
VarInsnNode(Opcodes.ALOAD, 0),
|
return ctx.cls.indirectBootstrap.let { indirectBootstrapNode ->
|
||||||
FieldInsnNode(Opcodes.GETFIELD, ctx.cls.thisRef.asmName,
|
val funcType = ctx.cls.mod.types[index]
|
||||||
"table", Array<MethodHandle>::class.ref.asmDesc),
|
val desc = Type.getMethodDescriptor(
|
||||||
InsnNode(Opcodes.SWAP),
|
(funcType.ret?.jclass ?: Void.TYPE).asmType,
|
||||||
InsnNode(Opcodes.AALOAD),
|
// All params
|
||||||
MethodInsnNode(Opcodes.INVOKEVIRTUAL, MethodHandle::class.ref.asmName,
|
*funcType.params.map { it.jclass.asmType }.toTypedArray(),
|
||||||
"invokeExact", funcType.asmDesc, false)
|
// This
|
||||||
|
ctx.cls.thisRef.asm,
|
||||||
|
// The int index
|
||||||
|
Type.INT_TYPE
|
||||||
)
|
)
|
||||||
|
fn.popExpecting(Int::class.ref).popExpecting(ctx.cls.thisRef).
|
||||||
|
popExpectingMulti(funcType.params.map(Node.Type.Value::typeRef)).
|
||||||
|
addInsns(
|
||||||
|
InvokeDynamicInsnNode(
|
||||||
|
"indirectBootstrap",
|
||||||
|
desc,
|
||||||
|
Handle(Opcodes.H_INVOKESTATIC, indirectBootstrapNode.owner,
|
||||||
|
indirectBootstrapNode.name, indirectBootstrapNode.desc, false)
|
||||||
|
)
|
||||||
|
).let { fn -> funcType.ret?.let { fn.push(it.typeRef) } ?: fn }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun applyReturnInsn(ctx: FuncContext, fn: Func): Func {
|
fun applyReturnInsn(ctx: FuncContext, fn: Func): Func {
|
||||||
|
@ -132,7 +132,9 @@ open class InsnReworker {
|
|||||||
else Insn.ThisNeededOnStack
|
else Insn.ThisNeededOnStack
|
||||||
injectBeforeLastStackCount(inject, ctx.funcTypeAtIndex(insn.index).params.size)
|
injectBeforeLastStackCount(inject, ctx.funcTypeAtIndex(insn.index).params.size)
|
||||||
}
|
}
|
||||||
is Node.Instr.CallIndirect -> TODO("Not sure what I need yet")
|
// Indirect calls require "this" before the index
|
||||||
|
is Node.Instr.CallIndirect ->
|
||||||
|
injectBeforeLastStackCount(Insn.ThisNeededOnStack, 1)
|
||||||
// Global set requires "this" before the single param
|
// Global set requires "this" before the single param
|
||||||
is Node.Instr.SetGlobal -> {
|
is Node.Instr.SetGlobal -> {
|
||||||
val inject =
|
val inject =
|
||||||
@ -179,7 +181,8 @@ open class InsnReworker {
|
|||||||
(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_PARAM * it.params.size) + (if (it.ret == null) NOP else PUSH_RESULT)
|
// We add one for the table index
|
||||||
|
POP_PARAM + (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
|
||||||
|
@ -1,9 +1,27 @@
|
|||||||
package asmble.compile.jvm
|
package asmble.compile.jvm
|
||||||
|
|
||||||
|
import org.objectweb.asm.ClassReader
|
||||||
import org.objectweb.asm.Opcodes
|
import org.objectweb.asm.Opcodes
|
||||||
import org.objectweb.asm.tree.*
|
import org.objectweb.asm.tree.*
|
||||||
|
|
||||||
open class SyntheticAssertionBuilder {
|
open class SyntheticFuncBuilder {
|
||||||
|
|
||||||
|
fun buildIndirectBootstrap(ctx: ClsContext, name: String): MethodNode {
|
||||||
|
// With WASM, you have all parameters then the index of the method to call in the table.
|
||||||
|
// So we need to build a dynamic call that can take parameters + the table index.
|
||||||
|
// Just take the helper's instructions and add them here. For now we don't cache because
|
||||||
|
// ASM does some annoying state manip even when just looping over instructions. For now,
|
||||||
|
// none of the insns in the helper reference the "owner" so we don't have to change those.
|
||||||
|
val runtimeHelpers = ClassNode().also {
|
||||||
|
ClassReader(RuntimeHelpers::class.java.name).
|
||||||
|
accept(it, ClassReader.SKIP_DEBUG and ClassReader.SKIP_FRAMES)
|
||||||
|
}
|
||||||
|
val helperMeth = runtimeHelpers.methods.first { (it as MethodNode).name == "bootstrapIndirect" } as MethodNode
|
||||||
|
return MethodNode(
|
||||||
|
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, name,
|
||||||
|
helperMeth.desc, null, null
|
||||||
|
).addInsns(*helperMeth.instructions.toArray())
|
||||||
|
}
|
||||||
|
|
||||||
fun buildIDivAssertion(ctx: ClsContext, name: String) =
|
fun buildIDivAssertion(ctx: ClsContext, name: String) =
|
||||||
LabelNode().let { safeLabel ->
|
LabelNode().let { safeLabel ->
|
||||||
@ -186,5 +204,5 @@ open class SyntheticAssertionBuilder {
|
|||||||
InsnNode(Opcodes.ATHROW)
|
InsnNode(Opcodes.ATHROW)
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object : SyntheticAssertionBuilder()
|
companion object : SyntheticFuncBuilder()
|
||||||
}
|
}
|
@ -40,7 +40,10 @@ data class ScriptContext(
|
|||||||
}
|
}
|
||||||
is Script.Cmd.Register ->
|
is Script.Cmd.Register ->
|
||||||
copy(registrations = registrations + (
|
copy(registrations = registrations + (
|
||||||
cmd.string to (modules.lastOrNull() ?: error("No module to register"))
|
cmd.string to (
|
||||||
|
(if (cmd.name != null) modules.find { it.name == cmd.name } else modules.lastOrNull()) ?:
|
||||||
|
error("No module to register")
|
||||||
|
)
|
||||||
))
|
))
|
||||||
is Script.Cmd.Action ->
|
is Script.Cmd.Action ->
|
||||||
doAction(cmd).let { this }
|
doAction(cmd).let { this }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user