Files
JCIL/CLI/AssemblyBuilder.cs
T
2026-05-08 14:54:47 +02:00

1268 lines
34 KiB
C#
Executable File

using System.Collections.Frozen;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using JCIL.CLI.Types;
using JCIL.CLI.Types.Java.Lang;
using JCIL.Core;
using JCIL.Java.Class;
using Array = System.Array;
using Boolean = JCIL.CLI.Types.Java.Lang.Boolean;
using Byte = JCIL.CLI.Types.Java.Lang.Byte;
using Char = JCIL.CLI.Types.Java.Lang.Char;
using Double = JCIL.CLI.Types.Java.Lang.Double;
using Object = JCIL.CLI.Types.Java.Lang.Object;
using OpCode = JCIL.Java.Class.OpCode;
using String = JCIL.CLI.Types.Java.Lang.String;
using Void = JCIL.CLI.Types.Java.Lang.Void;
namespace JCIL.CLI;
public sealed class AssemblyBuilder
{
public readonly System.Reflection.Emit.AssemblyBuilder Assembly;
public readonly ModuleBuilder Module;
private readonly Dictionary<Type, FrozenSet<Type>> _casts = [];
private readonly Dictionary<Class, TypeRepresentation> _classes = [];
internal readonly TypeLoader TypeLoader;
public int CompiledMethods { get; private set; }
public AssemblyBuilder(TypeLoader typeLoader, AssemblyName name, bool runnable)
{
TypeLoader = typeLoader;
Assembly = runnable
? System.Reflection.Emit.AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndCollect)
: new PersistedAssemblyBuilder(name, typeof(int).Assembly);
Module = Assembly.DefineDynamicModule("JCIL_Output");
}
public TypeRepresentation MakeType(Class javaClass)
{
lock (_classes)
{
if (_classes.TryGetValue(javaClass, out var type))
return type;
switch (javaClass.SpecialClassMetadata)
{
case IntrinsicMetadata intrinsic:
{
switch (intrinsic.Type)
{
case IntrinsicType.Byte: return _classes[javaClass] = new Byte(this, javaClass);
case IntrinsicType.Char: return _classes[javaClass] = new Char(this, javaClass);
case IntrinsicType.Int: return _classes[javaClass] = new Integer(this, javaClass);
case IntrinsicType.Short: return _classes[javaClass] = new Short(this, javaClass);
case IntrinsicType.Long: return _classes[javaClass] = new Long(this, javaClass);
case IntrinsicType.Boolean: return _classes[javaClass] = new Boolean(this, javaClass);
case IntrinsicType.Float: return _classes[javaClass] = new Float(this, javaClass);
case IntrinsicType.Double: return _classes[javaClass] = new Double(this, javaClass);
case IntrinsicType.Void: return _classes[javaClass] = new Void(this, javaClass);
default: throw new NotImplementedException($"Unimplemented Intrinsic {intrinsic.Type}");
}
}
case ArrayMetadata array:
return _classes[javaClass] = new ArrayOf(this, MakeType(array.ElementClass), javaClass);
}
if (javaClass.Namespace.Span is "java/lang")
{
if (javaClass.Name.Span is "String")
type = _classes[javaClass] = new String(this, javaClass);
if (javaClass.Name.Span is "Object")
type = _classes[javaClass] = new Object(this, javaClass);
if (type is not null)
{
_casts[type.Type] = CollectTypeCasts(javaClass);
return type;
}
}
lock (_casts)
{
TypeAttributes flags = default;
if ((javaClass.AccessFlags & ClassAccessFlags.Public) != 0) flags |= TypeAttributes.Public;
if ((javaClass.AccessFlags & ClassAccessFlags.Abstract) != 0) flags |= TypeAttributes.Abstract;
if ((javaClass.AccessFlags & ClassAccessFlags.Final) != 0) flags |= TypeAttributes.Sealed;
if ((javaClass.AccessFlags & ClassAccessFlags.Interface) != 0) flags |= TypeAttributes.Interface;
var name = javaClass.Namespace.IsEmpty
? javaClass.Name.ToString()
: $"{javaClass.Namespace.ToString().Replace('/', '.')}.{javaClass.Name}";
var builder = Module.DefineType(name, flags);
var newClass = new NewClass(this, builder, javaClass);
_classes[javaClass] = newClass;
_casts[builder] = CollectTypeCasts(javaClass);
if (javaClass.SuperClass is {} super)
builder.SetParent(MakeType(super).Type);
foreach (var javaInterface in javaClass.Interfaces)
builder.AddInterfaceImplementation(MakeType(javaInterface).Type);
newClass.CreateFields();
return newClass;
}
}
}
internal void CompileOpCodes(Class javaClass, MethodBase methodBase, ILGenerator il, Code code)
{
using var opcodes = code.CreateOpCodeReader();
var thisType = methodBase.DeclaringType!;
if (methodBase is MethodBuilder methodBuilder)
{
var ret = methodBuilder.ReturnType;
var par = methodBuilder.GetParameters().Select(v => v.ParameterType.Name);
var stc = methodBase.IsStatic ? "static " : string.Empty;
Debug.Writer.WriteLine($"Compiling `{stc}{ret.Name} {thisType.FullName}.{methodBase.Name}({string.Join(", ", par)})`");
}
else
{
var par = methodBase.GetParameters().Select(v => v.ParameterType.Name);
Debug.Writer.WriteLine($"Compiling `{thisType.FullName}({string.Join(", ", par)})`");
}
Debug.Writer.Indent++;
var labels = CreateLabels(il, opcodes);
var position = opcodes.Reader.Stream.Position;
var stack = new Stack<Value>(code.MaxStack);
var locals = new Value[code.MaxLocals];
var localCount = 0;
int AssignLocal(int idx, Value value)
{
if (value.Type == typeof(long) || value.Type == typeof(double))
{
locals[idx] = value;
locals[idx + 1] = value;
return 2;
}
locals[idx] = value;
return 1;
}
{
var idx = 0;
if (!methodBase.IsStatic)
idx += AssignLocal(idx, Value.Parameter(thisType, idx));
foreach (var parameter in methodBase.GetParameters())
idx += AssignLocal(idx, Value.Parameter(parameter.ParameterType, idx));
}
void CompileOpCode(long position, OpCode opcode)
{
Debug.Writer.WriteLine($"IL_{position:X04}: {opcode.ToString(javaClass)}");
Label JmpLabel()
{
return labels[position + (short)opcode.P0.UShort];
}
switch (opcode.Mnemonic)
{
case OpCodeMnemonic.AConstNull:
stack.Push(Value.Null);
il.Emit(OpCodes.Ldnull);
break;
case OpCodeMnemonic.IConst0:
case OpCodeMnemonic.IConst1:
case OpCodeMnemonic.IConst2:
case OpCodeMnemonic.IConst3:
case OpCodeMnemonic.IConst4:
case OpCodeMnemonic.IConst5:
stack.Push(new Value(opcode.Mnemonic - OpCodeMnemonic.IConst0).Load(il));
break;
case OpCodeMnemonic.IConstM1: stack.Push(new Value(-1).Load(il)); break;
case OpCodeMnemonic.LConst0: stack.Push(new Value(0L).Load(il)); break;
case OpCodeMnemonic.LConst1: stack.Push(new Value(1L).Load(il)); break;
case OpCodeMnemonic.FConst0: stack.Push(new Value(0f).Load(il)); break;
case OpCodeMnemonic.FConst1: stack.Push(new Value(1f).Load(il)); break;
case OpCodeMnemonic.FConst2: stack.Push(new Value(2f).Load(il)); break;
case OpCodeMnemonic.DConst0: stack.Push(new Value(0d).Load(il)); break;
case OpCodeMnemonic.DConst1: stack.Push(new Value(1d).Load(il)); break;
case OpCodeMnemonic.PushByte: stack.Push(new Value(opcode.P0.Byte).Load(il)); break;
case OpCodeMnemonic.PushShort: stack.Push(new Value((short)opcode.P0.UShort).Load(il)); break;
case OpCodeMnemonic.Ldc:
case OpCodeMnemonic.LdcW:
case OpCodeMnemonic.Ldc2W:
{
var constant = javaClass.GetConstant<object>(opcode.P0.UShort);
switch (constant)
{
case int val: stack.Push(new Value(val).Load(il)); break;
case long val: stack.Push(new Value(val).Load(il)); break;
case float val: stack.Push(new Value(val).Load(il)); break;
case double val: stack.Push(new Value(val).Load(il)); break;
case string val: stack.Push(new Value(val).Load(il)); break;
case Class val: stack.Push(Value.TypeRef(MakeType(val).Type).Load(il)); break;
default:
{
throw new NotImplementedException(
$"Unknown opcode: {opcode.Mnemonic} {constant.GetType()}");
}
}
break;
}
case OpCodeMnemonic.ILoad:
case OpCodeMnemonic.ILoad0:
case OpCodeMnemonic.ILoad1:
case OpCodeMnemonic.ILoad2:
case OpCodeMnemonic.ILoad3:
{
var idx = opcode.Mnemonic switch
{
OpCodeMnemonic.ILoad0 => 0,
OpCodeMnemonic.ILoad1 => 1,
OpCodeMnemonic.ILoad2 => 2,
OpCodeMnemonic.ILoad3 => 3,
OpCodeMnemonic.ILoad => opcode.P0.UShort,
_ => -1,
};
ref var loc = ref locals[idx];
AssertCast(loc, typeof(int), il);
stack.Push(loc.Load(il));
break;
}
case OpCodeMnemonic.LLoad:
case OpCodeMnemonic.LLoad0:
case OpCodeMnemonic.LLoad1:
case OpCodeMnemonic.LLoad2:
case OpCodeMnemonic.LLoad3:
{
var idx = opcode.Mnemonic switch
{
OpCodeMnemonic.LLoad0 => 0,
OpCodeMnemonic.LLoad1 => 1,
OpCodeMnemonic.LLoad2 => 2,
OpCodeMnemonic.LLoad3 => 3,
OpCodeMnemonic.LLoad => opcode.P0.UShort,
_ => -1,
};
ref var loc = ref locals[idx];
AssertCast(loc, typeof(long), il);
stack.Push(loc.Load(il));
break;
}
case OpCodeMnemonic.FLoad:
case OpCodeMnemonic.FLoad0:
case OpCodeMnemonic.FLoad1:
case OpCodeMnemonic.FLoad2:
case OpCodeMnemonic.FLoad3:
{
var idx = opcode.Mnemonic switch
{
OpCodeMnemonic.FLoad0 => 0,
OpCodeMnemonic.FLoad1 => 1,
OpCodeMnemonic.FLoad2 => 2,
OpCodeMnemonic.FLoad3 => 3,
OpCodeMnemonic.FLoad => opcode.P0.UShort,
_ => -1,
};
ref var loc = ref locals[idx];
AssertCast(loc, typeof(float), il);
stack.Push(loc.Load(il));
break;
}
case OpCodeMnemonic.ALoad:
case OpCodeMnemonic.ALoad0:
case OpCodeMnemonic.ALoad1:
case OpCodeMnemonic.ALoad2:
case OpCodeMnemonic.ALoad3:
{
var idx = opcode.Mnemonic switch
{
OpCodeMnemonic.ALoad0 => 0,
OpCodeMnemonic.ALoad1 => 1,
OpCodeMnemonic.ALoad2 => 2,
OpCodeMnemonic.ALoad3 => 3,
OpCodeMnemonic.ALoad => opcode.P0.UShort,
_ => -1,
};
if (locals[idx].Type is { IsClass: false, IsInterface: false })
throw new InvalidOperationException($"Local {idx} ({locals[idx].Type}) is not a class.");
stack.Push(locals[idx].Load(il));
break;
}
case OpCodeMnemonic.IALoad:
case OpCodeMnemonic.BALoad:
case OpCodeMnemonic.AALoad:
{
var idx = stack.Pop();
var array = stack.Pop();
AssertIntCompatible(idx);
AssertArray(array.Type);
var elementType = array.Type.GetElementType()!;
switch (opcode.Mnemonic)
{
case OpCodeMnemonic.IALoad:
if (elementType != typeof(int) && elementType != typeof(int))
throw new InvalidOperationException($"Expected int, found `{elementType}`.");
il.Emit(OpCodes.Ldelem_I4);
break;
case OpCodeMnemonic.BALoad:
if (elementType != typeof(byte) && elementType != typeof(bool))
throw new InvalidOperationException($"Expected byte or boolean, found `{elementType}`.");
il.Emit(OpCodes.Ldelem_I1);
break;
case OpCodeMnemonic.AALoad:
if (!elementType.IsClass)
throw new InvalidOperationException($"Expected object, found `{elementType}`.");
il.Emit(OpCodes.Ldelem_Ref, elementType);
break;
}
stack.Push(new Value(elementType));
break;
}
case OpCodeMnemonic.IStore:
case OpCodeMnemonic.IStore0:
case OpCodeMnemonic.IStore1:
case OpCodeMnemonic.IStore2:
case OpCodeMnemonic.IStore3:
{
var value = stack.Pop();
AssertCast(value, typeof(int), il);
var idx = opcode.Mnemonic switch
{
OpCodeMnemonic.IStore => opcode.P0.UShort,
_ => opcode.Mnemonic - OpCodeMnemonic.IStore0,
};
ref var loc = ref locals[idx];
if (loc.Type != typeof(int))
{
il.DeclareLocal(typeof(int));
AssignLocal(idx, Value.Local(typeof(int), localCount++));
}
loc.Store(il);
break;
}
case OpCodeMnemonic.LStore:
case OpCodeMnemonic.LStore0:
case OpCodeMnemonic.LStore1:
case OpCodeMnemonic.LStore2:
case OpCodeMnemonic.LStore3:
{
var value = stack.Pop();
AssertCast(value, typeof(long), il);
var idx = opcode.Mnemonic switch
{
OpCodeMnemonic.LStore => opcode.P0.UShort,
_ => opcode.Mnemonic - OpCodeMnemonic.LStore0,
};
ref var loc = ref locals[idx];
if (loc.Type != typeof(long))
{
il.DeclareLocal(typeof(long));
AssignLocal(idx, Value.Local(typeof(long), localCount++));
}
loc.Store(il);
break;
}
case OpCodeMnemonic.AStore:
case OpCodeMnemonic.AStore0:
case OpCodeMnemonic.AStore1:
case OpCodeMnemonic.AStore2:
case OpCodeMnemonic.AStore3:
{
var value = stack.Pop();
AssertCast(value, typeof(object), il);
var idx = opcode.Mnemonic switch
{
OpCodeMnemonic.AStore => opcode.P0.UShort,
_ => opcode.Mnemonic - OpCodeMnemonic.AStore0,
};
ref var loc = ref locals[idx];
if (!value.Type.IsAssignableTo(loc.Type))
{
il.DeclareLocal(value.Type);
AssignLocal(idx, Value.Local(value.Type, localCount++));
}
loc.Store(il);
break;
}
case OpCodeMnemonic.IAStore:
case OpCodeMnemonic.BAStore:
case OpCodeMnemonic.AAStore:
{
var value = stack.Pop();
var idx = stack.Pop();
var array = stack.Pop();
AssertArray(array);
AssertIntCompatible(idx);
var eType = array.Type.GetElementType()!;
AssertCast(value, eType, il);
il.Emit(opcode.Mnemonic switch
{
OpCodeMnemonic.BAStore => OpCodes.Stelem_I1,
OpCodeMnemonic.IAStore => OpCodes.Stelem_I4,
OpCodeMnemonic.AAStore => OpCodes.Stelem_Ref,
_ => throw new UnreachableException(),
});
break;
}
case OpCodeMnemonic.Pop:
{
stack.Pop();
il.Emit(OpCodes.Pop);
break;
}
case OpCodeMnemonic.Dup:
{
var value = stack.Pop();
stack.Push(value);
stack.Push(value);
il.Emit(OpCodes.Dup);
break;
}
case OpCodeMnemonic.IAdd:
case OpCodeMnemonic.ISub:
case OpCodeMnemonic.IMul:
case OpCodeMnemonic.IDiv:
case OpCodeMnemonic.IRem:
case OpCodeMnemonic.IOr:
case OpCodeMnemonic.IAnd:
case OpCodeMnemonic.IXor:
case OpCodeMnemonic.IShl:
case OpCodeMnemonic.IShr:
case OpCodeMnemonic.IUShr:
{
var a = stack.Pop();
var b = stack.Pop();
AssertIntCompatible(a.Type);
AssertIntCompatible(b.Type);
il.Emit(opcode.Mnemonic switch
{
OpCodeMnemonic.IAdd => OpCodes.Add,
OpCodeMnemonic.ISub => OpCodes.Sub,
OpCodeMnemonic.IMul => OpCodes.Mul,
OpCodeMnemonic.IDiv => OpCodes.Div,
OpCodeMnemonic.IRem => OpCodes.Rem,
OpCodeMnemonic.IOr => OpCodes.Or,
OpCodeMnemonic.IAnd => OpCodes.And,
OpCodeMnemonic.IXor => OpCodes.Xor,
OpCodeMnemonic.IShl => OpCodes.Shl,
OpCodeMnemonic.IShr => OpCodes.Shr,
OpCodeMnemonic.IUShr => OpCodes.Shr_Un,
_ => throw new UnreachableException(),
});
stack.Push(new Value(typeof(int)));
break;
}
case OpCodeMnemonic.INeg:
{
var v = stack.Pop();
AssertIntCompatible(v.Type);
il.Emit(OpCodes.Neg);
stack.Push(new Value(typeof(int)));
break;
}
case OpCodeMnemonic.LAdd:
case OpCodeMnemonic.LSub:
case OpCodeMnemonic.LMul:
case OpCodeMnemonic.LDiv:
case OpCodeMnemonic.LRem:
case OpCodeMnemonic.LOr:
case OpCodeMnemonic.LAnd:
case OpCodeMnemonic.LShl:
case OpCodeMnemonic.LShr:
case OpCodeMnemonic.LUShr:
{
var a = stack.Pop();
var b = stack.Pop();
AssertLongCompatible(a.Type);
AssertLongCompatible(b.Type);
il.Emit(opcode.Mnemonic switch
{
OpCodeMnemonic.LAdd => OpCodes.Add,
OpCodeMnemonic.LSub => OpCodes.Sub,
OpCodeMnemonic.LMul => OpCodes.Mul,
OpCodeMnemonic.LDiv => OpCodes.Div,
OpCodeMnemonic.LRem => OpCodes.Rem,
OpCodeMnemonic.LOr => OpCodes.Or,
OpCodeMnemonic.LAnd => OpCodes.And,
OpCodeMnemonic.LShl => OpCodes.Shl,
OpCodeMnemonic.LShr => OpCodes.Shr,
OpCodeMnemonic.LUShr => OpCodes.Shr_Un,
_ => throw new UnreachableException(),
});
stack.Push(new Value(typeof(long)));
break;
}
case OpCodeMnemonic.IntToLong:
{
var value = stack.Pop();
AssertIntCompatible(value.Type);
il.Emit(OpCodes.Conv_I8);
stack.Push(new Value(typeof(long)));
break;
}
case OpCodeMnemonic.LongToInt:
{
var value = stack.Pop();
AssertLongCompatible(value.Type);
il.Emit(OpCodes.Conv_I4);
stack.Push(new Value(typeof(int)));
break;
}
case OpCodeMnemonic.IntToChar:
{
var value = stack.Pop();
AssertIntCompatible(value.Type);
il.Emit(OpCodes.Conv_I2);
stack.Push(new Value(typeof(char)));
break;
}
case OpCodeMnemonic.LCmp:
{
var a = stack.Pop();
var b = stack.Pop();
var m = ILHelpers.LCmp;
if (a.Type != typeof(long)) throw new InvalidOperationException($"Expected long, found `{a.Type}`");
if (b.Type != typeof(long)) throw new InvalidOperationException($"Expected long, found `{b.Type}`");
il.Emit(OpCodes.Call, m.Method);
stack.Push(new Value(typeof(int)));
break;
}
case OpCodeMnemonic.FCmpL:
{
var a = stack.Pop();
var b = stack.Pop();
var m = ILHelpers.FCmpL;
if (a.Type != typeof(float)) throw new InvalidOperationException($"Expected float, found `{a.Type}`");
if (b.Type != typeof(float)) throw new InvalidOperationException($"Expected float, found `{b.Type}`");
il.Emit(OpCodes.Call, m.Method);
stack.Push(new Value(typeof(int)));
break;
}
case OpCodeMnemonic.FCmpG:
{
var a = stack.Pop();
var b = stack.Pop();
var m = ILHelpers.FCmpG;
if (a.Type != typeof(float)) throw new InvalidOperationException($"Expected float, found `{a.Type}`");
if (b.Type != typeof(float)) throw new InvalidOperationException($"Expected float, found `{b.Type}`");
il.Emit(OpCodes.Call, m.Method);
stack.Push(new Value(typeof(int)));
break;
}
case OpCodeMnemonic.IfEq:
{
var value = stack.Pop();
AssertLongCompatible(value.Type);
il.Emit(OpCodes.Brfalse, JmpLabel());
break;
}
case OpCodeMnemonic.IfNe:
{
var value = stack.Pop();
AssertLongCompatible(value.Type);
il.Emit(OpCodes.Brtrue, JmpLabel());
break;
}
case OpCodeMnemonic.IfLt:
{
var value = stack.Pop();
AssertLongCompatible(value.Type);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Clt);
il.Emit(OpCodes.Brtrue, JmpLabel());
break;
}
case OpCodeMnemonic.IfGe:
{
var value = stack.Pop();
AssertLongCompatible(value.Type);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Cgt);
il.Emit(OpCodes.Brtrue, JmpLabel());
il.Emit(OpCodes.Brfalse, JmpLabel());
break;
}
case OpCodeMnemonic.IfLe:
{
var value = stack.Pop();
AssertLongCompatible(value.Type);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Clt);
il.Emit(OpCodes.Brtrue, JmpLabel());
il.Emit(OpCodes.Brfalse, JmpLabel());
break;
}
case OpCodeMnemonic.IfICmpNe:
{
var a = stack.Pop();
var b = stack.Pop();
AssertIntCompatible(a);
AssertIntCompatible(b);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Brfalse, JmpLabel());
break;
}
case OpCodeMnemonic.IfICmpGe:
{
var a = stack.Pop();
var b = stack.Pop();
var m = ILHelpers.ICmpGe;
AssertCast(a, typeof(int), il);
AssertCast(b, typeof(int), il);
il.Emit(OpCodes.Call, m.Method);
il.Emit(OpCodes.Brtrue, JmpLabel());
break;
}
case OpCodeMnemonic.IfICmpLe:
{
var a = stack.Pop();
var b = stack.Pop();
var m = ILHelpers.ICmpLe;
AssertCast(a, typeof(int), il);
AssertCast(b, typeof(int), il);
il.Emit(OpCodes.Call, m.Method);
il.Emit(OpCodes.Brtrue, JmpLabel());
break;
}
case OpCodeMnemonic.IfICmpEq:
case OpCodeMnemonic.IfICmpLt:
case OpCodeMnemonic.IfICmpGt:
{
var a = stack.Pop();
var b = stack.Pop();
AssertIntCompatible(a);
AssertIntCompatible(b);
il.Emit(opcode.Mnemonic switch
{
OpCodeMnemonic.IfICmpEq => OpCodes.Ceq,
OpCodeMnemonic.IfICmpLt => OpCodes.Clt,
OpCodeMnemonic.IfICmpGt => OpCodes.Cgt,
_ => throw new UnreachableException(),
});
il.Emit(OpCodes.Brtrue, JmpLabel());
break;
}
case OpCodeMnemonic.IfACmpNe:
{
if (!stack.Pop().Type.IsClass || !stack.Pop().Type.IsClass)
throw new InvalidOperationException("Value is not an object.");
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Brfalse, JmpLabel());
break;
}
case OpCodeMnemonic.Goto:
{
il.Emit(OpCodes.Br, JmpLabel());
break;
}
case OpCodeMnemonic.IReturn:
case OpCodeMnemonic.LReturn:
case OpCodeMnemonic.FReturn:
case OpCodeMnemonic.DReturn:
case OpCodeMnemonic.AReturn:
{
var value = stack.Pop();
il.Emit(OpCodes.Ret);
break;
}
case OpCodeMnemonic.Return:
{
il.Emit(OpCodes.Ret);
break;
}
case OpCodeMnemonic.GetStaticField:
{
var fieldRef = javaClass.GetConstant<FieldRef>(opcode.P0.UShort);
var parent = MakeType(fieldRef.Class);
stack.Push(parent.GetField(fieldRef.Field.Name, il));
break;
}
case OpCodeMnemonic.GetField:
{
var fieldRef = javaClass.GetConstant<FieldRef>(opcode.P0.UShort);
var parent = MakeType(fieldRef.Class);
var value = stack.Pop();
if (!value.Type.IsAssignableTo(parent.Type))
throw new InvalidOperationException($"Value of type `{value.Type.FullName}` is not compatible with type `{parent.Type.FullName}`.");
stack.Push(parent.GetField(fieldRef.Field.Name, il));
break;
}
case OpCodeMnemonic.SetField:
{
var fieldRef = javaClass.GetConstant<FieldRef>(opcode.P0.UShort);
var parent = MakeType(fieldRef.Class);
var value = stack.Pop();
parent.SetField(fieldRef.Field.Name, value, il);
break;
}
case OpCodeMnemonic.InvokeVirtual:
case OpCodeMnemonic.InvokeSpecial:
case OpCodeMnemonic.InvokeStatic:
case OpCodeMnemonic.InvokeInterface:
{
var methodRef = javaClass.GetConstant<MethodRef>(opcode.P0.UShort);
var parent = MakeType(methodRef.Class);
var signature = (MethodSignature)javaClass.Loader.GetClass(methodRef.Method.Descriptor.AsMemory())
.SpecialClassMetadata!;
parent.CallMethod(methodRef.Method.Name, signature, stack, il);
break;
}
case OpCodeMnemonic.New:
{
var type = MakeType(javaClass.GetConstant<Class>(opcode.P0.UShort));
if (type is not TypeSurrogate)
{
var posBackup = opcodes.Reader.Stream.Position;
var stackBackup = new Stack<Value>(stack);
opcodes.MoveNext();
if (opcodes.Current.Mnemonic == OpCodeMnemonic.Dup)
{
opcodes.MoveNext();
position = opcodes.Reader.Stream.Position;
while (opcodes.Current.Mnemonic != OpCodeMnemonic.InvokeSpecial)
{
CompileOpCode(position, opcodes.Current);
opcodes.MoveNext();
position = opcodes.Reader.Stream.Position;
}
var methodRef = javaClass.GetConstant<MethodRef>(opcodes.Current.P0.UShort);
var parent = MakeType(methodRef.Class);
var signature = (MethodSignature)javaClass.Loader
.GetClass(methodRef.Method.Descriptor.AsMemory())
.SpecialClassMetadata!;
if (parent is NewClass newClass)
{
var ctor = (ConstructorInfo)newClass.GetMethod(methodRef.Method.Name, signature);
while (stack.Count > stackBackup.Count) stack.Pop();
il.Emit(OpCodes.Newobj, ctor);
stack.Push(new Value(parent.Type));
}
else
{
throw new NotImplementedException();
}
break;
}
opcodes.Reader.Stream.Position = posBackup;
stack = stackBackup;
}
var func = RuntimeHelpers.GetUninitializedObject;
il.Emit(OpCodes.Ldtoken, type.Type);
il.Emit(OpCodes.Call, func.Method);
stack.Push(new Value(type.Type));
break;
}
case OpCodeMnemonic.ANewArray:
{
var len = stack.Pop();
AssertIntCompatible(len.Type);
var type = MakeType(javaClass.GetConstant<Class>(opcode.P0.UShort));
il.Emit(OpCodes.Newarr, type.Type);
stack.Push(new Value(type.Type.MakeArrayType()));
break;
}
case OpCodeMnemonic.NewArray:
{
var elementType = opcode.P0.Byte switch
{
4 => typeof(bool),
5 => typeof(char),
6 => typeof(float),
7 => typeof(double),
8 => typeof(byte),
9 => typeof(short),
10 => typeof(int),
11 => typeof(long),
_ => typeof(void),
};
il.Emit(OpCodes.Newarr, elementType);
stack.Push(new Value(elementType.MakeArrayType()));
break;
}
case OpCodeMnemonic.ArrayLen:
{
var value = stack.Pop();
AssertArray(value.Type);
il.Emit(OpCodes.Ldlen);
stack.Push(new Value(typeof(int)));
break;
}
case OpCodeMnemonic.AThrow:
{
stack.Pop();
il.Emit(OpCodes.Throw);
break;
}
case OpCodeMnemonic.CheckCast:
{
var type = MakeType(javaClass.GetConstant<Class>(opcode.P0.UShort));
var value = stack.Pop();
AssertClass(value.Type);
il.Emit(OpCodes.Castclass, type.Type);
stack.Push(new Value(type.Type));
break;
}
case OpCodeMnemonic.InstanceOf:
{
var type = MakeType(javaClass.GetConstant<Class>(opcode.P0.UShort));
var value = stack.Pop();
AssertClass(value.Type);
il.Emit(OpCodes.Isinst, type.Type);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Castclass, type.Type);
il.Emit(OpCodes.Ceq);
stack.Push(new Value(typeof(int)));
break;
}
case OpCodeMnemonic.IfNull:
{
AssertClass(stack.Pop().Type);
il.Emit(OpCodes.Brfalse, JmpLabel());
break;
}
case OpCodeMnemonic.IfNonNull:
{
AssertClass(stack.Pop().Type);
il.Emit(OpCodes.Brtrue, JmpLabel());
break;
}
default:
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Unknown opcode: {opcode.Mnemonic}");
Console.ResetColor();
il.Emit(OpCodes.Nop);
break;
}
}
}
for (; opcodes.MoveNext();)
{
il.MarkLabel(labels[position]);
var opcode = opcodes.Current;
CompileOpCode(position, opcode);
position = opcodes.Reader.Stream.Position;
}
Debug.Writer.Indent--;
CompiledMethods += 1;
}
private static Dictionary<long, Label> CreateLabels(ILGenerator il, OpCodeReader opcodes)
{
var start = opcodes.Reader.Stream.Position;
var position = start;
var labels = new Dictionary<long, Label>();
while (opcodes.MoveNext())
{
labels.Add(position, il.DefineLabel());
position = opcodes.Reader.Stream.Position;
}
opcodes.Reader.Stream.Position = start;
return labels;
}
internal void AssertCast(Value value, Type type, ILGenerator il)
{
if(ReferenceEquals(type, value.Type))
return;
if (type == typeof(int))
{
if (value.Type == typeof(byte) || value.Type == typeof(short) || value.Type == typeof(char) || value.Type == typeof(bool))
{
il.Emit(OpCodes.Conv_I4);
return;
}
}
if (type == typeof(long))
{
if (value.Type == typeof(byte) || value.Type == typeof(short) || value.Type == typeof(int) || value.Type == typeof(char) || value.Type == typeof(bool))
{
il.Emit(OpCodes.Conv_I8);
return;
}
}
if (type == typeof(byte) || type == typeof(bool))
{
if (value.Type == typeof(int) || value.Type == typeof(short) || value.Type == typeof(char))
{
il.Emit(OpCodes.Conv_I1);
return;
}
}
if (value.Type.IsArray)
{
if(type == typeof(object))
return;
if(type.IsArray && type.GetElementType() == value.Type.GetElementType())
return;
}
if (value.IsNull && !type.IsValueType)
return;
if(IsAssignableTo(value.Type, type))
return;
throw new InvalidOperationException($"Value of type `{value.Type}` cannot be assigned to one of type `{type}`.");
}
private static void AssertClass(Type type)
{
if (type.IsValueType)
throw new InvalidOperationException($"Value of type `{type}` is not an object.");
}
private static void AssertArray(Type type)
{
if (!type.IsArray && type != typeof(Array))
throw new InvalidOperationException($"Value of type `{type}` is not an array.");
}
private static void AssertIntCompatible(Type type)
{
if(type == typeof(int)) return;
if(type == typeof(byte)) return;
if(type == typeof(char)) return;
if(type == typeof(short)) return;
if(type == typeof(bool)) return;
throw new InvalidOperationException($"Value of type `{type}` is not an integer.");
}
private static void AssertLongCompatible(Type type)
{
if(type == typeof(long)) return;
if(type == typeof(int)) return;
if(type == typeof(byte)) return;
if(type == typeof(char)) return;
if(type == typeof(short)) return;
if(type == typeof(bool)) return;
throw new InvalidOperationException($"Value of type `{type}` is not an integer.");
}
private bool IsAssignableTo(Type from, Type to)
{
if (ReferenceEquals(from, to)) return true;
lock (_casts)
{
if(_casts.TryGetValue(from, out var casts))
if(casts.Contains(to)) return true;
}
return false;
}
private FrozenSet<Type> CollectTypeCasts(Class? javaClass)
{
if(javaClass is null)
return FrozenSet<Type>.Empty;
var set = new HashSet<Type>();
while (javaClass != null)
{
set.Add(MakeType(javaClass).Type);
foreach (var javaInterface in javaClass.Interfaces)
set.Add(MakeType(javaInterface).Type);
javaClass = javaClass.SuperClass;
}
return set.ToFrozenSet();
}
}
internal readonly struct Value
{
public delegate Value LoadDelegate(ILGenerator il, OpCodeParameter data);
public delegate void StoreDelegate(ILGenerator il, OpCodeParameter data);
public readonly Type Type;
private LoadDelegate? _load { get; init; }
private StoreDelegate? _store { get; init; }
private OpCodeParameter _data { get; init; }
public bool IsNull => Type == typeof(object) && _data.Byte == 1;
public static implicit operator Type(Value v) => v.Type;
public Value Load(ILGenerator il)
{
return _load is { } load ? load(il, _data) : throw new InvalidOperationException("Value cannot be loaded.");
}
public void Store(ILGenerator il)
{
if (_store is not {} store) throw new InvalidOperationException("Value cannot be stored.");
store(il, _data);
}
public Value(Type type)
{
Type = type;
}
public Value(int value)
{
Type = typeof(int);
_data = new OpCodeParameter { UInt = (uint)value };
_load = (il, data) =>
{
var val = (int)data.UInt;
switch (val)
{
case 0: il.Emit(OpCodes.Ldc_I4_0); break;
case 1: il.Emit(OpCodes.Ldc_I4_1); break;
case 2: il.Emit(OpCodes.Ldc_I4_2); break;
case 3: il.Emit(OpCodes.Ldc_I4_3); break;
case 4: il.Emit(OpCodes.Ldc_I4_4); break;
case 5: il.Emit(OpCodes.Ldc_I4_5); break;
case 6: il.Emit(OpCodes.Ldc_I4_6); break;
case 7: il.Emit(OpCodes.Ldc_I4_7); break;
case 8: il.Emit(OpCodes.Ldc_I4_8); break;
case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
case <= 255: il.Emit(OpCodes.Ldc_I4_S, (sbyte)val); break;
default: il.Emit(OpCodes.Ldc_I4, val); break;
}
return new Value(val);
};
}
public Value(long value)
{
Type = typeof(long);
_data = new OpCodeParameter { ULong = (ulong) value };
_load = (il, data) =>
{
il.Emit(OpCodes.Ldc_I8, (long) data.ULong);
return new Value((long)data.ULong);
};
}
public Value(float value)
{
Type = typeof(float);
_data = new OpCodeParameter { Float = value };
_load = (il, data) =>
{
il.Emit(OpCodes.Ldc_R4, data.Float);
return new Value(data.Float);
};
}
public Value(double value)
{
Type = typeof(double);
_data = new OpCodeParameter { Double = value };
_load = (il, data) =>
{
il.Emit(OpCodes.Ldc_R8, data.Double);
return new Value(data.Double);
};
}
public Value(string value)
{
Type = typeof(string);
_data = new OpCodeParameter { Object = value };
_load = (il, data) =>
{
var val = Unsafe.As<string>(data.Object)!;
il.Emit(OpCodes.Ldstr, val);
return new Value(val);
};
}
public Value(FieldInfo field)
{
Type = field.FieldType;
_data = new OpCodeParameter { Object = field };
_load = (il, data) =>
{
var val = Unsafe.As<FieldInfo>(data.Object)!;
il.Emit(val.IsStatic ? OpCodes.Ldsfld : OpCodes.Ldfld, val);
return new Value(val);
};
}
public static readonly Value Null = new(typeof(object)) { _data = new OpCodeParameter { Byte = 1 }};
public static Value TypeRef(Type type)
{
return new Value(typeof(Type))
{
_data = new OpCodeParameter { Object = type },
_load = (il, data) =>
{
var val = Unsafe.As<Type>(data.Object)!;
il.Emit(OpCodes.Ldtoken, val);
return new Value(val);
},
};
}
public static Value Parameter(Type type, int idx)
{
return new Value(type)
{
_data = new OpCodeParameter { UInt = (uint)idx, Object = type },
_load = (il, data) =>
{
var idx = (int)data.UInt;
var type = Unsafe.As<Type>(data.Object);
switch (idx)
{
case 0: il.Emit(OpCodes.Ldarg_0); break;
case 1: il.Emit(OpCodes.Ldarg_1); break;
case 2: il.Emit(OpCodes.Ldarg_2); break;
case 3: il.Emit(OpCodes.Ldarg_3); break;
case <= 255: il.Emit(OpCodes.Ldarg_S, (byte)idx); break;
default: il.Emit(OpCodes.Ldarg, (short)idx); break;
}
return Value.Parameter(type, idx);
},
_store = (il, data) =>
{
var idx = (int)data.UInt;
switch (idx)
{
case <= 255: il.Emit(OpCodes.Starg_S, (byte)idx); break;
default: il.Emit(OpCodes.Starg, (short)idx); break;
}
},
};
}
public static Value Local(Type type, int idx)
{
return new Value(type)
{
_data = new OpCodeParameter { UInt = (uint)idx, Object = type },
_load = (il, data) =>
{
var idx = (int)data.UInt;
var type = Unsafe.As<Type>(data.Object);
switch (idx)
{
case 0: il.Emit(OpCodes.Ldloc_0); break;
case 1: il.Emit(OpCodes.Ldloc_1); break;
case 2: il.Emit(OpCodes.Ldloc_2); break;
case 3: il.Emit(OpCodes.Ldloc_3); break;
case <= 255: il.Emit(OpCodes.Ldloc_S, (byte)idx); break;
default: il.Emit(OpCodes.Ldloc, (short)idx); break;
}
return Local(type, idx);
},
_store = (il, data) =>
{
var idx = (int)data.UInt;
switch (idx)
{
case 0: il.Emit(OpCodes.Stloc_0); break;
case 1: il.Emit(OpCodes.Stloc_1); break;
case 2: il.Emit(OpCodes.Stloc_2); break;
case 3: il.Emit(OpCodes.Stloc_3); break;
case <= 255: il.Emit(OpCodes.Stloc_S, (byte)idx); break;
default: il.Emit(OpCodes.Stloc, (short)idx); break;
}
},
};
}
public override string ToString()
{
return $"{Type.Name} {_data}";
}
}