mirror of
https://github.com/fluencelabs/asmble
synced 2025-04-25 14:52:21 +00:00
More compiler work
This commit is contained in:
parent
d310acde44
commit
40bd73c9a1
59
src/main/java/asmble/temp/Temp.java
Normal file
59
src/main/java/asmble/temp/Temp.java
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package asmble.temp;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
class Temp {
|
||||||
|
|
||||||
|
public void temp() throws Throwable {
|
||||||
|
MethodHandle mh = MethodHandles.lookup().findVirtual(Temp.class, "foo",
|
||||||
|
MethodType.methodType(String.class));
|
||||||
|
int ret = (int) mh.invokeExact();
|
||||||
|
throw new UnsupportedOperationException("Unreachable: " + ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int foo() {
|
||||||
|
return 45;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Module1 {
|
||||||
|
|
||||||
|
private final ByteBuffer memory;
|
||||||
|
private final MethodHandle spectestPrint;
|
||||||
|
// private final MethodHandle localFunc0;
|
||||||
|
|
||||||
|
public Module1(int amount, MethodHandle spectestPrint) {
|
||||||
|
this(ByteBuffer.allocateDirect(amount), spectestPrint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Module1(ByteBuffer memory, MethodHandle spectestPrint) {
|
||||||
|
// TODO: could check memory capacity here
|
||||||
|
// We trust this is zeroed
|
||||||
|
this.memory = memory;
|
||||||
|
this.memory.limit(65536 /* 1 page */);
|
||||||
|
this.memory.put(new byte[] { 1, 2, 3 /*...*/ }, 0, 3 /* full length */);
|
||||||
|
this.spectestPrint = spectestPrint;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void good(int param0) throws Throwable {
|
||||||
|
$func1(param0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void $func1(int param0) throws Throwable {
|
||||||
|
// Compiler option to determine number of accesses before it's made a local var...default 1
|
||||||
|
ByteBuffer memory = this.memory;
|
||||||
|
MethodHandle spectestPrint = this.spectestPrint;
|
||||||
|
// (call $print (i32.load8_u offset=0 (get_local $i))) ;; 97 'a'
|
||||||
|
// iload_1
|
||||||
|
int iload_1 = param0;
|
||||||
|
int param_var = memory.get(iload_1);
|
||||||
|
// TODO: compiler option to not put Throwable on functions
|
||||||
|
spectestPrint.invokeExact(param_var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package asmble.compile.jvm
|
package asmble.compile.jvm
|
||||||
|
|
||||||
|
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.*
|
import org.objectweb.asm.tree.*
|
||||||
@ -15,23 +16,31 @@ val <R> KFunction<R>.asmDesc: String get() = Type.getMethodDescriptor(this.javaM
|
|||||||
val <R> KFunction<R>.declarer: Class<*> get() = this.javaMethod!!.declaringClass
|
val <R> KFunction<R>.declarer: Class<*> get() = this.javaMethod!!.declaringClass
|
||||||
|
|
||||||
fun KFunction<*>.invokeStatic() =
|
fun KFunction<*>.invokeStatic() =
|
||||||
MethodInsnNode(Opcodes.INVOKESTATIC, this.declarer.asmName, this.name, this.asmDesc, false)
|
MethodInsnNode(Opcodes.INVOKESTATIC, this.declarer.ref.asmName, this.name, this.asmDesc, false)
|
||||||
|
|
||||||
fun KFunction<*>.invokeVirtual() =
|
fun KFunction<*>.invokeVirtual() =
|
||||||
MethodInsnNode(Opcodes.INVOKEVIRTUAL, this.declarer.asmName, this.name, this.asmDesc, false)
|
MethodInsnNode(Opcodes.INVOKEVIRTUAL, this.declarer.ref.asmName, this.name, this.asmDesc, false)
|
||||||
|
|
||||||
inline fun <T> forceType(fn: T) = fn
|
inline fun <T> forceType(fn: T) = fn
|
||||||
inline fun <T : Function<*>> forceFnType(fn: T) = fn.reflect()!!
|
inline fun <T : Function<*>> forceFnType(fn: T) = fn.reflect()!!
|
||||||
|
|
||||||
val <T : Any> KClass<T>.asmName: String get() = this.java.asmName
|
val KClass<*>.ref: TypeRef get() = this.java.ref
|
||||||
|
|
||||||
val <T : Any> Class<T>.asmName: String get() = Type.getInternalName(this)
|
fun <T : Exception> KClass<T>.athrow(msg: String) = listOf(
|
||||||
|
TypeInsnNode(Opcodes.NEW, this.ref.asmName),
|
||||||
|
InsnNode(Opcodes.DUP),
|
||||||
|
msg.const,
|
||||||
|
MethodInsnNode(Opcodes.INVOKESPECIAL, this.ref.asmName, "<init>",
|
||||||
|
Void::class.ref.asMethodRetDesc(String::class.ref), false)
|
||||||
|
)
|
||||||
|
|
||||||
|
val Class<*>.ref: TypeRef get() = TypeRef(Type.getType(this))
|
||||||
|
|
||||||
val KProperty<*>.declarer: Class<*> get() = this.javaField!!.declaringClass
|
val KProperty<*>.declarer: Class<*> get() = this.javaField!!.declaringClass
|
||||||
val KProperty<*>.asmDesc: String get() = Type.getDescriptor(this.javaField!!.type)
|
val KProperty<*>.asmDesc: String get() = Type.getDescriptor(this.javaField!!.type)
|
||||||
|
|
||||||
fun KProperty<*>.getStatic() =
|
fun KProperty<*>.getStatic() =
|
||||||
FieldInsnNode(Opcodes.GETSTATIC, this.declarer.asmName, this.name, this.asmDesc)
|
FieldInsnNode(Opcodes.GETSTATIC, this.declarer.ref.asmDesc, this.name, this.asmDesc)
|
||||||
|
|
||||||
val Int.const: AbstractInsnNode get() = when (this) {
|
val Int.const: AbstractInsnNode get() = when (this) {
|
||||||
-1 -> InsnNode(Opcodes.ICONST_M1)
|
-1 -> InsnNode(Opcodes.ICONST_M1)
|
||||||
@ -45,3 +54,30 @@ val Int.const: AbstractInsnNode get() = when (this) {
|
|||||||
in (Byte.MAX_VALUE + 1)..Short.MAX_VALUE -> IntInsnNode(Opcodes.SIPUSH, this)
|
in (Byte.MAX_VALUE + 1)..Short.MAX_VALUE -> IntInsnNode(Opcodes.SIPUSH, this)
|
||||||
else -> LdcInsnNode(this)
|
else -> LdcInsnNode(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Int.isAccess(access: Int) = (this and access) == access
|
||||||
|
val Int.isAccessStatic: Boolean get() = this.isAccess(Opcodes.ACC_STATIC)
|
||||||
|
|
||||||
|
val String.const: AbstractInsnNode get() = LdcInsnNode(this)
|
||||||
|
|
||||||
|
val Node.Type.Value.kclass: KClass<*> get() = when (this) {
|
||||||
|
Node.Type.Value.I32 -> Int::class
|
||||||
|
Node.Type.Value.I64 -> Long::class
|
||||||
|
Node.Type.Value.F32 -> Float::class
|
||||||
|
Node.Type.Value.F64 -> Double::class
|
||||||
|
}
|
||||||
|
|
||||||
|
val Node.Type.Value.typeRef: TypeRef get() = this.jclass.ref
|
||||||
|
val Node.Type.Value.jclass: Class<*> get() = this.kclass.java
|
||||||
|
|
||||||
|
val AbstractInsnNode.isTerminating: Boolean get() = when (this.opcode) {
|
||||||
|
Opcodes.ARETURN, Opcodes.ATHROW, Opcodes.DRETURN, Opcodes.FRETURN,
|
||||||
|
Opcodes.IRETURN, Opcodes.LRETURN, Opcodes.RETURN -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
val Node.Type.Func.asmDesc: String get() =
|
||||||
|
(this.ret?.typeRef ?: Void::class.ref).asMethodRetDesc(*this.params.map { it.typeRef }.toTypedArray())
|
||||||
|
|
||||||
|
fun Node.Func.actualLocalIndex(givenIndex: Int) =
|
||||||
|
this.locals.take(givenIndex).sumBy { if (it == Node.Type.Value.I64 || it == Node.Type.Value.F64) 2 else 1 }
|
@ -1,10 +1,342 @@
|
|||||||
package asmble.compile.jvm
|
package asmble.compile.jvm
|
||||||
|
|
||||||
import asmble.ast.Node
|
import asmble.ast.Node
|
||||||
import org.objectweb.asm.tree.ClassNode
|
import org.objectweb.asm.Opcodes
|
||||||
|
import org.objectweb.asm.Type
|
||||||
|
import org.objectweb.asm.tree.*
|
||||||
|
import java.lang.invoke.MethodHandle
|
||||||
|
|
||||||
open class AstToAsm {
|
open class AstToAsm {
|
||||||
fun fromModule(n: Node.Module): ClassNode {
|
// Note, the class does not have a name out of here (yet)
|
||||||
TODO()
|
fun fromModule(ctx: Context) {
|
||||||
|
// Invoke dynamic among other things
|
||||||
|
ctx.cls.version = Opcodes.V1_7
|
||||||
|
ctx.cls.access += Opcodes.ACC_PUBLIC
|
||||||
|
// TODO: make sure the imports are sorted as we expect
|
||||||
|
addFields(ctx)
|
||||||
|
addConstructors(ctx)
|
||||||
|
addFuncs(ctx)
|
||||||
|
// TODO: addImportForwarders
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addFields(ctx: Context) {
|
||||||
|
// First field is always a private final memory field
|
||||||
|
// Ug, ambiguity on List<?> +=
|
||||||
|
ctx.cls.fields.plusAssign(FieldNode(Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL, "memory",
|
||||||
|
ctx.mem.memType.asmDesc, null, null))
|
||||||
|
// Now all method imports as method handles
|
||||||
|
ctx.cls.fields += ctx.importFuncs.indices.map {
|
||||||
|
FieldNode(Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL, importFuncFieldName(it),
|
||||||
|
MethodHandle::class.ref.asmDesc, null, null)
|
||||||
|
}
|
||||||
|
// Now all import globals as getter (and maybe setter) method handles
|
||||||
|
ctx.cls.fields += ctx.importGlobals.withIndex().flatMap { (index, import) ->
|
||||||
|
val ret = listOf(FieldNode(Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL, importGlobalGetterFieldName(index),
|
||||||
|
MethodHandle::class.ref.asmDesc, null, null))
|
||||||
|
if (!(import.kind as Node.Import.Kind.Global).type.mutable) ret else {
|
||||||
|
ret + FieldNode(Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL, importGlobalSetterFieldName(index),
|
||||||
|
MethodHandle::class.ref.asmDesc, null, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Now all non-import globals
|
||||||
|
ctx.cls.fields += ctx.mod.globals.withIndex().map { (index, global) ->
|
||||||
|
// In the MVP, we can trust the init is constant stuff and a single instr
|
||||||
|
require(global.init.size <= 1) { "Global init has more than 1 insn" }
|
||||||
|
val init: Number = global.init.firstOrNull().let {
|
||||||
|
when (it) {
|
||||||
|
is Node.Instr.Args.Const<*> -> it.value
|
||||||
|
null -> when (global.type.contentType) {
|
||||||
|
is Node.Type.Value.I32 -> 0
|
||||||
|
is Node.Type.Value.I64 -> 0L
|
||||||
|
is Node.Type.Value.F32 -> 0F
|
||||||
|
is Node.Type.Value.F64 -> 0.0
|
||||||
|
}
|
||||||
|
else -> throw RuntimeException("Unsupported init insn: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val access = Opcodes.ACC_PRIVATE + if (!global.type.mutable) Opcodes.ACC_FINAL else 0
|
||||||
|
FieldNode(access, globalName(ctx.importGlobals.size + index),
|
||||||
|
global.type.contentType.typeRef.asmDesc, null, init)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addConstructors(ctx: Context) {
|
||||||
|
// We have at least two constructors:
|
||||||
|
// <init>(int maxMemory, imports...)
|
||||||
|
// <init>(MemClass maxMemory, imports...)
|
||||||
|
// If the max memory was supplied in the mem section, we also have
|
||||||
|
// <init>(imports...)
|
||||||
|
// TODO: what happens w/ more than 254 imports?
|
||||||
|
|
||||||
|
val importTypes = ctx.mod.imports.map {
|
||||||
|
when (it.kind) {
|
||||||
|
is Node.Import.Kind.Func -> MethodHandle::class.ref
|
||||||
|
else -> TODO()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// <init>(MemClass maxMemory, imports...)
|
||||||
|
// Set the mem field then call init then put it back on the stack
|
||||||
|
var memCon = Func("<init>", listOf(ctx.mem.memType) + importTypes).addInsns(
|
||||||
|
VarInsnNode(Opcodes.ALOAD, 0),
|
||||||
|
VarInsnNode(Opcodes.ALOAD, 1),
|
||||||
|
FieldInsnNode(Opcodes.PUTFIELD, ctx.thisRef.asmName, "memory", ctx.mem.memType.asmDesc),
|
||||||
|
VarInsnNode(Opcodes.ALOAD, 1)
|
||||||
|
).push(ctx.mem.memType)
|
||||||
|
// Do mem init and remove it from the stack if it's still there afterwards
|
||||||
|
memCon = ctx.mem.init(memCon, ctx.mod.memories.first().limits.initial)
|
||||||
|
if (memCon.stack.lastOrNull() == ctx.mem.memType) memCon = memCon.popExpecting(ctx.mem.memType)
|
||||||
|
// Set all import functions
|
||||||
|
memCon = ctx.importFuncs.indices.fold(memCon) { memCon, importIndex ->
|
||||||
|
memCon.addInsns(
|
||||||
|
VarInsnNode(Opcodes.ALOAD, 0),
|
||||||
|
VarInsnNode(Opcodes.ALOAD, importIndex + 1),
|
||||||
|
FieldInsnNode(Opcodes.PUTFIELD, ctx.thisRef.asmName,
|
||||||
|
importFuncFieldName(importIndex), MethodHandle::class.ref.asmDesc)
|
||||||
|
)
|
||||||
|
}.addInsns(InsnNode(Opcodes.RETURN))
|
||||||
|
|
||||||
|
// <init>(int maxMemory, imports...)
|
||||||
|
// Just call this(createMem(maxMemory), imports...)
|
||||||
|
var amountCon = Func("<init>", listOf(Int::class.ref) + importTypes).addInsns(
|
||||||
|
VarInsnNode(Opcodes.ALOAD, 0),
|
||||||
|
VarInsnNode(Opcodes.ILOAD, 1)
|
||||||
|
).push(ctx.thisRef, Int::class.ref)
|
||||||
|
amountCon = ctx.mem.create(amountCon).popExpectingMulti(ctx.thisRef, ctx.mem.memType)
|
||||||
|
// In addition to this and mem on the stack, add all imports
|
||||||
|
amountCon = amountCon.params.drop(1).foldIndexed(amountCon) { index, amountCon, param ->
|
||||||
|
amountCon.addInsns(VarInsnNode(Opcodes.ALOAD, 2 + index))
|
||||||
|
}
|
||||||
|
// Make call
|
||||||
|
amountCon = amountCon.addInsns(
|
||||||
|
MethodInsnNode(Opcodes.INVOKESPECIAL, ctx.thisRef.asmName, memCon.name, memCon.desc, false),
|
||||||
|
InsnNode(Opcodes.RETURN)
|
||||||
|
)
|
||||||
|
|
||||||
|
var constructors = listOf(amountCon, memCon)
|
||||||
|
|
||||||
|
//<init>(imports...) only if there was a given max
|
||||||
|
ctx.mod.memories.getOrNull(0)?.limits?.maximum?.also {
|
||||||
|
val regCon = Func("<init>", importTypes).
|
||||||
|
addInsns(VarInsnNode(Opcodes.ALOAD, 0)).
|
||||||
|
addInsns(importTypes.indices.map { VarInsnNode(Opcodes.ALOAD, it + 1) }).
|
||||||
|
addInsns(InsnNode(Opcodes.RETURN))
|
||||||
|
constructors = listOf(regCon) + constructors
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.cls.methods += constructors.map(Func::toMethodNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addFuncs(ctx: Context) {
|
||||||
|
ctx.cls.methods += ctx.mod.funcs.mapIndexed { index, func ->
|
||||||
|
fromFunc(ctx, func, ctx.importFuncs.size + index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun importGlobalGetterFieldName(index: Int) = "import\$get" + globalName(index)
|
||||||
|
fun importGlobalSetterFieldName(index: Int) = "import\$set" + globalName(index)
|
||||||
|
fun globalName(index: Int) = "\$global$index"
|
||||||
|
fun importFuncFieldName(index: Int) = "import" + funcName(index)
|
||||||
|
fun funcName(index: Int) = "\$func$index"
|
||||||
|
|
||||||
|
fun fromFunc(ctx: Context, f: Node.Func, index: Int): Func {
|
||||||
|
// Technically all local funcs are static with "this" as the last param.
|
||||||
|
// This is important because it allows us to call other functions without
|
||||||
|
// reworking the stack. They are private, and if they are exported then
|
||||||
|
// the parameters get turned around as expected.
|
||||||
|
// TODO: validate local size?
|
||||||
|
// TODO: initialize non-param locals?
|
||||||
|
var func = Func(
|
||||||
|
access = Opcodes.ACC_STATIC + Opcodes.ACC_PRIVATE,
|
||||||
|
name = funcName(index),
|
||||||
|
params = f.type.params.map(Node.Type.Value::typeRef) + ctx.thisRef,
|
||||||
|
ret = f.type.ret?.let(Node.Type.Value::typeRef) ?: Void::class.ref
|
||||||
|
)
|
||||||
|
// Add all instructions
|
||||||
|
func = f.instructions.fold(func) { func, insn -> applyInsn(ctx, f, func, insn) }
|
||||||
|
return func
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyInsn(ctx: Context, f: Node.Func, fn: Func, i: Node.Instr) = when (i) {
|
||||||
|
is Node.Instr.Unreachable ->
|
||||||
|
fn.addInsns(UnsupportedOperationException::class.athrow("Unreachable"))
|
||||||
|
is Node.Instr.Nop ->
|
||||||
|
fn.addInsns(InsnNode(Opcodes.NOP))
|
||||||
|
// TODO: other control flow...
|
||||||
|
is Node.Instr.Return ->
|
||||||
|
applyReturnInsn(ctx, f, fn)
|
||||||
|
is Node.Instr.Call ->
|
||||||
|
applyCallInsn(ctx, f, fn, i.index, false)
|
||||||
|
is Node.Instr.CallIndirect ->
|
||||||
|
applyCallInsn(ctx, f, fn, i.index, true)
|
||||||
|
is Node.Instr.Drop ->
|
||||||
|
fn.pop().let { (fn, popped) ->
|
||||||
|
fn.addInsns(InsnNode(if (popped.stackSize == 2) Opcodes.POP2 else Opcodes.POP))
|
||||||
|
}
|
||||||
|
is Node.Instr.Select ->
|
||||||
|
applySelectInsn(ctx, fn)
|
||||||
|
is Node.Instr.GetLocal ->
|
||||||
|
applyGetLocal(ctx, f, fn, i.index)
|
||||||
|
is Node.Instr.SetLocal ->
|
||||||
|
applySetLocal(ctx, f, fn, i.index)
|
||||||
|
is Node.Instr.TeeLocal ->
|
||||||
|
applyTeeLocal(ctx, f, fn, i.index)
|
||||||
|
is Node.Instr.GetGlobal ->
|
||||||
|
applyGetGlobal(ctx, fn, i.index)
|
||||||
|
is Node.Instr.SetGlobal ->
|
||||||
|
applySetGlobal(ctx, fn, i.index)
|
||||||
|
else -> TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applySetGlobal(ctx: Context, fn: Func, index: Int) =
|
||||||
|
// Import is handled completeld differently than self
|
||||||
|
// TODO: check mutability?
|
||||||
|
if (index < ctx.importGlobals.size)
|
||||||
|
applyImportSetGlobal(ctx, fn, index, ctx.importGlobals[index].kind as Node.Import.Kind.Global)
|
||||||
|
else
|
||||||
|
applySelfSetGlobal(ctx, fn, index, ctx.mod.globals[ctx.importGlobals.size - index])
|
||||||
|
|
||||||
|
fun applySelfSetGlobal(ctx: Context, fn: Func, index: Int, global: Node.Global) =
|
||||||
|
// We have to swap "this" with the value on the stack
|
||||||
|
fn.addInsns(VarInsnNode(Opcodes.ALOAD, fn.lastParamLocalVarIndex)).
|
||||||
|
push(ctx.thisRef).
|
||||||
|
stackSwap().
|
||||||
|
popExpecting(global.type.contentType.typeRef).
|
||||||
|
addInsns(
|
||||||
|
FieldInsnNode(Opcodes.PUTFIELD, ctx.thisRef.asmName, globalName(index),
|
||||||
|
global.type.contentType.typeRef.asmDesc)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun applyImportSetGlobal(ctx: Context, fn: Func, index: Int, import: Node.Import.Kind.Global) =
|
||||||
|
// Load the setter method handle field, then invoke it with stack val
|
||||||
|
fn.popExpecting(import.type.contentType.typeRef).addInsns(
|
||||||
|
VarInsnNode(Opcodes.ALOAD, fn.lastParamLocalVarIndex),
|
||||||
|
FieldInsnNode(Opcodes.GETFIELD, ctx.thisRef.asmName,
|
||||||
|
importGlobalSetterFieldName(index), MethodHandle::class.ref.asmDesc),
|
||||||
|
MethodInsnNode(Opcodes.INVOKEVIRTUAL, MethodHandle::class.ref.asmName, "invokeExact",
|
||||||
|
"(${import.type.contentType.typeRef.asmDesc})V", false)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun applyGetGlobal(ctx: Context, fn: Func, index: Int) =
|
||||||
|
// Import is handled completely different than self
|
||||||
|
if (index < ctx.importGlobals.size)
|
||||||
|
applyImportGetGlobal(ctx, fn, index, ctx.importGlobals[index].kind as Node.Import.Kind.Global)
|
||||||
|
else
|
||||||
|
applySelfGetGlobal(ctx, fn, index, ctx.mod.globals[ctx.importGlobals.size - index])
|
||||||
|
|
||||||
|
fun applySelfGetGlobal(ctx: Context, fn: Func, index: Int, global: Node.Global) =
|
||||||
|
fn.addInsns(
|
||||||
|
VarInsnNode(Opcodes.ALOAD, fn.lastParamLocalVarIndex),
|
||||||
|
FieldInsnNode(Opcodes.GETFIELD, ctx.thisRef.asmName, globalName(index),
|
||||||
|
global.type.contentType.typeRef.asmDesc)
|
||||||
|
).push(global.type.contentType.typeRef)
|
||||||
|
|
||||||
|
fun applyImportGetGlobal(ctx: Context, fn: Func, index: Int, import: Node.Import.Kind.Global) =
|
||||||
|
// Load the getter method handle field, then invoke it with nothing
|
||||||
|
fn.addInsns(
|
||||||
|
VarInsnNode(Opcodes.ALOAD, fn.lastParamLocalVarIndex),
|
||||||
|
FieldInsnNode(Opcodes.GETFIELD, ctx.thisRef.asmName,
|
||||||
|
importGlobalGetterFieldName(index), MethodHandle::class.ref.asmDesc),
|
||||||
|
MethodInsnNode(Opcodes.INVOKEVIRTUAL, MethodHandle::class.ref.asmName, "invokeExact",
|
||||||
|
"()" + import.type.contentType.typeRef.asmDesc, false)
|
||||||
|
).push(import.type.contentType.typeRef)
|
||||||
|
|
||||||
|
fun applyTeeLocal(ctx: Context, f: Node.Func, fn: Func, index: Int) = f.locals[index].typeRef.let { typeRef ->
|
||||||
|
fn.addInsns(InsnNode(if (typeRef.stackSize == 2) Opcodes.DUP2 else Opcodes.DUP)).
|
||||||
|
push(typeRef).let { applySetLocal(ctx, f, it, index) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applySetLocal(ctx: Context, f: Node.Func, fn: Func, index: Int) =
|
||||||
|
fn.popExpecting(f.locals[index].typeRef).let { fn ->
|
||||||
|
when (f.locals[index]) {
|
||||||
|
Node.Type.Value.I32 -> fn.addInsns(VarInsnNode(Opcodes.ISTORE, f.actualLocalIndex(index)))
|
||||||
|
Node.Type.Value.I64 -> fn.addInsns(VarInsnNode(Opcodes.LSTORE, f.actualLocalIndex(index)))
|
||||||
|
Node.Type.Value.F32 -> fn.addInsns(VarInsnNode(Opcodes.FSTORE, f.actualLocalIndex(index)))
|
||||||
|
Node.Type.Value.F64 -> fn.addInsns(VarInsnNode(Opcodes.DSTORE, f.actualLocalIndex(index)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyGetLocal(ctx: Context, f: Node.Func, fn: Func, index: Int) = when (f.locals[index]) {
|
||||||
|
Node.Type.Value.I32 -> fn.addInsns(VarInsnNode(Opcodes.ILOAD, f.actualLocalIndex(index)))
|
||||||
|
Node.Type.Value.I64 -> fn.addInsns(VarInsnNode(Opcodes.LLOAD, f.actualLocalIndex(index)))
|
||||||
|
Node.Type.Value.F32 -> fn.addInsns(VarInsnNode(Opcodes.FLOAD, f.actualLocalIndex(index)))
|
||||||
|
Node.Type.Value.F64 -> fn.addInsns(VarInsnNode(Opcodes.DLOAD, f.actualLocalIndex(index)))
|
||||||
|
}.push(f.locals[index].typeRef)
|
||||||
|
|
||||||
|
fun applySelectInsn(ctx: Context, origFn: Func): Func {
|
||||||
|
var fn = origFn
|
||||||
|
// 3 things, first two must have same type, third is 0 check (0 means use second, otherwise use first)
|
||||||
|
// What we'll do is:
|
||||||
|
// IFNE third L1 (which means if it's non-zero, goto L1)
|
||||||
|
// SWAP (or the double-style swap) (which means second is now first, and first is now second)
|
||||||
|
// L1:
|
||||||
|
// POP (or pop2, remove the last)
|
||||||
|
|
||||||
|
// Check that we have an int for comparison, then the ref types
|
||||||
|
val (ref1, ref2) = fn.popExpecting(Int::class.ref).pop().let { (newFn, ref1) ->
|
||||||
|
newFn.pop().let { (newFn, ref2) -> fn = newFn; ref1 to ref2 }
|
||||||
|
}
|
||||||
|
require(ref1 == ref2) { "Select types do not match: $ref1 and $ref2" }
|
||||||
|
|
||||||
|
val lbl = LabelNode()
|
||||||
|
// Conditional jump
|
||||||
|
return fn.addInsns(JumpInsnNode(Opcodes.IFNE, lbl),
|
||||||
|
// Swap
|
||||||
|
InsnNode(if (ref1.stackSize == 2) Opcodes.DUP2 else Opcodes.SWAP),
|
||||||
|
// Label and pop
|
||||||
|
LabelNode(lbl.label),
|
||||||
|
InsnNode(if (ref1.stackSize == 2) Opcodes.POP2 else Opcodes.POP)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyCallInsn(ctx: Context, f: Node.Func, origFn: Func, index: Int, indirect: Boolean): Func {
|
||||||
|
// Check whether it's an import or local to get type
|
||||||
|
val funcType = ctx.importFuncs.getOrNull(index).let {
|
||||||
|
when (it) {
|
||||||
|
null -> ctx.mod.funcs.getOrNull(ctx.importFuncs.size - index)?.type
|
||||||
|
else -> (it.kind as? Node.Import.Kind.Func)?.typeIndex?.let(ctx.mod.types::getOrNull)
|
||||||
|
}
|
||||||
|
} ?: throw RuntimeException("Cannot find func at index $index")
|
||||||
|
// Check stack expectations
|
||||||
|
var fn = origFn.popExpectingMulti(funcType.params.map(Node.Type.Value::typeRef))
|
||||||
|
// Add "this" at the end and call statically
|
||||||
|
fn = fn.addInsns(
|
||||||
|
VarInsnNode(Opcodes.ALOAD, fn.lastParamLocalVarIndex),
|
||||||
|
MethodInsnNode(Opcodes.INVOKESTATIC, ctx.thisRef.asmName,
|
||||||
|
funcName(index), funcType.asmDesc, false)
|
||||||
|
)
|
||||||
|
// Return push on stack?
|
||||||
|
funcType.ret?.also { fn = fn.push(it.typeRef) }
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyReturnInsn(ctx: Context, f: Node.Func, fn: Func) = when (f.type.ret) {
|
||||||
|
null ->
|
||||||
|
fn.addInsns(InsnNode(Opcodes.RETURN))
|
||||||
|
Node.Type.Value.I32 ->
|
||||||
|
fn.popExpecting(Int::class.ref).addInsns(InsnNode(Opcodes.IRETURN))
|
||||||
|
Node.Type.Value.I64 ->
|
||||||
|
fn.popExpecting(Long::class.ref).addInsns(InsnNode(Opcodes.LRETURN))
|
||||||
|
Node.Type.Value.F32 ->
|
||||||
|
fn.popExpecting(Float::class.ref).addInsns(InsnNode(Opcodes.FRETURN))
|
||||||
|
Node.Type.Value.F64 ->
|
||||||
|
fn.popExpecting(Double::class.ref).addInsns(InsnNode(Opcodes.DRETURN))
|
||||||
|
}.let {
|
||||||
|
require(it.stack.isEmpty()) { "Stack not empty on void return" }
|
||||||
|
it
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Context(
|
||||||
|
val packageName: String,
|
||||||
|
val className: String,
|
||||||
|
val mod: Node.Module,
|
||||||
|
val cls: ClassNode,
|
||||||
|
val mem: Mem = ByteBufferMem()
|
||||||
|
) {
|
||||||
|
val importFuncs: List<Node.Import> by lazy { mod.imports.filter { it.kind is Node.Import.Kind.Func } }
|
||||||
|
val importGlobals: List<Node.Import> by lazy { mod.imports.filter { it.kind is Node.Import.Kind.Global } }
|
||||||
|
val thisRef = TypeRef(Type.getObjectType(packageName.replace('.', '/') + className))
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : AstToAsm()
|
||||||
|
}
|
@ -12,55 +12,54 @@ import kotlin.reflect.KClass
|
|||||||
import kotlin.reflect.KFunction
|
import kotlin.reflect.KFunction
|
||||||
import kotlin.reflect.jvm.reflect
|
import kotlin.reflect.jvm.reflect
|
||||||
|
|
||||||
open class ByteBufferMem(val direct: Boolean = true, val defaultMax: Int = 5 * Mem.PAGE_SIZE) : Mem<ByteBuffer> {
|
open class ByteBufferMem(val direct: Boolean = true) : Mem {
|
||||||
override val memClass = ByteBuffer::class.java
|
override val memType = ByteBuffer::class.ref
|
||||||
|
|
||||||
override fun create(func: Func, maximum: Int?) = func.addInsns(
|
override fun create(func: Func) = func.popExpecting(Int::class.ref).addInsns(
|
||||||
(maximum ?: defaultMax).const,
|
|
||||||
(if (direct) ByteBuffer::allocateDirect else ByteBuffer::allocate).invokeStatic()
|
(if (direct) ByteBuffer::allocateDirect else ByteBuffer::allocate).invokeStatic()
|
||||||
).push(memClass)
|
).push(memType)
|
||||||
|
|
||||||
override fun init(func: Func, initial: Int) = func.popExpecting(memClass).addInsns(
|
override fun init(func: Func, initial: Int) = func.popExpecting(memType).addInsns(
|
||||||
// Set the limit to initial
|
// Set the limit to initial
|
||||||
initial.const,
|
initial.const,
|
||||||
forceFnType<ByteBuffer.(Int) -> Buffer>(ByteBuffer::limit).invokeVirtual(),
|
forceFnType<ByteBuffer.(Int) -> Buffer>(ByteBuffer::limit).invokeVirtual(),
|
||||||
TypeInsnNode(Opcodes.CHECKCAST, ByteBuffer::class.asmName),
|
TypeInsnNode(Opcodes.CHECKCAST, ByteBuffer::class.ref.asmName),
|
||||||
// Set it to use little endian
|
// Set it to use little endian
|
||||||
ByteOrder::LITTLE_ENDIAN.getStatic(),
|
ByteOrder::LITTLE_ENDIAN.getStatic(),
|
||||||
forceFnType<ByteBuffer.(ByteOrder) -> ByteBuffer>(ByteBuffer::order).invokeVirtual()
|
forceFnType<ByteBuffer.(ByteOrder) -> ByteBuffer>(ByteBuffer::order).invokeVirtual()
|
||||||
).push(ByteBuffer::class.java)
|
).push(ByteBuffer::class.ref)
|
||||||
|
|
||||||
override fun data(func: Func, bytes: ByteArray, buildOffset: (Func) -> Func) = func.
|
override fun data(func: Func, bytes: ByteArray, buildOffset: (Func) -> Func) = func.
|
||||||
popExpecting(memClass).
|
popExpecting(memType).
|
||||||
addInsns(
|
addInsns(
|
||||||
bytes.size.const,
|
bytes.size.const,
|
||||||
IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_BYTE),
|
IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_BYTE)
|
||||||
*bytes.withIndex().flatMap { (index, byte) ->
|
|
||||||
listOf(InsnNode(Opcodes.DUP), index.const, byte.toInt().const)
|
|
||||||
}.toTypedArray()
|
|
||||||
).
|
).
|
||||||
apply(buildOffset).popExpecting(Int::class.java).
|
addInsns(bytes.withIndex().flatMap { (index, byte) ->
|
||||||
|
listOf(InsnNode(Opcodes.DUP), index.const, byte.toInt().const)
|
||||||
|
}).
|
||||||
|
apply(buildOffset).popExpecting(Int::class.ref).
|
||||||
// BOO! https://discuss.kotlinlang.org/t/overload-resolution-ambiguity-function-reference-requiring-local-var/2425
|
// BOO! https://discuss.kotlinlang.org/t/overload-resolution-ambiguity-function-reference-requiring-local-var/2425
|
||||||
addInsns(forceFnType<ByteBuffer.(ByteArray, Int, Int) -> ByteBuffer>(ByteBuffer::put).invokeVirtual()).
|
addInsns(forceFnType<ByteBuffer.(ByteArray, Int, Int) -> ByteBuffer>(ByteBuffer::put).invokeVirtual()).
|
||||||
push(memClass)
|
push(memType)
|
||||||
|
|
||||||
override fun currentMemory(func: Func) = func.popExpecting(memClass).addInsns(
|
override fun currentMemory(func: Func) = func.popExpecting(memType).addInsns(
|
||||||
forceFnType<ByteBuffer.() -> Int>(ByteBuffer::limit).invokeVirtual(),
|
forceFnType<ByteBuffer.() -> Int>(ByteBuffer::limit).invokeVirtual(),
|
||||||
Mem.PAGE_SIZE.const,
|
Mem.PAGE_SIZE.const,
|
||||||
InsnNode(Opcodes.IDIV)
|
InsnNode(Opcodes.IDIV)
|
||||||
).push(Int::class.java)
|
).push(Int::class.ref)
|
||||||
|
|
||||||
override fun growMemory(func: Func) = func.popExpecting(memClass).popExpecting(Int::class.java).addInsns(
|
override fun growMemory(func: Func) = func.popExpecting(memType).popExpecting(Int::class.ref).addInsns(
|
||||||
Mem.PAGE_SIZE.const,
|
Mem.PAGE_SIZE.const,
|
||||||
// TODO: overflow check, e.g. Math.multiplyExact
|
// TODO: overflow check, e.g. Math.multiplyExact
|
||||||
InsnNode(Opcodes.IMUL),
|
InsnNode(Opcodes.IMUL),
|
||||||
forceFnType<ByteBuffer.(Int) -> Buffer>(ByteBuffer::limit).invokeVirtual()
|
forceFnType<ByteBuffer.(Int) -> Buffer>(ByteBuffer::limit).invokeVirtual()
|
||||||
).push(ByteBuffer::class.java)
|
).push(ByteBuffer::class.ref)
|
||||||
|
|
||||||
override fun loadOp(func: Func, insn: Node.Instr.Args.AlignOffset) = func.popExpecting(memClass).let { func ->
|
override fun loadOp(func: Func, insn: Node.Instr.Args.AlignOffset) = func.popExpecting(memType).let { func ->
|
||||||
require(insn.offset <= Int.MAX_VALUE, { "Offsets > ${Int.MAX_VALUE} unsupported" }).let { this }
|
require(insn.offset <= Int.MAX_VALUE, { "Offsets > ${Int.MAX_VALUE} unsupported" }).let { this }
|
||||||
fun Func.load(fn: ByteBuffer.(Int) -> Any, retClass: KClass<*>) =
|
fun Func.load(fn: ByteBuffer.(Int) -> Any, retClass: KClass<*>) =
|
||||||
this.addInsns(insn.offset.toInt().const, fn.reflect()!!.invokeVirtual()).push(retClass.java)
|
this.addInsns(insn.offset.toInt().const, fn.reflect()!!.invokeVirtual()).push(retClass.ref)
|
||||||
fun Func.loadI32(fn: ByteBuffer.(Int) -> Any) =
|
fun Func.loadI32(fn: ByteBuffer.(Int) -> Any) =
|
||||||
this.load(fn, Int::class)
|
this.load(fn, Int::class)
|
||||||
fun Func.loadI64(fn: ByteBuffer.(Int) -> Any) =
|
fun Func.loadI64(fn: ByteBuffer.(Int) -> Any) =
|
||||||
@ -68,9 +67,9 @@ open class ByteBufferMem(val direct: Boolean = true, val defaultMax: Int = 5 * M
|
|||||||
fun Func.toUnsigned(fn: KFunction<*>) =
|
fun Func.toUnsigned(fn: KFunction<*>) =
|
||||||
this.addInsns(fn.invokeVirtual())
|
this.addInsns(fn.invokeVirtual())
|
||||||
fun Func.toUnsigned64(fn: KFunction<*>) =
|
fun Func.toUnsigned64(fn: KFunction<*>) =
|
||||||
this.popExpecting(Int::class.java).toUnsigned(fn).push(Long::class.java)
|
this.popExpecting(Int::class.ref).toUnsigned(fn).push(Long::class.ref)
|
||||||
fun Func.i32ToI64() =
|
fun Func.i32ToI64() =
|
||||||
this.popExpecting(Int::class.java).addInsns(InsnNode(Opcodes.I2L)).push(Long::class.java)
|
this.popExpecting(Int::class.ref).addInsns(InsnNode(Opcodes.I2L)).push(Long::class.ref)
|
||||||
when (insn) {
|
when (insn) {
|
||||||
is Node.Instr.I32Load ->
|
is Node.Instr.I32Load ->
|
||||||
func.loadI32(ByteBuffer::getInt)
|
func.loadI32(ByteBuffer::getInt)
|
||||||
@ -104,47 +103,40 @@ open class ByteBufferMem(val direct: Boolean = true, val defaultMax: Int = 5 * M
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun storeOp(func: Func, insn: Node.Instr.Args.AlignOffset) = func.popExpecting(memClass).let { func ->
|
override fun storeOp(func: Func, insn: Node.Instr.Args.AlignOffset) = func.popExpecting(memType).let { func ->
|
||||||
require(insn.offset <= Int.MAX_VALUE, { "Offsets > ${Int.MAX_VALUE} unsupported" }).let { this }
|
require(insn.offset <= Int.MAX_VALUE, { "Offsets > ${Int.MAX_VALUE} unsupported" }).let { this }
|
||||||
fun <T> Func.store32(fn: ByteBuffer.(Int, T) -> ByteBuffer, inClass: KClass<*>) =
|
fun <T> Func.store(fn: ByteBuffer.(Int, T) -> ByteBuffer, inClass: KClass<*>) =
|
||||||
// We add the index and then swap with the value already on the stack
|
// We add the index and then swap with the value already on the stack
|
||||||
this.popExpecting(inClass.java).addInsns(
|
this.addInsns(insn.offset.toInt().const).
|
||||||
insn.offset.toInt().const,
|
push(Int::class.ref).
|
||||||
InsnNode(Opcodes.SWAP),
|
stackSwap().
|
||||||
fn.reflect()!!.invokeVirtual()
|
popExpecting(inClass.ref).
|
||||||
).push(ByteBuffer::class.java)
|
addInsns(fn.reflect()!!.invokeVirtual()).
|
||||||
fun <T> Func.store64(fn: ByteBuffer.(Int, T) -> ByteBuffer, inClass: KClass<*>) =
|
push(ByteBuffer::class.ref)
|
||||||
// We add the offset, dup_x2 to swap, and pop to get rid of the dup offset
|
|
||||||
this.popExpecting(inClass.java).addInsns(
|
|
||||||
insn.offset.toInt().const,
|
|
||||||
InsnNode(Opcodes.DUP_X2),
|
|
||||||
InsnNode(Opcodes.POP),
|
|
||||||
fn.reflect()!!.invokeVirtual()
|
|
||||||
).push(ByteBuffer::class.java)
|
|
||||||
fun Func.changeI64ToI32() =
|
fun Func.changeI64ToI32() =
|
||||||
this.popExpecting(Long::class.java).push(Int::class.java)
|
this.popExpecting(Long::class.ref).push(Int::class.ref)
|
||||||
when (insn) {
|
when (insn) {
|
||||||
is Node.Instr.I32Store ->
|
is Node.Instr.I32Store ->
|
||||||
func.store32(ByteBuffer::putInt, Int::class)
|
func.store(ByteBuffer::putInt, Int::class)
|
||||||
is Node.Instr.I64Store ->
|
is Node.Instr.I64Store ->
|
||||||
func.store64(ByteBuffer::putLong, Long::class)
|
func.store(ByteBuffer::putLong, Long::class)
|
||||||
is Node.Instr.F32Store ->
|
is Node.Instr.F32Store ->
|
||||||
func.store32(ByteBuffer::putFloat, Float::class)
|
func.store(ByteBuffer::putFloat, Float::class)
|
||||||
is Node.Instr.F64Store ->
|
is Node.Instr.F64Store ->
|
||||||
func.store64(ByteBuffer::putDouble, Double::class)
|
func.store(ByteBuffer::putDouble, Double::class)
|
||||||
is Node.Instr.I32Store8 ->
|
is Node.Instr.I32Store8 ->
|
||||||
func.addInsns(InsnNode(Opcodes.I2B)).store32(ByteBuffer::put, Int::class)
|
func.addInsns(InsnNode(Opcodes.I2B)).store(ByteBuffer::put, Int::class)
|
||||||
is Node.Instr.I32Store16 ->
|
is Node.Instr.I32Store16 ->
|
||||||
func.addInsns(InsnNode(Opcodes.I2S)).store32(ByteBuffer::putShort, Int::class)
|
func.addInsns(InsnNode(Opcodes.I2S)).store(ByteBuffer::putShort, Int::class)
|
||||||
is Node.Instr.I64Store8 ->
|
is Node.Instr.I64Store8 ->
|
||||||
func.addInsns(InsnNode(Opcodes.L2I), InsnNode(Opcodes.I2B)).
|
func.addInsns(InsnNode(Opcodes.L2I), InsnNode(Opcodes.I2B)).
|
||||||
changeI64ToI32().store32(ByteBuffer::put, Int::class)
|
changeI64ToI32().store(ByteBuffer::put, Int::class)
|
||||||
is Node.Instr.I64Store16 ->
|
is Node.Instr.I64Store16 ->
|
||||||
func.addInsns(InsnNode(Opcodes.L2I), InsnNode(Opcodes.I2S)).
|
func.addInsns(InsnNode(Opcodes.L2I), InsnNode(Opcodes.I2S)).
|
||||||
changeI64ToI32().store32(ByteBuffer::putShort, Int::class)
|
changeI64ToI32().store(ByteBuffer::putShort, Int::class)
|
||||||
is Node.Instr.I64Store32 ->
|
is Node.Instr.I64Store32 ->
|
||||||
func.addInsns(InsnNode(Opcodes.L2I)).
|
func.addInsns(InsnNode(Opcodes.L2I)).
|
||||||
changeI64ToI32().store32(ByteBuffer::putInt, Int::class)
|
changeI64ToI32().store(ByteBuffer::putInt, Int::class)
|
||||||
else -> throw IllegalArgumentException("Unknown store op $insn")
|
else -> throw IllegalArgumentException("Unknown store op $insn")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,78 @@
|
|||||||
package asmble.compile.jvm
|
package asmble.compile.jvm
|
||||||
|
|
||||||
import asmble.ast.Node
|
import org.objectweb.asm.Opcodes
|
||||||
|
import org.objectweb.asm.Type
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode
|
import org.objectweb.asm.tree.AbstractInsnNode
|
||||||
|
import org.objectweb.asm.tree.InsnNode
|
||||||
|
import org.objectweb.asm.tree.MethodNode
|
||||||
|
|
||||||
data class Func(val insns: List<AbstractInsnNode>, val stack: List<Class<*>>) {
|
data class Func(
|
||||||
|
val name: String,
|
||||||
|
val params: List<TypeRef> = emptyList(),
|
||||||
|
val ret: TypeRef = Void::class.ref,
|
||||||
|
val access: Int = Opcodes.ACC_PUBLIC,
|
||||||
|
val insns: List<AbstractInsnNode> = emptyList(),
|
||||||
|
val stack: List<TypeRef> = emptyList()
|
||||||
|
) {
|
||||||
|
|
||||||
|
val desc: String get() = ret.asMethodRetDesc(*params.toTypedArray())
|
||||||
|
|
||||||
|
val lastParamLocalVarIndex: Int get() =
|
||||||
|
params.dropLast(1).fold(if (access.isAccessStatic) 1 else 2) { total, param -> total + param.stackSize }
|
||||||
|
|
||||||
|
fun addInsns(insns: List<AbstractInsnNode>) = copy(insns = this.insns + insns)
|
||||||
|
|
||||||
fun addInsns(vararg insns: AbstractInsnNode) = copy(insns = this.insns + insns)
|
fun addInsns(vararg insns: AbstractInsnNode) = copy(insns = this.insns + insns)
|
||||||
|
|
||||||
fun apply(fn: (Func) -> Func) = fn(this)
|
fun apply(fn: (Func) -> Func) = fn(this)
|
||||||
|
|
||||||
fun push(vararg types: Class<*>) = copy(stack = stack + types)
|
fun push(vararg types: TypeRef) = copy(stack = stack + types)
|
||||||
|
|
||||||
fun popExpectingNum() = popExpecting(Int::class.java, Long::class.java, Float::class.java, Double::class.java)
|
fun popExpectingMulti(types: List<TypeRef>) = types.reversed().fold(this, Func::popExpecting)
|
||||||
|
|
||||||
fun popExpecting(vararg types: Class<*>) = popExpectingAny(types::contains)
|
fun popExpectingMulti(vararg types: TypeRef) = types.reversed().fold(this, Func::popExpecting)
|
||||||
|
|
||||||
fun popExpectingAny(pred: (Class<*>) -> Boolean): Func {
|
fun popExpecting(type: TypeRef) = popExpectingAny(type)
|
||||||
|
|
||||||
|
fun popExpectingNum() = popExpectingAny(Int::class.ref, Long::class.ref, Float::class.ref, Double::class.ref)
|
||||||
|
|
||||||
|
fun popExpectingAny(vararg types: TypeRef) = popExpectingAny(types::contains)
|
||||||
|
|
||||||
|
fun popExpectingAny(pred: (TypeRef) -> Boolean): Func {
|
||||||
|
stack.lastOrNull()?.let { require(pred(it)) { "Stack var type ${stack.last()} unexpected" } }
|
||||||
|
return pop().first
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pop(): Pair<Func, TypeRef> {
|
||||||
require(stack.isNotEmpty(), { "Stack is empty" })
|
require(stack.isNotEmpty(), { "Stack is empty" })
|
||||||
require(pred(stack.last()), { "Stack var type ${stack.last()} unexpected" })
|
return copy(stack = stack.dropLast(1)) to stack.last()
|
||||||
return copy(stack = stack.dropLast(1))
|
}
|
||||||
|
|
||||||
|
fun toMethodNode(): MethodNode {
|
||||||
|
require(stack.isEmpty(), { "Stack not empty for $name when compiling" })
|
||||||
|
require(insns.lastOrNull()?.isTerminating ?: false, { "Last insn for $name is not terminating" })
|
||||||
|
val ret = MethodNode(access, name, desc, null, null)
|
||||||
|
insns.forEach(ret.instructions::add)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stackSwap() = pop().let { (fn, refLast) ->
|
||||||
|
fn.pop().let { (fn, refFirst) ->
|
||||||
|
if (refFirst.stackSize == 2) {
|
||||||
|
if (refLast.stackSize == 2)
|
||||||
|
// If they are both 2, dup2_x2 + pop2
|
||||||
|
fn.addInsns(InsnNode(Opcodes.DUP2_X2), InsnNode(Opcodes.POP))
|
||||||
|
else
|
||||||
|
// If only the first one is, dup_x2 + pop
|
||||||
|
fn.addInsns(InsnNode(Opcodes.DUP_X2), InsnNode(Opcodes.POP))
|
||||||
|
} else {
|
||||||
|
if (refLast.stackSize == 2)
|
||||||
|
// If the first is not 2 but the last is, dup_2x1, pop2
|
||||||
|
fn.addInsns(InsnNode(Opcodes.DUP2_X1), InsnNode(Opcodes.POP2))
|
||||||
|
else
|
||||||
|
// If neither are 2, just swap
|
||||||
|
fn.addInsns(InsnNode(Opcodes.SWAP))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,11 +2,13 @@ package asmble.compile.jvm
|
|||||||
|
|
||||||
import asmble.ast.Node
|
import asmble.ast.Node
|
||||||
|
|
||||||
interface Mem<T : Any> {
|
interface Mem {
|
||||||
// The class to accept as "memory"
|
// The class to accept as "memory"
|
||||||
val memClass: Class<T>
|
val memType: TypeRef
|
||||||
|
|
||||||
fun create(func: Func, maximum: Int?): Func
|
// Caller can trust the max is on the stack as an i32. The result
|
||||||
|
// must be put on the stack
|
||||||
|
fun create(func: Func): Func
|
||||||
|
|
||||||
// Caller can trust the mem instance is on the stack and must handle it. If
|
// Caller can trust the mem instance is on the stack and must handle it. If
|
||||||
// it's already there after call anyways, this can leave the mem inst on the
|
// it's already there after call anyways, this can leave the mem inst on the
|
||||||
|
12
src/main/kotlin/asmble/compile/jvm/TypeRef.kt
Normal file
12
src/main/kotlin/asmble/compile/jvm/TypeRef.kt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package asmble.compile.jvm
|
||||||
|
|
||||||
|
import org.objectweb.asm.Type
|
||||||
|
|
||||||
|
data class TypeRef(val asm: Type) {
|
||||||
|
val asmName: String get() = asm.internalName
|
||||||
|
val asmDesc: String get() = asm.descriptor
|
||||||
|
|
||||||
|
fun asMethodRetDesc(vararg args: TypeRef) = Type.getMethodDescriptor(asm, *args.map { it.asm }.toTypedArray())
|
||||||
|
|
||||||
|
val stackSize: Int get() = if (asm == Type.DOUBLE_TYPE || asm == Type.LONG_TYPE) 2 else 1
|
||||||
|
}
|
@ -7,5 +7,3 @@ fun <T : Any> Collection<T?>.takeUntilNull(): List<T> {
|
|||||||
fun <T : Any, R : Any> Collection<T>.takeUntilNullLazy(map: (T) -> R?): List<R> {
|
fun <T : Any, R : Any> Collection<T>.takeUntilNullLazy(map: (T) -> R?): List<R> {
|
||||||
return this.asSequence().map(map).takeUntilNull().toList()
|
return this.asSequence().map(map).takeUntilNull().toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user