Files
JCIL/CLI/AssemblyBuilder.cs
T
2026-05-05 22:41:02 +02:00

789 lines
27 KiB
C#
Executable File

using System.Collections.Concurrent;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using JCIL.Core;
using JCIL.Java.Class;
namespace JCIL.CLI;
public sealed class AssemblyBuilder
{
public readonly System.Reflection.Emit.AssemblyBuilder Assembly;
private readonly ModuleBuilder _module;
private readonly ConcurrentDictionary<Class, Type> _classes = [];
private readonly ConcurrentDictionary<Field, FieldInfo> _fields = [];
private readonly ConcurrentDictionary<Method, LazyMethod> _methods = [];
private readonly MethodInfo _getType;
public int CompiledMethods { get; private set; }
public int TotalMethods => _methods.Count;
public AssemblyBuilder(AssemblyName name, bool runnable)
{
Assembly = runnable
? System.Reflection.Emit.AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndCollect)
: new PersistedAssemblyBuilder(name, typeof(int).Assembly);
_module = Assembly.DefineDynamicModule("JCIL_Output");
_getType = typeof(object).GetMethod(nameof(object.GetType), BindingFlags.Instance | BindingFlags.Public)!;
}
public void ForceBuildMethod(Method method)
{
_ = _methods[method].Method;
}
public Type Build(Class javaClass)
{
switch (javaClass.SpecialClassMetadata)
{
case IntrinsicMetadata { Type: IntrinsicType.Boolean }: return typeof(bool);
case IntrinsicMetadata { Type: IntrinsicType.Byte }: return typeof(byte);
case IntrinsicMetadata { Type: IntrinsicType.Char }: return typeof(char);
case IntrinsicMetadata { Type: IntrinsicType.Short }: return typeof(short);
case IntrinsicMetadata { Type: IntrinsicType.Int }: return typeof(int);
case IntrinsicMetadata { Type: IntrinsicType.Long }: return typeof(long);
case IntrinsicMetadata { Type: IntrinsicType.Float }: return typeof(float);
case IntrinsicMetadata { Type: IntrinsicType.Double }: return typeof(double);
case IntrinsicMetadata { Type: IntrinsicType.Void }: return typeof(void);
case ArrayMetadata { ElementClass: var elementClass }: return Build(elementClass).MakeArrayType();
}
var build = false;
var theType = _classes.GetOrAdd(javaClass, _ =>
{
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.Length == 0
? javaClass.Name.ToString()
: $"{javaClass.Namespace.ToString().Replace('/', '.')}.{javaClass.Name}";
build = true;
return _module.DefineType(name, flags);
});
if (build && theType is TypeBuilder typeBuilder)
{
// Console.WriteLine($"Building type `{typeBuilder.FullName}`");
if (!theType.IsInterface && javaClass.SuperClass is not null && javaClass.SuperClass != javaClass)
typeBuilder.SetParent(Build(javaClass.SuperClass));
foreach (var field in javaClass.Fields)
{
FieldAttributes flags = default;
if ((field.AccessFlags & FieldAccessFlags.Static) != 0) flags |= FieldAttributes.Static;
if ((field.AccessFlags & FieldAccessFlags.Final) != 0) flags |= FieldAttributes.InitOnly;
if ((field.AccessFlags & FieldAccessFlags.Public) != 0) flags |= FieldAttributes.Public;
if ((field.AccessFlags & FieldAccessFlags.Private) != 0) flags |= FieldAttributes.Private;
if ((field.AccessFlags & FieldAccessFlags.Protected) != 0) flags |= FieldAttributes.Family;
var type = Build(field.Type);
_fields.TryAdd(field, typeBuilder.DefineField(field.Name, type, flags));
}
foreach (var method in javaClass.Methods)
{
MethodAttributes flags = default;
if ((method.AccessFlags & MethodAccessFlags.Static) != 0) flags |= MethodAttributes.Static;
else flags |= MethodAttributes.Virtual;
if ((method.AccessFlags & MethodAccessFlags.Public) != 0) flags |= MethodAttributes.Public;
if ((method.AccessFlags & MethodAccessFlags.Private) != 0) flags |= MethodAttributes.Private;
if ((method.AccessFlags & MethodAccessFlags.Protected) != 0) flags |= MethodAttributes.Family;
if ((method.AccessFlags & MethodAccessFlags.Abstract) != 0) flags |= MethodAttributes.Abstract;
if ((method.AccessFlags & MethodAccessFlags.Final) != 0) flags |= MethodAttributes.Final;
var signature = (MethodSignature)method.Type.SpecialClassMetadata!;
var paramTypes = signature.ParamTypes.Select(Build).ToArray();
if (method.Name == "<init>")
{
flags &= ~MethodAttributes.Virtual;
var ctor = typeBuilder.DefineConstructor(flags, CallingConventions.Standard, paramTypes);
if (method.Attributes.OfType<MethodParameters>().FirstOrDefault() is { } attrib)
{
for (var j = 0; j < attrib.Parameters.Count; j++)
{
// TODO populate parameter attributes
ctor.DefineParameter(j, ParameterAttributes.None, attrib.Parameters[j].Item1);
}
}
_methods.TryAdd(method, new LazyMethod(ctor, () =>
{
Console.WriteLine($"Building method `{typeBuilder.FullName}.{method.Name}`");
if (method.TryGetCode(out var code, out var opcodes))
CompileOpCodes(javaClass, ctor, ctor.GetILGenerator(), code, opcodes);
}));
}
else if (method.Name == "<clinit>")
{
var ctor = typeBuilder.DefineTypeInitializer();
if (method.Attributes.OfType<MethodParameters>().FirstOrDefault() is { } attrib)
{
for (var j = 0; j < attrib.Parameters.Count; j++)
{
// TODO populate parameter attributes
ctor.DefineParameter(j, ParameterAttributes.None, attrib.Parameters[j].Item1);
}
}
_methods.TryAdd(method, new LazyMethod(ctor, () =>
{
Console.WriteLine($"Building method `{typeBuilder.FullName}.{method.Name}`");
if (method.TryGetCode(out var code, out var opcodes))
CompileOpCodes(javaClass, ctor, ctor.GetILGenerator(), code, opcodes);
}));
}
else
{
var methodBuilder = typeBuilder.DefineMethod(method.Name, flags);
methodBuilder.SetReturnType(Build(signature.ReturnType));
methodBuilder.SetParameters(paramTypes);
if (method.Attributes.OfType<MethodParameters>().FirstOrDefault() is { } attrib)
{
for (var j = 0; j < attrib.Parameters.Count; j++)
{
// TODO populate parameter attributes
methodBuilder.DefineParameter(j, ParameterAttributes.None, attrib.Parameters[j].Item1);
}
}
_methods.TryAdd(method, new LazyMethod(methodBuilder, () =>
{
Console.WriteLine($"Building method `{typeBuilder.FullName}.{method.Name}`");
if (method.TryGetCode(out var code, out var opcodes))
CompileOpCodes(javaClass, methodBuilder, methodBuilder.GetILGenerator(), code, opcodes);
}));
}
}
return typeBuilder.CreateType();
}
foreach (var interfaceClass in javaClass.Interfaces)
_ = Build(interfaceClass);
return theType;
}
private void CompileOpCodes(Class javaClass, MethodBase methodBase, ILGenerator il, Code code, OpCodeReader opcodes)
{
var locals = new (Type, int)[code.MaxLocals];
var localsCount = 0;
var localsIdx = 0;
if (!methodBase.IsStatic)
{
il.DeclareLocal(methodBase.DeclaringType!);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Stloc_0);
locals[localsIdx++] = (methodBase.DeclaringType!, localsCount++);
}
var parameters = methodBase.GetParameters();
for (int i = 0, j = methodBase.IsStatic ? 0 : 1; i < parameters.Length; i++, j++)
{
il.DeclareLocal(parameters[i].ParameterType);
il.Emit(OpCodes.Ldarg, (short) j);
il.Emit(OpCodes.Stloc, (short) j);
locals[localsIdx++] = (parameters[i].ParameterType, localsCount++);
}
if (code.Attributes.OfType<StackMapTable>().FirstOrDefault() is not { Frames: var frames })
frames = [];
var position = opcodes.Reader.Stream.Position;
var labels = new Dictionary<long, Label>();
while (opcodes.MoveNext())
{
labels.Add(position, il.DefineLabel());
position = opcodes.Reader.Stream.Position;
}
opcodes.Reset();
position = opcodes.Reader.Stream.Position;
void AddLocal(in StackMapTable.VerificationTypeInfo info)
{
Type compiledType;
switch (info.Tag)
{
case StackMapTable.VerificationTypeInfoTag.Top:
locals[localsIdx++] = default;
return;
case StackMapTable.VerificationTypeInfoTag.Integer: compiledType = typeof(int); break;
case StackMapTable.VerificationTypeInfoTag.Long: compiledType = typeof(long); break;
case StackMapTable.VerificationTypeInfoTag.Float: compiledType = typeof(float); break;
case StackMapTable.VerificationTypeInfoTag.Double: compiledType = typeof(double); break;
case StackMapTable.VerificationTypeInfoTag.Object:
{
var type = (Class)javaClass.GetConstant(info.Parameter)!;
compiledType = Build(type);
break;
}
case StackMapTable.VerificationTypeInfoTag.UninitializedThis:
compiledType = methodBase.DeclaringType!;
break;
default: throw new NotImplementedException($"Unimplemented local tag {info.Tag}");
}
locals[localsIdx++] = (compiledType, localsCount++);
il.DeclareLocal(compiledType);
}
for (var f = -1; opcodes.MoveNext();)
{
if (f + 1 < frames.Count && position >= frames[f + 1].StartOffset)
{
switch (frames[++f])
{
case StackMapTable.SameFrame: break;
case StackMapTable.SameFrameExtended: break;
case StackMapTable.OneLocalFrame: break;
case StackMapTable.OneLocalFrameExtended: break;
case StackMapTable.AppendFrame frame:
foreach (var info in frame.Locals) AddLocal(info);
break;
case StackMapTable.ChopFrame frame:
localsIdx -= frame.Count;
break;
case StackMapTable.FullFrame frame:
{
localsIdx = 0;
foreach (var info in frame.Locals) AddLocal(info);
break;
}
default: throw new NotImplementedException($"Unimplemented {frames[f]}");
}
}
il.MarkLabel(labels[position]);
var opcode = opcodes.Current;
Label JmpLabel()
{
return labels[position + (short)opcode.P0.UShort];
}
void LoadLocal(int idx)
{
var loc = locals[idx].Item2;
switch (loc)
{
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)loc); break;
default: il.Emit(OpCodes.Ldloc, (short)loc); break;
}
}
void StoreLocal(Type ty, int idx)
{
ref var loc = ref locals[idx];
if (loc.Item2 == -1 || loc.Item1 is null || (loc.Item1 != ty && (loc.Item1.IsValueType || ty.IsValueType)))
{
il.DeclareLocal(ty);
loc = (ty, localsCount++);
}
switch (loc.Item2)
{
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)loc.Item2); break;
default: il.Emit(OpCodes.Stloc, (short)loc.Item2); break;
}
}
switch (opcode.Mnemonic)
{
case OpCodeMnemonic.Nop: break;
case OpCodeMnemonic.AConstNull: il.Emit(OpCodes.Ldnull); break;
case OpCodeMnemonic.IConstM1: il.Emit(OpCodes.Ldc_I4_M1); break;
case OpCodeMnemonic.IConst0: il.Emit(OpCodes.Ldc_I4_0); break;
case OpCodeMnemonic.IConst1: il.Emit(OpCodes.Ldc_I4_1); break;
case OpCodeMnemonic.IConst2: il.Emit(OpCodes.Ldc_I4_2); break;
case OpCodeMnemonic.IConst3: il.Emit(OpCodes.Ldc_I4_3); break;
case OpCodeMnemonic.IConst4: il.Emit(OpCodes.Ldc_I4_4); break;
case OpCodeMnemonic.IConst5: il.Emit(OpCodes.Ldc_I4_5); break;
case OpCodeMnemonic.LConst0: il.Emit(OpCodes.Ldc_I8, 0L); break;
case OpCodeMnemonic.LConst1: il.Emit(OpCodes.Ldc_I8, 1L); break;
case OpCodeMnemonic.FConst0: il.Emit(OpCodes.Ldc_R4, 0f); break;
case OpCodeMnemonic.FConst1: il.Emit(OpCodes.Ldc_R4, 1f); break;
case OpCodeMnemonic.FConst2: il.Emit(OpCodes.Ldc_R4, 2f); break;
case OpCodeMnemonic.DConst0: il.Emit(OpCodes.Ldc_R4, 0d); break;
case OpCodeMnemonic.DConst1: il.Emit(OpCodes.Ldc_R4, 1d); break;
case OpCodeMnemonic.PushByte: il.Emit(OpCodes.Ldc_I4, (int)opcode.P0.Byte); break;
case OpCodeMnemonic.PushShort: il.Emit(OpCodes.Ldc_I4, (int)(short)opcode.P0.UShort); break;
case OpCodeMnemonic.Ldc:
case OpCodeMnemonic.LdcW:
case OpCodeMnemonic.Ldc2W:
{
var constant = javaClass.GetConstant(opcode.P0.UShort);
switch (constant)
{
//TODO this is wrong
case Class c: il.Emit(OpCodes.Ldtoken, Build(c)); break;
case int val: il.Emit(OpCodes.Ldc_I4, val); break;
case short val: il.Emit(OpCodes.Ldc_I4, (int) val); break;
case long val: il.Emit(OpCodes.Ldc_I8, val); break;
case float val: il.Emit(OpCodes.Ldc_R4, val); break;
case double val: il.Emit(OpCodes.Ldc_R8, val); break;
case string str: il.Emit(OpCodes.Ldstr, str); break;
default:
throw new NotImplementedException(
$"Unimplemented ldc {constant?.GetType().FullName ?? "null"}");
}
break;
}
case OpCodeMnemonic.ILoad:
case OpCodeMnemonic.LLoad:
case OpCodeMnemonic.ALoad: LoadLocal(opcode.P0.Byte); break;
case OpCodeMnemonic.ILoad0:
case OpCodeMnemonic.LLoad0:
case OpCodeMnemonic.FLoad0:
case OpCodeMnemonic.DLoad0:
case OpCodeMnemonic.ALoad0: LoadLocal(0); break;
case OpCodeMnemonic.ALoad1:
case OpCodeMnemonic.LLoad1:
case OpCodeMnemonic.FLoad1:
case OpCodeMnemonic.DLoad1:
case OpCodeMnemonic.ILoad1: LoadLocal(1); break;
case OpCodeMnemonic.ALoad2:
case OpCodeMnemonic.LLoad2:
case OpCodeMnemonic.FLoad2:
case OpCodeMnemonic.DLoad2:
case OpCodeMnemonic.ILoad2: LoadLocal(2); break;
case OpCodeMnemonic.ALoad3:
case OpCodeMnemonic.LLoad3:
case OpCodeMnemonic.FLoad3:
case OpCodeMnemonic.DLoad3:
case OpCodeMnemonic.ILoad3: LoadLocal(3); break;
case OpCodeMnemonic.IALoad: il.Emit(OpCodes.Ldelem_I4); break;
case OpCodeMnemonic.LALoad: il.Emit(OpCodes.Ldelem_I8); break;
case OpCodeMnemonic.AALoad: il.Emit(OpCodes.Ldelem_Ref); break;
case OpCodeMnemonic.BALoad: il.Emit(OpCodes.Ldelem_I1); break;
case OpCodeMnemonic.CALoad: il.Emit(OpCodes.Ldelem_I2); break;
case OpCodeMnemonic.SALoad: il.Emit(OpCodes.Ldelem_I2); break;
case OpCodeMnemonic.AStore: StoreLocal(typeof(object), opcode.P0.Byte); break;
case OpCodeMnemonic.IStore: StoreLocal(typeof(int), opcode.P0.Byte); break;
case OpCodeMnemonic.LStore: StoreLocal(typeof(long), opcode.P0.Byte); break;
case OpCodeMnemonic.FStore: StoreLocal(typeof(float), opcode.P0.Byte); break;
case OpCodeMnemonic.DStore: StoreLocal(typeof(double), opcode.P0.Byte); break;
case OpCodeMnemonic.AStore0: StoreLocal(typeof(object), 0); break;
case OpCodeMnemonic.IStore0: StoreLocal(typeof(int), 0); break;
case OpCodeMnemonic.LStore0: StoreLocal(typeof(long), 0); break;
case OpCodeMnemonic.AStore1:StoreLocal(typeof(object), 1); break;
case OpCodeMnemonic.IStore1:StoreLocal(typeof(int), 1); break;
case OpCodeMnemonic.LStore1:StoreLocal(typeof(long), 1); break;
case OpCodeMnemonic.AStore2:StoreLocal(typeof(object), 2); break;
case OpCodeMnemonic.IStore2:StoreLocal(typeof(int), 2); break;
case OpCodeMnemonic.LStore2:StoreLocal(typeof(long), 2); break;
case OpCodeMnemonic.AStore3:StoreLocal(typeof(object), 3); break;
case OpCodeMnemonic.IStore3:StoreLocal(typeof(int), 3); break;
case OpCodeMnemonic.LStore3:StoreLocal(typeof(long), 3); break;
case OpCodeMnemonic.LAStore: il.Emit(OpCodes.Stelem_I8); break;
case OpCodeMnemonic.AAStore: il.Emit(OpCodes.Stelem_Ref); break;
case OpCodeMnemonic.BAStore: il.Emit(OpCodes.Stelem_I1); break;
case OpCodeMnemonic.CAStore: il.Emit(OpCodes.Stelem_I2); break;
case OpCodeMnemonic.Pop: il.Emit(OpCodes.Pop); break;
case OpCodeMnemonic.Pop2:
{
var class1Label = il.DefineLabel();
var class2Label = il.DefineLabel();
var continueLabel = il.DefineLabel();
il.Emit(OpCodes.Dup);
il.EmitCall(OpCodes.Call, _getType, null);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldtoken, typeof(long).MetadataToken);
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Beq, class2Label);
il.Emit(OpCodes.Ldtoken, typeof(double).MetadataToken);
il.Emit(OpCodes.Beq, class2Label);
il.MarkLabel(class1Label);
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Br, continueLabel);
il.MarkLabel(class2Label);
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Br, continueLabel);
il.MarkLabel(continueLabel);
break;
}
case OpCodeMnemonic.Dup: il.Emit(OpCodes.Dup); break;
case OpCodeMnemonic.IAdd:
case OpCodeMnemonic.LAdd:
case OpCodeMnemonic.FAdd:
case OpCodeMnemonic.DAdd: il.Emit(OpCodes.Add); break;
case OpCodeMnemonic.ISub:
case OpCodeMnemonic.LSub:
case OpCodeMnemonic.FSub:
case OpCodeMnemonic.DSub: il.Emit(OpCodes.Sub); break;
case OpCodeMnemonic.IMul:
case OpCodeMnemonic.LMul:
case OpCodeMnemonic.FMul:
case OpCodeMnemonic.DMul: il.Emit(OpCodes.Mul); break;
case OpCodeMnemonic.IDiv:
case OpCodeMnemonic.LDiv:
case OpCodeMnemonic.FDiv:
case OpCodeMnemonic.DDiv: il.Emit(OpCodes.Div); break;
case OpCodeMnemonic.IRem:
case OpCodeMnemonic.LRem:
case OpCodeMnemonic.FRem:
case OpCodeMnemonic.DRem: il.Emit(OpCodes.Rem); break;
case OpCodeMnemonic.INeg:
case OpCodeMnemonic.LNeg: il.Emit(OpCodes.Neg); break;
case OpCodeMnemonic.IShl:
case OpCodeMnemonic.LShl: il.Emit(OpCodes.Shl); break;
case OpCodeMnemonic.IShr:
case OpCodeMnemonic.LShr: il.Emit(OpCodes.Shr); break;
case OpCodeMnemonic.IUShr:
case OpCodeMnemonic.LUShr: il.Emit(OpCodes.Shr_Un); break;
case OpCodeMnemonic.IAnd:
case OpCodeMnemonic.LAnd: il.Emit(OpCodes.And); break;
case OpCodeMnemonic.IOr:
case OpCodeMnemonic.LOr: il.Emit(OpCodes.Or); break;
case OpCodeMnemonic.IInc:
{
LoadLocal(opcode.P0.UShort);
il.Emit(OpCodes.Ldc_I4, opcode.P0.Byte);
il.Emit(OpCodes.Add);
StoreLocal(typeof(int), opcode.P0.UShort);
break;
}
case OpCodeMnemonic.IntToLong: il.Emit(OpCodes.Conv_I8); break;
case OpCodeMnemonic.IntToByte: il.Emit(OpCodes.Conv_I1); break;
case OpCodeMnemonic.IntToChar: il.Emit(OpCodes.Conv_U2); break;
case OpCodeMnemonic.IntToShort: il.Emit(OpCodes.Conv_I2); break;
case OpCodeMnemonic.LCmp:
{
var func = ILHelpers.LCmp;
il.EmitCall(OpCodes.Call, func.Method, null);
break;
}
case OpCodeMnemonic.InvokeSpecial:
case OpCodeMnemonic.InvokeStatic:
{
var methodRef = (MethodRef)javaClass.GetConstant(opcode.P0.UShort)!;
_ = Build(methodRef.Class);
var signature = (MethodSignature)javaClass.Loader.GetClass(methodRef.Method.Descriptor.AsMemory())
.SpecialClassMetadata!;
var method = methodRef.Class.GetMethod(methodRef.Method.Name, signature);
if (method is null || !_methods.TryGetValue(method, out var compiledMethod))
throw new Exception($"Method {methodRef.Method.Name} not found");
if (compiledMethod.Method is MethodInfo methodInfo)
il.EmitCall(OpCodes.Call, methodInfo, null);
if (compiledMethod.Method is ConstructorInfo constructorInfo)
il.Emit(OpCodes.Call, constructorInfo);
break;
}
case OpCodeMnemonic.InvokeVirtual:
{
var methodRef = (MethodRef)javaClass.GetConstant(opcode.P0.UShort)!;
_ = Build(methodRef.Class);
var signature = (MethodSignature)javaClass.Loader.GetClass(methodRef.Method.Descriptor.AsMemory())
.SpecialClassMetadata!;
var method = methodRef.Class.GetMethod(methodRef.Method.Name, signature);
if (method is null || !_methods.TryGetValue(method, out var compiledMethod))
throw new Exception($"Method {methodRef.Method.Name} not found");
if (compiledMethod.Method is MethodInfo methodInfo)
il.EmitCall(OpCodes.Callvirt, methodInfo, null);
break;
}
case OpCodeMnemonic.InvokeInterface:
{
var methodRef = (MethodRef)javaClass.GetConstant(opcode.P0.UShort)!;
_ = Build(methodRef.Class);
var signature = (MethodSignature)javaClass.Loader.GetClass(methodRef.Method.Descriptor.AsMemory())
.SpecialClassMetadata!;
var method = methodRef.Class.GetMethod(methodRef.Method.Name, signature);
if (method is null || !_methods.TryGetValue(method, out var compiledMethod))
throw new Exception($"Method {methodRef.Method.Name} not found");
if (compiledMethod.Method is MethodInfo methodInfo)
il.EmitCall(OpCodes.Callvirt, methodInfo, null);
break;
}
case OpCodeMnemonic.New:
{
var obj = (Class)javaClass.GetConstant(opcode.P0.UShort)!;
var objT = Build(obj);
// TODO Make this work with any number of parameters
var posBackup = opcodes.Reader.Stream.Position;
opcodes.MoveNext();
if (opcodes.Current.Mnemonic == OpCodeMnemonic.Dup)
{
opcodes.MoveNext();
if (opcodes.Current.Mnemonic == OpCodeMnemonic.InvokeSpecial)
{
var methodRef = (MethodRef)javaClass.GetConstant(opcodes.Current.P0.UShort)!;
_ = Build(methodRef.Class);
var signature = (MethodSignature)javaClass.Loader
.GetClass(methodRef.Method.Descriptor.AsMemory())
.SpecialClassMetadata!;
var method = methodRef.Class.GetMethod(methodRef.Method.Name, signature);
if (method is null || !_methods.TryGetValue(method, out var compiledMethod))
throw new Exception($"Method {methodRef.Method.Name} not found");
if (compiledMethod.Method is ConstructorInfo constructorInfo)
{
il.Emit(OpCodes.Newobj, constructorInfo);
break;
}
}
}
opcodes.Reader.Stream.Position = posBackup;
var func = RuntimeHelpers.GetUninitializedObject;
il.Emit(OpCodes.Ldtoken, objT);
il.Emit(OpCodes.Call, func.Method);
break;
}
case OpCodeMnemonic.NewArray:
{
il.Emit(OpCodes.Newarr, 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),
_ => throw new NotImplementedException(nameof(opcode.P0)),
});
break;
}
case OpCodeMnemonic.ANewArray:
{
var elem = (Class)javaClass.GetConstant(opcode.P0.UShort)!;
il.Emit(OpCodes.Newarr, Build(elem));
break;
}
case OpCodeMnemonic.IfEq: il.Emit(OpCodes.Brfalse, JmpLabel()); break;
case OpCodeMnemonic.IfNe: il.Emit(OpCodes.Brtrue, JmpLabel()); break;
case OpCodeMnemonic.IfLt:
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Blt, JmpLabel());
break;
case OpCodeMnemonic.IfLe:
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ble, JmpLabel());
break;
case OpCodeMnemonic.IfGe:
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Bge, JmpLabel());
break;
case OpCodeMnemonic.IfGt:
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Bgt, JmpLabel());
break;
case OpCodeMnemonic.IfICmpEq: il.Emit(OpCodes.Beq, JmpLabel()); break;
case OpCodeMnemonic.IfICmpNe: il.Emit(OpCodes.Bne_Un, JmpLabel()); break;
case OpCodeMnemonic.IfICmpLt: il.Emit(OpCodes.Blt, JmpLabel()); break;
case OpCodeMnemonic.IfICmpLe: il.Emit(OpCodes.Ble, JmpLabel()); break;
case OpCodeMnemonic.IfICmpGe: il.Emit(OpCodes.Bge, JmpLabel()); break;
case OpCodeMnemonic.IfICmpGt: il.Emit(OpCodes.Bgt, JmpLabel()); break;
case OpCodeMnemonic.IfACmpEq:
{
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Brtrue, JmpLabel());
break;
}
case OpCodeMnemonic.IfACmpNe:
{
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Brfalse, JmpLabel());
break;
}
case OpCodeMnemonic.Goto: il.Emit(OpCodes.Br, JmpLabel()); break;
case OpCodeMnemonic.IfNull: il.Emit(OpCodes.Brfalse, JmpLabel()); break;
case OpCodeMnemonic.IfNonNull: il.Emit(OpCodes.Brtrue, JmpLabel()); break;
case OpCodeMnemonic.Return:
case OpCodeMnemonic.IReturn:
case OpCodeMnemonic.LReturn:
case OpCodeMnemonic.FReturn:
case OpCodeMnemonic.DReturn:
case OpCodeMnemonic.AReturn:
{
il.Emit(OpCodes.Ret);
break;
}
case OpCodeMnemonic.SetField:
{
var fieldRef = (FieldRef)javaClass.GetConstant(opcode.P0.UShort)!;
_ = Build(fieldRef.Class);
var field = fieldRef.Class.GetField(fieldRef.Field.Name)!;
var compiledField = _fields[field];
il.Emit(OpCodes.Stfld, compiledField);
break;
}
case OpCodeMnemonic.SetStaticField:
{
var fieldRef = (FieldRef)javaClass.GetConstant(opcode.P0.UShort)!;
_ = Build(fieldRef.Class);
var field = fieldRef.Class.GetField(fieldRef.Field.Name)!;
var compiledField = _fields[field];
il.Emit(OpCodes.Stsfld, compiledField);
break;
}
case OpCodeMnemonic.GetField:
{
var fieldRef = (FieldRef)javaClass.GetConstant(opcode.P0.UShort)!;
_ = Build(fieldRef.Class);
var field = fieldRef.Class.GetField(fieldRef.Field.Name)!;
var compiledField = _fields[field];
il.Emit(OpCodes.Ldfld, compiledField);
break;
}
case OpCodeMnemonic.GetStaticField:
{
var fieldRef = (FieldRef)javaClass.GetConstant(opcode.P0.UShort)!;
_ = Build(fieldRef.Class);
var field = fieldRef.Class.GetField(fieldRef.Field.Name)!;
var compiledField = _fields[field];
il.Emit(OpCodes.Ldsfld, compiledField);
break;
}
case OpCodeMnemonic.ArrayLen:
{
il.Emit(OpCodes.Ldlen);
break;
}
case OpCodeMnemonic.AThrow:
{
il.Emit(OpCodes.Throw);
break;
}
case OpCodeMnemonic.InstanceOf:
{
var type = Build((Class)javaClass.GetConstant(opcode.P0.UShort)!);
il.Emit(OpCodes.Isinst, type);
break;
}
case OpCodeMnemonic.CheckCast:
{
var type = Build((Class)javaClass.GetConstant(opcode.P0.UShort)!);
il.Emit(OpCodes.Castclass, type);
break;
}
case OpCodeMnemonic.MonitorEnter:
{
Action<object> func = Monitor.Enter;
il.EmitCall(OpCodes.Call, func.Method, null);
break;
}
case OpCodeMnemonic.MonitorExit:
{
Action<object> func = Monitor.Exit;
il.EmitCall(OpCodes.Call, func.Method, null);
break;
}
default:
{
il.Emit(OpCodes.Nop);
Console.WriteLine($"Unknown opcode: {opcode.Mnemonic}");
break;
}
}
position = opcodes.Reader.Stream.Position;
}
CompiledMethods += 1;
}
}
internal class LazyMethod(MethodBase method, Action compile)
{
public bool Compiled { get; private set; }
public MethodBase Method
{
get
{
if (!Compiled)
{
Compiled = true;
compile();
}
return method;
}
}
}