Add invokedynamic instruction for call_indirect

This commit is contained in:
Chad Retz 2017-04-13 15:58:53 -05:00
parent a1d02602cf
commit edb7d64136
8 changed files with 131 additions and 33 deletions

View 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);
// }
}

View File

@ -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

View File

@ -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)
) )
} }
} }

View File

@ -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)
} }

View File

@ -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 {

View File

@ -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

View File

@ -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()
} }

View File

@ -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 }