Compare commits

..

2 Commits

Author SHA1 Message Date
shy_mia d38760e52f Preliminary code analysis 2026-05-08 14:54:47 +02:00
shy_mia 5ad527293c Some opcodes and casts 2026-05-08 11:01:12 +02:00
9 changed files with 503 additions and 102 deletions
+180 -73
View File
@@ -1,14 +1,21 @@
using System.Collections.Concurrent;
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.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;
@@ -16,12 +23,15 @@ public sealed class AssemblyBuilder
{
public readonly System.Reflection.Emit.AssemblyBuilder Assembly;
public readonly ModuleBuilder Module;
private readonly ConcurrentDictionary<Class, TypeRepresentation> _classes = [];
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(AssemblyName name, bool runnable)
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);
@@ -31,78 +41,83 @@ public sealed class AssemblyBuilder
public TypeRepresentation MakeType(Class javaClass)
{
if (_classes.TryGetValue(javaClass, out var type))
return type;
switch (javaClass.SpecialClassMetadata)
lock (_classes)
{
case IntrinsicMetadata intrinsic:
if (_classes.TryGetValue(javaClass, out var type))
return type;
switch (javaClass.SpecialClassMetadata)
{
switch (intrinsic.Type)
case IntrinsicMetadata intrinsic:
{
case IntrinsicType.Byte:
return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.Byte(this, c));
case IntrinsicType.Char:
return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.Char(this, c));
case IntrinsicType.Int:
return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.Integer(this, c));
case IntrinsicType.Short:
return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.Short(this, c));
case IntrinsicType.Long:
return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.Long(this, c));
case IntrinsicType.Boolean:
return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.Boolean(this, c));
case IntrinsicType.Float:
return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.Float(this, c));
case IntrinsicType.Double:
return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.Double(this, c));
case IntrinsicType.Void:
return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.Void(this, c));
default: throw new NotImplementedException($"Unimplemented Intrinsic {intrinsic.Type}");
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;
}
}
case ArrayMetadata array:
return _classes.GetOrAdd(javaClass, c => new ArrayOf(this, MakeType(array.ElementClass), c));
}
if (javaClass.Namespace.Span is "java/lang")
{
if(javaClass.Name.Span is "String")
return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.String(this, c));
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}";
if(javaClass.Name.Span is "Object")
return _classes.GetOrAdd(javaClass, c => new JCIL.CLI.Types.Java.Lang.Object(this, c));
}
var builder = Module.DefineType(name, flags);
var newClass = new NewClass(this, builder, javaClass);
_classes[javaClass] = newClass;
_casts[builder] = CollectTypeCasts(javaClass);
var newClass = (NewClass)_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.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.TypeBuilder.SetParent(MakeType(super).Type);
if (javaClass.SuperClass is {} super)
builder.SetParent(MakeType(super).Type);
foreach (var javaInterface in javaClass.Interfaces)
newClass.TypeBuilder.AddInterfaceImplementation(MakeType(javaInterface).Type);
foreach (var javaInterface in javaClass.Interfaces)
builder.AddInterfaceImplementation(MakeType(javaInterface).Type);
newClass.CreateFields();
return newClass;
newClass.CreateFields();
return newClass;
}
}
}
internal void CompileOpCodes(Class javaClass, MethodBase methodBase, ILGenerator il, Code code,
OpCodeReader opcodes)
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)
{
@@ -252,7 +267,6 @@ public sealed class AssemblyBuilder
break;
}
case OpCodeMnemonic.FLoad:
case OpCodeMnemonic.FLoad0:
case OpCodeMnemonic.FLoad1:
@@ -448,6 +462,7 @@ public sealed class AssemblyBuilder
case OpCodeMnemonic.IRem:
case OpCodeMnemonic.IOr:
case OpCodeMnemonic.IAnd:
case OpCodeMnemonic.IXor:
case OpCodeMnemonic.IShl:
case OpCodeMnemonic.IShr:
case OpCodeMnemonic.IUShr:
@@ -465,6 +480,7 @@ public sealed class AssemblyBuilder
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,
@@ -473,6 +489,15 @@ public sealed class AssemblyBuilder
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:
@@ -506,6 +531,7 @@ public sealed class AssemblyBuilder
stack.Push(new Value(typeof(long)));
break;
}
case OpCodeMnemonic.IntToLong:
{
@@ -524,6 +550,27 @@ public sealed class AssemblyBuilder
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:
{
@@ -587,6 +634,18 @@ public sealed class AssemblyBuilder
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();
@@ -597,6 +656,30 @@ public sealed class AssemblyBuilder
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:
@@ -783,6 +866,13 @@ public sealed class AssemblyBuilder
break;
}
case OpCodeMnemonic.AThrow:
{
stack.Pop();
il.Emit(OpCodes.Throw);
break;
}
case OpCodeMnemonic.CheckCast:
{
var type = MakeType(javaClass.GetConstant<Class>(opcode.P0.UShort));
@@ -819,7 +909,7 @@ public sealed class AssemblyBuilder
il.Emit(OpCodes.Brtrue, JmpLabel());
break;
}
default:
{
Console.ForegroundColor = ConsoleColor.Red;
@@ -858,7 +948,7 @@ public sealed class AssemblyBuilder
return labels;
}
internal static void AssertCast(Value value, Type type, ILGenerator il)
internal void AssertCast(Value value, Type type, ILGenerator il)
{
if(ReferenceEquals(type, value.Type))
return;
@@ -941,18 +1031,35 @@ public sealed class AssemblyBuilder
throw new InvalidOperationException($"Value of type `{type}` is not an integer.");
}
private static bool IsAssignableTo(Type from, Type to)
private bool IsAssignableTo(Type from, Type to)
{
if (ReferenceEquals(from, to)) return true;
if(from.BaseType is {} baseType && IsAssignableTo(baseType, to))
return true;
foreach (var type in from.GetInterfaces())
if(IsAssignableTo(type, 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
+258
View File
@@ -0,0 +1,258 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using JCIL.Java.Class;
namespace JCIL.CLI;
public sealed class Block
{
public Class? CatchType { get; init; }
public required ReadOnlyMemory<OpCode> OpCodes { get; init; }
public IReadOnlyList<Class> Locals { get; private set; } = [];
public IReadOnlyList<Class> Stack { get; private set; } = [];
public int Start => OpCodes.Span[0].Position;
public override string ToString()
{
return $"Block_{OpCodes.Span[0].Position:X04}";
}
}
public sealed class CodeAnalysis
{
public required SortedDictionary<int, Block> Blocks { get; init; }
public static CodeAnalysis Analyze(TypeLoader loader, Method method)
{
if (!method.TryGetCode(out var code))
throw new ArgumentException("Method does not have a body.");
using var reader = code.CreateOpCodeReader();
var opcodes = reader.ToArray();
Class? catchType = null;
var blocks = new SortedDictionary<int, Block>();
for (int i = 0, start = 0, count = 0; i < opcodes.Length; i++, count++)
{
ref readonly var opcode = ref opcodes[i];
switch (opcode.Mnemonic)
{
case OpCodeMnemonic.IfEq:
case OpCodeMnemonic.IfNe:
case OpCodeMnemonic.IfLt:
case OpCodeMnemonic.IfGt:
case OpCodeMnemonic.IfLe:
case OpCodeMnemonic.IfGe:
case OpCodeMnemonic.IfICmpEq:
case OpCodeMnemonic.IfICmpNe:
case OpCodeMnemonic.IfICmpLt:
case OpCodeMnemonic.IfICmpGt:
case OpCodeMnemonic.IfICmpLe:
case OpCodeMnemonic.IfICmpGe:
case OpCodeMnemonic.IfACmpEq:
case OpCodeMnemonic.IfACmpNe:
case OpCodeMnemonic.Goto:
case OpCodeMnemonic.IReturn:
case OpCodeMnemonic.LReturn:
case OpCodeMnemonic.FReturn:
case OpCodeMnemonic.DReturn:
case OpCodeMnemonic.AReturn:
case OpCodeMnemonic.Return:
case OpCodeMnemonic.AThrow:
case OpCodeMnemonic.IfNull:
case OpCodeMnemonic.IfNonNull:
case OpCodeMnemonic.TableSwitch:
case OpCodeMnemonic.LookupSwitch:
{
var blockOpCodes = opcodes.AsMemory(start, ++count);
blocks.Add(opcodes[start].Position, new Block { OpCodes = blockOpCodes, CatchType = catchType });
if (i != opcodes.Length - 1)
{
var nextPosition = opcodes[i + 1].Position;
catchType = code.ExceptionTable.FirstOrDefault(e => e.Handler == nextPosition).CatchType;
}
start = i + 1;
count = -1;
break;
}
}
}
Block ResolveBlock(int addr)
{
if (blocks.TryGetValue(addr, out var block))
return block;
// Split block if an instruction jumps in the middle of it
foreach (var (key, nextBlock) in blocks)
{
if(key <= addr) block = nextBlock;
else break;
}
var span = block!.OpCodes.Span;
for (var i = 0; i < span.Length; i++)
{
if (span[i].Position != addr) continue;
blocks[span[0].Position] = new Block
{
OpCodes = block.OpCodes[..i],
CatchType = block.CatchType,
};
return blocks[addr] = new Block
{
OpCodes = block.OpCodes[i..],
CatchType = block.CatchType,
};
}
throw new UnreachableException();
}
foreach (ref var opcode in opcodes.AsSpan())
{
switch (opcode.Mnemonic)
{
case OpCodeMnemonic.IfEq:
case OpCodeMnemonic.IfNe:
case OpCodeMnemonic.IfLt:
case OpCodeMnemonic.IfGt:
case OpCodeMnemonic.IfLe:
case OpCodeMnemonic.IfGe:
case OpCodeMnemonic.IfICmpEq:
case OpCodeMnemonic.IfICmpNe:
case OpCodeMnemonic.IfICmpLt:
case OpCodeMnemonic.IfICmpGt:
case OpCodeMnemonic.IfICmpLe:
case OpCodeMnemonic.IfICmpGe:
case OpCodeMnemonic.IfACmpEq:
case OpCodeMnemonic.IfACmpNe:
case OpCodeMnemonic.Goto:
case OpCodeMnemonic.IfNull:
case OpCodeMnemonic.IfNonNull:
{
opcode.P0.Object = ResolveBlock(opcode.Position + (short)opcode.P0.UShort);
break;
}
case OpCodeMnemonic.TableSwitch:
{
var tableSwitch = (TableSwitch) opcode.P0.Object!;
var targets = new Block[tableSwitch.Offsets.Length + 1];
opcode.P1.Object = targets;
targets[0] = ResolveBlock(opcode.Position + tableSwitch.Default);
for (var i = 0; i < tableSwitch.Offsets.Length; i++)
targets[i + 1] = ResolveBlock(opcode.Position + tableSwitch.Offsets[i]);
break;
}
case OpCodeMnemonic.LookupSwitch:
{
var tableSwitch = (LookupSwitch) opcode.P0.Object!;
var targets = new Block[tableSwitch.Entries.Length + 1];
opcode.P1.Object = targets;
targets[0] = ResolveBlock(opcode.Position + tableSwitch.Default);
for (var i = 0; i < tableSwitch.Entries.Length; i++)
targets[i + 1] = ResolveBlock(opcode.Position + tableSwitch.Entries[i].Value);
break;
}
}
}
if(code.Attributes.OfType<StackMapTable>().FirstOrDefault() is not {} stackMap)
return new CodeAnalysis { Blocks = blocks };
foreach (var frame in stackMap.Frames)
ResolveBlock(frame.StartOffset);
var stack = new List<Class>(code.MaxStack);
var locals = new List<Class>(code.MaxLocals);
if((method.AccessFlags & MethodAccessFlags.Static) == 0)
locals.Add(method.DeclaringClass);
Class ResolveClass(StackMapTable.VerificationTypeInfo info)
{
switch (info.Tag)
{
case StackMapTable.VerificationTypeInfoTag.Top: return locals[^1];
case StackMapTable.VerificationTypeInfoTag.Integer: return loader.Int;
case StackMapTable.VerificationTypeInfoTag.Float: return loader.Float;
case StackMapTable.VerificationTypeInfoTag.Double: return loader.Double;
case StackMapTable.VerificationTypeInfoTag.Long: return loader.Long;
case StackMapTable.VerificationTypeInfoTag.Object: return method.DeclaringClass.GetConstant<Class>(info.Parameter);
case StackMapTable.VerificationTypeInfoTag.Null:
case StackMapTable.VerificationTypeInfoTag.UninitializedThis:
case StackMapTable.VerificationTypeInfoTag.Uninitialized:
default: throw new NotImplementedException(info.Tag.ToString());
}
}
locals.AddRange(method.Signature.ParamTypes);
foreach (var block in blocks.Values)
{
if(block.Start >= stackMap.Frames[0].StartOffset) break;
LocalsRef(block) = locals.ToArray();
}
for (var i = 0; i < stackMap.Frames.Count; i++)
{
var frame = stackMap.Frames[i];
var end = i == stackMap.Frames.Count - 1 ? code.Bytecode.Length : stackMap.Frames[i + 1].StartOffset;
switch (frame)
{
case StackMapTable.SameFrame:
case StackMapTable.SameFrameExtended:
stack.Clear();
break;
case StackMapTable.FullFrame f:
stack.Clear();
locals.Clear();
locals.AddRange(f.Locals.Select(ResolveClass));
stack.AddRange(f.StackItems.Select(ResolveClass));
break;
case StackMapTable.ChopFrame f:
stack.Clear();
locals.RemoveRange(locals.Count - f.Count, f.Count);
break;
case StackMapTable.AppendFrame f:
stack.Clear();
locals.AddRange(f.Locals.Select(ResolveClass));
break;
case StackMapTable.OneTempFrame f:
stack.Clear();
stack.Add(ResolveClass(f.Entry));
break;
case StackMapTable.OneTempFrameExtended f:
stack.Clear();
stack.Add(ResolveClass(f.Entry));
break;
default: throw new NotImplementedException(frame.ToString());
}
foreach (var block in blocks.Values)
{
if(block.Start < frame.StartOffset) continue;
if(block.Start >= end) break;
LocalsRef(block) = locals.ToArray();
StackRef(block) = stack.ToArray();
}
}
return new CodeAnalysis { Blocks = blocks };
}
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "<Locals>k__BackingField")]
private static extern ref IReadOnlyList<Class> LocalsRef(Block block);
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "<Stack>k__BackingField")]
private static extern ref IReadOnlyList<Class> StackRef(Block block);
}
+1 -1
View File
@@ -16,7 +16,7 @@ public class Program
var javaClass = loader.LoadClass("Main".AsMemory());
Console.WriteLine($"Load time: {(DateTime.Now - now).TotalMilliseconds}ms");
var builder = new AssemblyBuilder(new AssemblyName("Test"), false);
var builder = new AssemblyBuilder(loader, new AssemblyName("Test"), false);
var dotnetClass = builder.MakeType(javaClass);
try
{
+8 -6
View File
@@ -34,7 +34,7 @@ public class NewClass : TypeRepresentation
internal override void SetField(string name, Value value, ILGenerator il)
{
var field = _fields[name];
AssemblyBuilder.AssertCast(value, field.FieldType, il);
Builder.AssertCast(value, field.FieldType, il);
il.Emit(field.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld, field);
}
@@ -77,14 +77,16 @@ public class NewClass : TypeRepresentation
if (original is null)
return method;
if (!original.TryGetCode(out var code, out var reader))
if (!original.TryGetCode(out var code))
return method;
var blocks = CodeAnalysis.Analyze(Builder.TypeLoader, original);
if (method is MethodBuilder methodBuilder)
Builder.CompileOpCodes(original.DeclaringClass, method, methodBuilder.GetILGenerator(), code, reader);
Builder.CompileOpCodes(original.DeclaringClass, method, methodBuilder.GetILGenerator(), code);
if (method is ConstructorBuilder constructorBuilder)
Builder.CompileOpCodes(original.DeclaringClass, method, constructorBuilder.GetILGenerator(), code, reader);
Builder.CompileOpCodes(original.DeclaringClass, method, constructorBuilder.GetILGenerator(), code);
return method;
}
@@ -184,11 +186,11 @@ public abstract class TypeSurrogate : NewClass
if (original is null)
return method;
if (!original.TryGetCode(out var code, out var reader))
if (!original.TryGetCode(out var code))
return method;
var methodBuilder = (MethodBuilder) method;
Builder.CompileOpCodes(Original, method, methodBuilder.GetILGenerator(), code, reader);
Builder.CompileOpCodes(Original, method, methodBuilder.GetILGenerator(), code);
return method;
}
+1 -1
View File
@@ -1,6 +1,6 @@
using JCIL.Java.Class;
namespace JCIL.CLI.Types.java.lang;
namespace JCIL.CLI.Types.Java.Lang;
public class Array(AssemblyBuilder builder, Class javaClass) : TypeSurrogate(builder, javaClass)
{
+15 -1
View File
@@ -1,7 +1,9 @@
using System.Runtime.CompilerServices;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace JCIL.Core;
[SuppressMessage("ReSharper", "InconsistentNaming")]
public static class ILHelpers
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@@ -29,4 +31,16 @@ public static class ILHelpers
if (a < b) return -1;
return 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static bool ICmpGe(int a, int b)
{
return a >= b;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static bool ICmpLe(int a, int b)
{
return a <= b;
}
}
+12 -4
View File
@@ -1,3 +1,5 @@
using System.Runtime.InteropServices;
namespace JCIL.Java.Class;
public abstract class Attribute
@@ -100,6 +102,12 @@ public sealed class Code : Attribute
};
}
public OpCodeReader CreateOpCodeReader()
{
MemoryMarshal.TryGetArray(Bytecode, out var array);
return new OpCodeReader(new MemoryStream(array.Array!, array.Offset, array.Count));
}
public record struct ExceptionTableEntry(ushort Start, ushort End, ushort Handler, Class CatchType);
}
@@ -162,13 +170,13 @@ public sealed class StackMapTable : Attribute
case < 128:
delta = frameType - 64 + one;
offset += delta;
frame = new OneLocalFrame(offset, VerificationTypeInfo.Read(reader));
frame = new OneTempFrame(offset, VerificationTypeInfo.Read(reader));
break;
case 247:
delta = reader.ReadUInt16() + one;
offset += delta;
frame = new OneLocalFrameExtended(offset, VerificationTypeInfo.Read(reader));
frame = new OneTempFrameExtended(offset, VerificationTypeInfo.Read(reader));
break;
case 248 or 249 or 250:
@@ -236,9 +244,9 @@ public sealed class StackMapTable : Attribute
public sealed record SameFrameExtended(int StartOffset) : Frame(StartOffset);
public sealed record OneLocalFrame(int StartOffset, VerificationTypeInfo Local) : Frame(StartOffset);
public sealed record OneTempFrame(int StartOffset, VerificationTypeInfo Entry) : Frame(StartOffset);
public sealed record OneLocalFrameExtended(int StartOffset, VerificationTypeInfo Local) : Frame(StartOffset);
public sealed record OneTempFrameExtended(int StartOffset, VerificationTypeInfo Entry) : Frame(StartOffset);
public sealed record AppendFrame(int StartOffset, VerificationTypeInfo[] Locals) : Frame(StartOffset);
+18 -8
View File
@@ -1,5 +1,6 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace JCIL.Java.Class;
@@ -53,6 +54,7 @@ public sealed class Method
public readonly string Name;
public readonly Class Type;
public readonly IReadOnlyList<Attribute> Attributes;
public MethodSignature Signature => Unsafe.As<MethodSignature>(Type.SpecialClassMetadata)!;
private Method(Class declaringClass, MethodAccessFlags flags, string name, Class type, Attribute[] attributes)
{
@@ -80,18 +82,15 @@ public sealed class Method
return new Method(parent, flags, name, descriptorClass, attributes);
}
public bool TryGetCode([MaybeNullWhen(false)] out Code code, out OpCodeReader opcodes)
public bool TryGetCode([MaybeNullWhen(false)] out Code code)
{
if (Attributes.OfType<Code>().FirstOrDefault() is { } attr)
if (Attributes.OfType<Code>().FirstOrDefault() is {} attr)
{
MemoryMarshal.TryGetArray(attr.Bytecode, out var array);
code = attr;
opcodes = new OpCodeReader(new MemoryStream(array.Array!));
return true;
}
code = null!;
opcodes = default;
return false;
}
@@ -308,6 +307,7 @@ public enum OpCodeMnemonic : byte
public struct OpCode
{
public OpCodeMnemonic Mnemonic;
public int Position;
public OpCodeParameter P0;
public OpCodeParameter P1;
@@ -353,20 +353,21 @@ public struct OpCodeParameter
[FieldOffset(8)] public object? Object;
}
public struct OpCodeReader(Stream bytecode) : IEnumerator<OpCode>
public sealed class OpCodeReader(Stream bytecode) : IEnumerator<OpCode>, IEnumerable<OpCode>
{
private OpCode _current;
public Reader Reader = new(bytecode);
public bool MoveNext()
{
var position = Reader.Stream.Position;
var b = Reader.Stream.ReadByte();
if (b == -1)
{
_current = default;
return false;
}
_current = new OpCode { Mnemonic = (OpCodeMnemonic)(byte)b };
_current = new OpCode { Mnemonic = (OpCodeMnemonic)(byte)b, Position = (int) position };
switch (_current.Mnemonic)
{
case OpCodeMnemonic.PushByte:
@@ -620,6 +621,15 @@ public struct OpCodeReader(Stream bytecode) : IEnumerator<OpCode>
{
Reader.Stream.Dispose();
}
public OpCodeReader GetEnumerator()
=> this;
IEnumerator<OpCode> IEnumerable<OpCode>.GetEnumerator()
=> this;
IEnumerator IEnumerable.GetEnumerator()
=> this;
}
public sealed class TableSwitch
+10 -8
View File
@@ -11,14 +11,16 @@ public sealed class TypeLoader
private readonly ConcurrentDictionary<ReadOnlyMemory<char>, Class> _classes =
new(ReadOnlyMemoryCharComparer.Instance);
public Class Object
{
get
{
field ??= LoadClass("java/lang/Object".AsMemory());
return field;
}
}
public Class Void => field ??= GetClass("V".AsMemory());
public Class Char => field ??= GetClass("C".AsMemory());
public Class Byte => field ??= GetClass("B".AsMemory());
public Class Short => field ??= GetClass("S".AsMemory());
public Class Int => field ??= GetClass("I".AsMemory());
public Class Long => field ??= GetClass("J".AsMemory());
public Class Float => field ??= GetClass("F".AsMemory());
public Class Double => field ??= GetClass("D".AsMemory());
public Class Boolean => field ??= GetClass("Z".AsMemory());
public Class Object => field ??= LoadClass("java/lang/Object".AsMemory());
public void AddPath(string path)
{