Preliminary code analysis
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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
@@ -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);
|
||||
|
||||
|
||||
+17
-7
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user