From ec384254b2c07003d931e801dd79ebfc735caeaa Mon Sep 17 00:00:00 2001 From: Mia Date: Wed, 6 May 2026 12:47:10 +0200 Subject: [PATCH] Will the crap ever end? --- CLI/AssemblyBuilder.cs | 358 +++++++++++++++++++++++++++++++---- CLI/Program.cs | 2 +- CLI/Types/java/lang/Array.cs | 13 +- CLI/Types/java/lang/Short.cs | 26 +++ JavaClass/Method.cs | 2 + 5 files changed, 364 insertions(+), 37 deletions(-) create mode 100644 CLI/Types/java/lang/Short.cs diff --git a/CLI/AssemblyBuilder.cs b/CLI/AssemblyBuilder.cs index 553c3b5..c474179 100755 --- a/CLI/AssemblyBuilder.cs +++ b/CLI/AssemblyBuilder.cs @@ -1,10 +1,12 @@ using System.Collections.Concurrent; +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.Java.Class; +using Array = System.Array; using OpCode = JCIL.Java.Class.OpCode; namespace JCIL.CLI; @@ -46,6 +48,8 @@ public sealed class AssemblyBuilder return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.Char(c)); case IntrinsicType.Int: return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.Integer(c)); + case IntrinsicType.Short: + return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.Short(c)); case IntrinsicType.Long: return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.Long(c)); case IntrinsicType.Boolean: @@ -62,6 +66,9 @@ public sealed class AssemblyBuilder case ArrayMetadata array: return _classes.GetOrAdd(javaClass, c => new ArrayOf(MakeType(array.ElementClass), c)); } + + if(javaClass is {Namespace.Span: "java/lang", Name.Span: "String" }) + return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.String(c)); var newClass = (NewClass)_classes.GetOrAdd(javaClass, _ => { @@ -70,9 +77,16 @@ public sealed class AssemblyBuilder 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 builder = _module.DefineType(javaClass.Name.ToString(), flags); + var name = javaClass.Namespace.IsEmpty + ? javaClass.Name.ToString() + : $"{javaClass.Namespace.ToString().Replace('/', '.')}.{javaClass.Name}"; + var builder = _module.DefineType(name, flags); return new NewClass(this, builder, javaClass); }); + + if (javaClass.SuperClass is {} super) + newClass.Type.SetParent(MakeType(super).Type); + newClass.CreateFields(); return newClass; } @@ -81,7 +95,18 @@ public sealed class AssemblyBuilder OpCodeReader opcodes) { var thisType = methodBase.DeclaringType!; - Debug.Writer.WriteLine($"Compiling {thisType.FullName}.{methodBase.Name}"); + 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); @@ -90,13 +115,25 @@ public sealed class AssemblyBuilder 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) - locals[idx] = Value.Parameter(thisType, idx++); + idx += AssignLocal(idx, Value.Parameter(thisType, idx)); foreach (var parameter in methodBase.GetParameters()) - locals[idx] = Value.Parameter(parameter.ParameterType, idx++); + idx += AssignLocal(idx, Value.Parameter(parameter.ParameterType, idx)); } void CompileOpCode(long position, OpCode opcode) @@ -108,17 +145,36 @@ public sealed class AssemblyBuilder return labels[position + (short)opcode.P0.UShort]; } - void CheckCast(Value value, Type type) + void AssertCast(Value value, Type type) { - if (value.Type == typeof(int) && type == typeof(byte)) + if(value.Type == type) return; + + if (type == typeof(int)) { - il.Emit(OpCodes.Conv_I1); + if (value.Type == typeof(byte) || value.Type == typeof(short) || value.Type == typeof(char) || value.Type == typeof(bool)) + { + il.Emit(OpCodes.Conv_I4); + return; + } } - else if (!value.Type.IsAssignableTo(type)) + + if (type == typeof(byte)) { - throw new InvalidOperationException( - $"Value of type {value.Type} cannot be assigned to one of type {type}."); + if (value.Type == typeof(int) || value.Type == typeof(short) || value.Type == typeof(char)) + { + il.Emit(OpCodes.Conv_I1); + return; + } } + + if (value.Type.IsAssignableTo(type)) + return; + + if(type == typeof(object) && value.Type.IsArray) + return; + + + throw new InvalidOperationException($"Value of type `{value.Type}` cannot be assigned to one of type `{type}`."); } switch (opcode.Mnemonic) @@ -185,13 +241,34 @@ public sealed class AssemblyBuilder _ => -1, }; - if (locals[idx].Type != typeof(int)) - throw new InvalidOperationException($"Local {idx} is not an integer."); - - stack.Push(locals[idx].Load(il)); + ref var loc = ref locals[idx]; + AssertCast(loc, typeof(int)); + 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)); + stack.Push(loc.Load(il)); + break; + } + case OpCodeMnemonic.ALoad: case OpCodeMnemonic.ALoad0: case OpCodeMnemonic.ALoad1: @@ -208,8 +285,8 @@ public sealed class AssemblyBuilder _ => -1, }; - if (locals[idx] is not { Type.IsClass: true }) - throw new InvalidOperationException($"Local {idx} is not a class."); + 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; @@ -217,18 +294,42 @@ public sealed class AssemblyBuilder case OpCodeMnemonic.AALoad: { - var value = stack.Pop(); + var idx = stack.Pop(); + var array = stack.Pop(); - if (!value.Type.IsArray) - throw new InvalidOperationException("Value is not an array."); + AssertIntCompatible(idx); + AssertArray(array.Type); - if (value.Type.GetElementType() is not { IsClass: true } elementType) + if (array.Type.GetElementType() is not { IsClass: true } elementType) throw new InvalidOperationException("Array element is not an object."); il.Emit(OpCodes.Ldelem_Ref, elementType); 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)); + 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: @@ -237,21 +338,59 @@ public sealed class AssemblyBuilder case OpCodeMnemonic.LStore3: { var value = stack.Pop(); - CheckCast(value, typeof(long)); - ref var loc = ref locals[opcode.Mnemonic switch + AssertCast(value, typeof(long)); + 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)); - loc = Value.Local(typeof(long), localCount++); + 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)); + 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.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.Emit(OpCodes.Stelem_Ref); + break; + } + case OpCodeMnemonic.Pop: { stack.Pop(); @@ -268,12 +407,101 @@ public sealed class AssemblyBuilder break; } + case OpCodeMnemonic.IAdd: + case OpCodeMnemonic.ISub: + case OpCodeMnemonic.IMul: + case OpCodeMnemonic.IDiv: + case OpCodeMnemonic.IRem: + case OpCodeMnemonic.IOr: + case OpCodeMnemonic.IAnd: + case OpCodeMnemonic.IShr: + case OpCodeMnemonic.IShl: + 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.IShr => OpCodes.Shr, + OpCodeMnemonic.IShl => OpCodes.Shl, + OpCodeMnemonic.IUShr => OpCodes.Shr_Un, + _ => throw new UnreachableException(), + }); + 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: + { + 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, + _ => throw new UnreachableException(), + }); + stack.Push(new Value(typeof(long))); + break; + } + + case OpCodeMnemonic.IfEq: { var value = stack.Pop(); - if (value.Type != typeof(int) && value.Type != typeof(bool)) - throw new InvalidOperationException($"Expected integer, found {value.Type}."); - + 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; } @@ -287,6 +515,12 @@ public sealed class AssemblyBuilder il.Emit(OpCodes.Brfalse, JmpLabel()); break; } + + case OpCodeMnemonic.Goto: + { + il.Emit(OpCodes.Br, JmpLabel()); + break; + } case OpCodeMnemonic.IReturn: case OpCodeMnemonic.LReturn: @@ -335,7 +569,7 @@ public sealed class AssemblyBuilder var field = parent.GetField(fieldRef.Field.Name); var value = stack.Pop(); - CheckCast(value, field.FieldType); + AssertCast(value, field.FieldType); il.Emit(OpCodes.Stfld, field); break; } @@ -350,6 +584,7 @@ public sealed class AssemblyBuilder var signature = (MethodSignature)javaClass.Loader.GetClass(methodRef.Method.Descriptor.AsMemory()) .SpecialClassMetadata!; + for (var i = 0; i < signature.ParamTypes.Count; i++) stack.Pop(); stack.Push(new Value(parent.CallMethod(methodRef.Method.Name, signature, il))); break; } @@ -394,6 +629,16 @@ public sealed class AssemblyBuilder break; } + case OpCodeMnemonic.ANewArray: + { + var len = stack.Pop(); + AssertIntCompatible(len.Type); + var type = MakeType(javaClass.GetConstant(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 @@ -413,20 +658,25 @@ public sealed class AssemblyBuilder break; } + case OpCodeMnemonic.ArrayLen: + { + var value = stack.Pop(); + AssertArray(value.Type); + il.Emit(OpCodes.Ldlen); + stack.Push(new Value(typeof(int))); + break; + } + case OpCodeMnemonic.IfNull: { - if (!stack.Pop().Type.IsClass) - throw new InvalidOperationException("Value is not an object."); - + AssertClass(stack.Pop().Type); il.Emit(OpCodes.Brfalse, JmpLabel()); break; } case OpCodeMnemonic.IfNonNull: { - if (!stack.Pop().Type.IsClass) - throw new InvalidOperationException("Value is not an object."); - + AssertClass(stack.Pop().Type); il.Emit(OpCodes.Brtrue, JmpLabel()); break; } @@ -468,6 +718,39 @@ public sealed class AssemblyBuilder opcodes.Reader.Stream.Position = start; return labels; } + + private static void AssertClass(Type type) + { + if (type is { IsClass: false, IsInterface: false }) + 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."); + } } internal class LazyMethod(MethodBase method, Action compile) @@ -508,7 +791,7 @@ internal readonly struct Value public void Store(ILGenerator il) { - if (_store is not { } store) throw new InvalidOperationException("Value cannot be stored."); + if (_store is not {} store) throw new InvalidOperationException("Value cannot be stored."); store(il, _data); } @@ -651,4 +934,9 @@ internal readonly struct Value }, }; } + + public override string ToString() + { + return $"{Type.Name} {_data}"; + } } \ No newline at end of file diff --git a/CLI/Program.cs b/CLI/Program.cs index 8501dff..3c2a2d2 100755 --- a/CLI/Program.cs +++ b/CLI/Program.cs @@ -15,7 +15,7 @@ public class Program loader.AddPath("../extracted_java_classes/java.base/"); var javaClass = loader.LoadClass("Main".AsMemory()); Console.WriteLine($"Load time: {(DateTime.Now - now).TotalMilliseconds}ms"); - + var builder = new AssemblyBuilder(new AssemblyName("Test"), false); var dotnetClass = builder.MakeType(javaClass); try diff --git a/CLI/Types/java/lang/Array.cs b/CLI/Types/java/lang/Array.cs index f666373..14b6da4 100644 --- a/CLI/Types/java/lang/Array.cs +++ b/CLI/Types/java/lang/Array.cs @@ -28,9 +28,12 @@ public class Array(Class javaClass) : TypeSurrogate public sealed class ArrayOf(TypeSurrogate elementType, Class javaClass) : Array(javaClass) { - public override Type Type => typeof(System.Array); + public override Type Type { get; } = elementType.Type.MakeArrayType(); public readonly TypeSurrogate ElementType = elementType; + private MethodInfo CloneMethod + => field ??= Type.GetMethod("Clone", BindingFlags.Instance | BindingFlags.Public)!; + public override FieldInfo GetField(string name) { @@ -44,6 +47,14 @@ public sealed class ArrayOf(TypeSurrogate elementType, Class javaClass) : Array( public override Type CallMethod(string name, MethodSignature signature, ILGenerator il) { + switch (name, signature.ParamTypes) + { + case ("clone", []): + { + il.Emit(OpCodes.Callvirt, CloneMethod); + return Type; + } + } throw new NotImplementedException(); } } \ No newline at end of file diff --git a/CLI/Types/java/lang/Short.cs b/CLI/Types/java/lang/Short.cs new file mode 100644 index 0000000..bf4c261 --- /dev/null +++ b/CLI/Types/java/lang/Short.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Reflection.Emit; +using JCIL.Java.Class; + +namespace JCIL.CLI.Types.Java.Lang; + +public sealed class Short(Class javaClass) : TypeSurrogate +{ + public override Type Type => typeof(short); + public readonly Class Original = javaClass; + + public override FieldInfo GetField(string name) + { + throw new NotImplementedException(); + } + + public override MethodBase GetMethod(string name, MethodSignature signature) + { + throw new NotImplementedException(); + } + + public override Type CallMethod(string name, MethodSignature signature, ILGenerator il) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/JavaClass/Method.cs b/JavaClass/Method.cs index ef235d5..c6b3156 100755 --- a/JavaClass/Method.cs +++ b/JavaClass/Method.cs @@ -320,6 +320,8 @@ public struct OpCode } case OpCodeMnemonic.InvokeStatic: + case OpCodeMnemonic.InvokeVirtual: + case OpCodeMnemonic.InvokeSpecial: { var methodRef = javaClass.GetConstant(P0.UShort); return $"{Mnemonic} {methodRef.Class}.{methodRef.Method.Name}";