Preliminary code analysis

This commit is contained in:
Mia
2026-05-08 14:53:21 +02:00
parent 5ad527293c
commit d38760e52f
8 changed files with 313 additions and 33 deletions
+6 -6
View File
@@ -1,11 +1,9 @@
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;
@@ -27,11 +25,13 @@ public sealed class AssemblyBuilder
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(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);
@@ -114,9 +114,10 @@ public sealed class AssemblyBuilder
}
}
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)
{
@@ -266,7 +267,6 @@ public sealed class AssemblyBuilder
break;
}
case OpCodeMnemonic.FLoad:
case OpCodeMnemonic.FLoad0:
case OpCodeMnemonic.FLoad1:
+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
{
+7 -5
View File
@@ -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)
{
+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);
+16 -6
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)
{
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)
{