Centralise the Ldind logic (#61)

This commit is contained in:
Patrick Stevens
2025-06-21 13:53:23 +01:00
committed by GitHub
parent f9e03f4340
commit c747d6eb3a
14 changed files with 804 additions and 266 deletions

View File

@@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>

View File

@@ -43,6 +43,7 @@ type NullaryIlOp =
| LdcI4_7
/// Push the int32 value 8 to the eval stack.
| LdcI4_8
/// Push the int32 value -1 to the eval stack.
| LdcI4_m1
/// Push a null object reference onto the stack.
| LdNull
@@ -388,6 +389,7 @@ type UnaryConstIlOp =
| Bgt_un of int32
| Ble_un of int32
| Blt_un of int32
/// Loads the local variable at a specific index onto the evaluation stack.
| Ldloc_s of uint8
| Ldloca_s of uint8
/// Load the address of an argument onto the stack.

View File

@@ -23,6 +23,7 @@ module LoggerFactory =
let makeTest () : (unit -> LogLine list) * ILoggerFactory =
// Shared sink for all loggers created by the factory.
let sink = ResizeArray ()
let isEnabled (logLevel : LogLevel) : bool = logLevel >= LogLevel.Information
let createLogger (category : string) : ILogger =
{ new ILogger with
@@ -31,9 +32,13 @@ module LoggerFactory =
member _.Dispose () = ()
}
member _.IsEnabled _logLevel = true
member _.IsEnabled l = isEnabled l
member _.Log (logLevel, eventId, state, ex, formatter) =
if not (isEnabled logLevel) then
()
else
let message =
try
formatter.Invoke (state, ex)

View File

@@ -31,7 +31,8 @@ module Roslyn =
Directory.GetFiles (runtimeDir, "*.dll")
|> Array.map (fun path -> MetadataReference.CreateFromFile path :> MetadataReference)
let compilationOptions = CSharpCompilationOptions OutputKind.ConsoleApplication
let compilationOptions =
CSharpCompilationOptions(OutputKind.ConsoleApplication).WithAllowUnsafe (true)
let compilation =
CSharpCompilation.Create (

View File

@@ -73,6 +73,18 @@ module TestCases =
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 1) ]
}
{
FileName = "Ldind.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain =
[
// `failures`
CliType.Numeric (CliNumericType.Int32 0)
// Return value
CliType.Numeric (CliNumericType.Int32 0)
]
}
{
FileName = "CustomDelegate.cs"
ExpectedReturnCode = 8

View File

@@ -29,6 +29,7 @@
<EmbeddedResource Include="sources\ResizeArray.cs" />
<EmbeddedResource Include="sources\ArgumentOrdering.cs" />
<EmbeddedResource Include="sources\CustomDelegate.cs" />
<EmbeddedResource Include="sources\Ldind.cs" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,361 @@
using System;
unsafe class LdindTest
{
static int Main(string[] args)
{
var failures = 0;
// Test Ldind.i1 (signed byte)
failures += TestLdindI1();
// Test Ldind.u1 (unsigned byte)
failures += TestLdindU1();
// Test Ldind.i2 (signed short)
failures += TestLdindI2();
// Test Ldind.u2 (unsigned short)
failures += TestLdindU2();
// Test Ldind.i4 (signed int)
failures += TestLdindI4();
// Test Ldind.u4 (unsigned int)
failures += TestLdindU4();
// Test Ldind.i8 (signed long)
failures += TestLdindI8();
// Test Ldind.i8 via u8 (there's no Ldind.u8)
failures += TestLdindI8ViaU8();
// Test Ldind.r4 (float)
failures += TestLdindR4();
// Test Ldind.r8 (double)
failures += TestLdindR8();
// Test truncation behavior
failures += TestTruncation();
// Test with managed pointers (ref)
// failures += TestManagedPointers();
// Test Ldind.i (native int)
// failures += TestLdindI();
return failures;
}
static int TestLdindI1()
{
sbyte value = -128;
sbyte* ptr = &value;
sbyte loaded = *ptr; // This generates ldind.i1
if (value != loaded)
{
return 1;
}
value = 127;
loaded = *ptr;
if (value != loaded)
{
return 1;
}
return 0;
}
static int TestLdindU1()
{
byte value = 255;
byte* ptr = &value;
byte loaded = *ptr; // This generates ldind.u1
if (value != loaded)
{
return 1;
}
value = 0;
loaded = *ptr;
if (value != loaded)
{
return 1;
}
return 0;
}
static int TestLdindI2()
{
short value = -32768;
short* ptr = &value;
short loaded = *ptr; // This generates ldind.i2
if (value != loaded)
{
return 1;
}
value = 32767;
loaded = *ptr;
if (value != loaded)
{
return 1;
}
return 0;
}
static int TestLdindU2()
{
ushort value = 65535;
ushort* ptr = &value;
ushort loaded = *ptr; // This generates ldind.u2
if (value != loaded)
{
return 1;
}
value = 0;
loaded = *ptr;
if (value != loaded)
{
return 1;
}
return 0;
}
static int TestLdindI4()
{
int value = int.MinValue;
int* ptr = &value;
int loaded = *ptr; // This generates ldind.i4
if (value != loaded)
{
return 1;
}
value = int.MaxValue;
loaded = *ptr;
if (value != loaded)
{
return 1;
}
return 0;
}
static int TestLdindU4()
{
uint value = uint.MaxValue;
uint* ptr = &value;
uint loaded = *ptr; // This generates ldind.u4
if (value != loaded)
{
return 1;
}
value = 0;
loaded = *ptr;
if (value != loaded)
{
return 1;
}
return 0;
}
static int TestLdindI8()
{
long value = long.MinValue;
long* ptr = &value;
long loaded = *ptr; // This generates ldind.i8
if (value != loaded)
{
return 1;
}
value = long.MaxValue;
loaded = *ptr;
if (value != loaded)
{
return 1;
}
return 0;
}
static int TestLdindI8ViaU8()
{
ulong value = ulong.MaxValue;
ulong* ptr = &value;
ulong loaded = *ptr; // This generates ldind.i8 again, because there's no ldind.u8
if (value != loaded)
{
return 1;
}
value = 0;
loaded = *ptr;
if (value != loaded)
{
return 1;
}
return 0;
}
static int TestLdindR4()
{
float value = float.MinValue;
float* ptr = &value;
float loaded = *ptr; // This generates ldind.r4
if (BitConverter.SingleToInt32Bits(value) != BitConverter.SingleToInt32Bits(loaded))
{
return 1;
}
value = float.MaxValue;
loaded = *ptr;
if (BitConverter.SingleToInt32Bits(value) != BitConverter.SingleToInt32Bits(loaded))
{
return 1;
}
value = float.NaN;
loaded = *ptr;
if (BitConverter.SingleToInt32Bits(value) != BitConverter.SingleToInt32Bits(loaded))
{
return 1;
}
return 0;
}
static int TestLdindR8()
{
double value = double.MinValue;
double* ptr = &value;
double loaded = *ptr; // This generates ldind.r8
if (BitConverter.DoubleToInt64Bits(value) != BitConverter.DoubleToInt64Bits(loaded))
{
return 1;
}
value = double.MaxValue;
loaded = *ptr;
if (BitConverter.DoubleToInt64Bits(value) != BitConverter.DoubleToInt64Bits(loaded))
{
return 1;
}
value = double.NaN;
loaded = *ptr;
if (BitConverter.DoubleToInt64Bits(value) != BitConverter.DoubleToInt64Bits(loaded))
{
return 1;
}
return 0;
}
static int TestLdindI()
{
IntPtr value = new IntPtr(42);
IntPtr* ptr = &value;
IntPtr loaded = *ptr; // This generates ldind.i
if (value != loaded)
{
return 1;
}
value = IntPtr.Zero;
loaded = *ptr;
if (value != loaded)
{
return 1;
}
return 0;
}
static int TestTruncation()
{
// Store a larger value and load as smaller type
int largeValue = 0x1234ABCD;
void* ptr = &largeValue;
byte byteValue = *(byte*)ptr; // Should truncate to 0xCD
if (byteValue != 0xCD)
{
return 1;
}
short shortValue = *(short*)ptr; // Should truncate to 0xABCD
if (shortValue != -21555)
{
return 1;
}
// Test sign extension
sbyte signedByte = *(sbyte*)ptr; // 0xCD as signed byte is -51
if (signedByte != -51)
{
return 1;
}
return 0;
}
static int TestManagedPointers()
{
int value = 42;
ref int refValue = ref value;
var expected42 = TestRefParameter(ref refValue);
if (expected42 != 42)
{
return 1;
}
if (refValue != 84)
{
return 1;
}
// Test with array element
int[] array = { 10, 20, 30 };
ref int element = ref array[1];
if (element != 20)
{
return 1;
}
// Test with local variable
int local = 100;
var expected100 = TestRefLocal(ref local);
if (expected100 != 100)
{
return 1;
}
return 0;
}
static int TestRefParameter(ref int param)
{
var result = 0;
// This will use ldind instructions when accessing param
result += param;
param = 84;
return result;
}
static int TestRefLocal(ref int local)
{
// Create a ref local
ref int refLocal = ref local;
return refLocal;
}
}

View File

@@ -66,7 +66,7 @@ module AbstractMachine =
let methodPtr =
match delegateToRun.Fields.["_methodPtr"] with
| CliType.Numeric (CliNumericType.ProvenanceTrackedNativeInt64 mi) -> mi
| _ -> failwith "unexpectedly not a method pointer in delegate invocation"
| d -> failwith $"unexpectedly not a method pointer in delegate invocation: {d}"
let typeGenerics =
instruction.ExecutingMethod.DeclaringType.Generics |> ImmutableArray.CreateRange
@@ -86,6 +86,9 @@ module AbstractMachine =
(state, instruction.Arguments)
||> Seq.fold (fun state arg -> IlMachineState.pushToEvalStack arg thread state)
let state, _ =
state.WithThreadSwitchedToAssembly methodPtr.DeclaringType.Assembly thread
// Don't advance the program counter again on return; that was already done by the Callvirt that
// caused this delegate to be invoked.
let state, result =
@@ -160,7 +163,9 @@ module AbstractMachine =
| Some instructions ->
match instructions.Locations.TryGetValue instruction.IlOpIndex with
| false, _ -> failwith "Wanted to execute a nonexistent instruction"
| false, _ ->
failwith
$"Wanted to execute a nonexistent instruction in {instruction.ExecutingMethod.DeclaringType.Name}.{instruction.ExecutingMethod.Name}"
| true, executingInstruction ->
let executingInType =
@@ -172,8 +177,9 @@ module AbstractMachine =
| false, _ -> "<unrecognised type>"
logger.LogInformation (
"Executing one step (index {ExecutingIlOpIndex} in method {ExecutingMethodType}.{ExecutingMethodName}): {ExecutingIlOp}",
"Executing one step (index {ExecutingIlOpIndex}, max {MaxIlOpIndex}, in method {ExecutingMethodType}.{ExecutingMethodName}): {ExecutingIlOp}",
instruction.IlOpIndex,
(Map.maxKeyValue instruction.ExecutingMethod.Instructions.Value.Locations |> fst),
executingInType,
instruction.ExecutingMethod.Name,
executingInstruction

View File

@@ -75,9 +75,11 @@ type CliValueType =
type CliRuntimePointerSource =
| LocalVariable of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
| Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
| Heap of ManagedHeapAddress
| Null
type CliRuntimePointer =
| Unmanaged of unit
| Unmanaged of int64
| Managed of CliRuntimePointerSource
/// This is the kind of type that can be stored in arguments, local variables, statics, array elements, fields.
@@ -120,15 +122,19 @@ module CliType =
| PrimitiveType.Int16 -> CliType.Numeric (CliNumericType.Int16 0s)
| PrimitiveType.UInt16 -> CliType.Numeric (CliNumericType.UInt16 0us)
| PrimitiveType.Int32 -> CliType.Numeric (CliNumericType.Int32 0)
| PrimitiveType.UInt32 -> failwith "todo"
| PrimitiveType.UInt32 ->
// uint32 doesn't exist; the spec has them stored on the stack as if signed, with two's complement wraparound
CliType.Numeric (CliNumericType.Int32 0)
| PrimitiveType.Int64 -> CliType.Numeric (CliNumericType.Int64 0L)
| PrimitiveType.UInt64 -> failwith "todo"
| PrimitiveType.UInt64 ->
// uint64 doesn't exist; the spec has them stored on the stack as if signed, with two's complement wraparound
CliType.Numeric (CliNumericType.Int64 0L)
| PrimitiveType.Single -> CliType.Numeric (CliNumericType.Float32 0.0f)
| PrimitiveType.Double -> CliType.Numeric (CliNumericType.Float64 0.0)
| PrimitiveType.String -> CliType.ObjectRef None
| PrimitiveType.TypedReference -> failwith "todo"
| PrimitiveType.IntPtr -> CliType.Numeric (CliNumericType.Int64 0L)
| PrimitiveType.UIntPtr -> CliType.Numeric (CliNumericType.Int64 0L)
| PrimitiveType.IntPtr -> CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
| PrimitiveType.UIntPtr -> CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
| PrimitiveType.Object -> CliType.ObjectRef None
let rec zeroOf
@@ -144,7 +150,9 @@ module CliType =
| TypeDefn.PrimitiveType primitiveType -> CliTypeResolutionResult.Resolved (zeroOfPrimitive primitiveType)
| TypeDefn.Array _ -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
| TypeDefn.Pinned typeDefn -> failwith "todo"
| TypeDefn.Pointer _ -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
| TypeDefn.Pointer _ ->
CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
|> CliTypeResolutionResult.Resolved
| TypeDefn.Byref _ -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
| TypeDefn.OneDimensionalArrayLowerBoundZero _ -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
| TypeDefn.Modified (original, afterMod, modificationRequired) -> failwith "todo"

View File

@@ -1,7 +1,5 @@
namespace WoofWare.PawPrint
open Microsoft.FSharp.Core
type ManagedPointerSource =
| LocalVariable of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
| Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
@@ -20,12 +18,14 @@ type ManagedPointerSource =
[<RequireQualifiedAccess>]
type NativeIntSource =
| Verbatim of int64
| ManagedPointer of ManagedPointerSource
| FunctionPointer of MethodInfo<FakeUnit, GenericParameter>
override this.ToString () : string =
match this with
| NativeIntSource.Verbatim int64 -> $"%i{int64}"
| NativeIntSource.FunctionPointer (methodDefinition) ->
| NativeIntSource.ManagedPointer ptr -> $"<managed pointer {ptr}>"
| NativeIntSource.FunctionPointer methodDefinition ->
$"<pointer to {methodDefinition.Name} in {methodDefinition.DeclaringType.Assembly.Name}>"
[<RequireQualifiedAccess>]
@@ -34,11 +34,16 @@ module NativeIntSource =
match n with
| NativeIntSource.Verbatim i -> i = 0L
| NativeIntSource.FunctionPointer _ -> failwith "TODO"
| NativeIntSource.ManagedPointer src ->
match src with
| ManagedPointerSource.Null -> true
| _ -> false
let isNonnegative (n : NativeIntSource) : bool =
match n with
| NativeIntSource.Verbatim i -> i >= 0L
| NativeIntSource.FunctionPointer _ -> failwith "TODO"
| NativeIntSource.ManagedPointer _ -> true
/// True if a < b.
let isLess (a : NativeIntSource) (b : NativeIntSource) : bool =
@@ -47,7 +52,9 @@ module NativeIntSource =
| _, _ -> failwith "TODO"
[<RequireQualifiedAccess>]
type UnsignedNativeIntSource = | Verbatim of uint64
type UnsignedNativeIntSource =
| Verbatim of uint64
| FromManagedPointer of ManagedPointerSource
/// See I.12.3.2.1 for definition
type EvalStackValue =
@@ -97,12 +104,21 @@ module EvalStackValue =
uint64 i |> UnsignedNativeIntSource.Verbatim |> Some
else
failwith "todo"
| NativeIntSource.ManagedPointer _ -> failwith "TODO"
| NativeIntSource.FunctionPointer _ -> failwith "TODO"
| EvalStackValue.Float f -> failwith "todo"
| EvalStackValue.ManagedPointer managedPointerSource -> failwith "todo"
| EvalStackValue.ManagedPointer managedPointerSource ->
UnsignedNativeIntSource.FromManagedPointer managedPointerSource |> Some
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
| EvalStackValue.UserDefinedValueType _ -> failwith "todo"
/// The conversion performed by Conv_i.
let toNativeInt (value : EvalStackValue) : NativeIntSource option =
match value with
| EvalStackValue.Int64 i -> Some (NativeIntSource.Verbatim i)
| EvalStackValue.Int32 i -> Some (NativeIntSource.Verbatim (int64<int> i))
| value -> failwith $"{value}"
let convToInt32 (value : EvalStackValue) : int32 option =
match value with
| EvalStackValue.Int32 i -> Some i
@@ -150,6 +166,7 @@ module EvalStackValue =
| EvalStackValue.NativeInt src ->
match src with
| NativeIntSource.Verbatim i -> CliType.Numeric (CliNumericType.Int64 i)
| NativeIntSource.ManagedPointer ptr -> failwith "TODO"
| NativeIntSource.FunctionPointer f ->
CliType.Numeric (CliNumericType.ProvenanceTrackedNativeInt64 f)
| i -> failwith $"TODO: %O{i}"
@@ -159,14 +176,26 @@ module EvalStackValue =
match popped with
| EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.Int8 (i % 256 |> int8))
| i -> failwith $"TODO: %O{i}"
| CliNumericType.Int16 s -> failwith "todo"
| CliNumericType.UInt8 b -> failwith "todo"
| CliNumericType.UInt16 s -> failwith "todo"
| CliNumericType.Float32 f -> failwith "todo"
| CliNumericType.Int16 _ ->
match popped with
| EvalStackValue.Int32 popped -> CliType.Numeric (CliNumericType.Int16 (popped % 65536 |> int16<int>))
| _ -> failwith $"TODO: {popped}"
| CliNumericType.UInt8 _ ->
match popped with
| EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.UInt8 (i % 256 |> uint8))
| i -> failwith $"todo: {i} to uint8"
| CliNumericType.UInt16 _ ->
match popped with
| EvalStackValue.Int32 popped -> CliType.Numeric (CliNumericType.UInt16 (uint16<int32> popped))
| i -> failwith $"todo: {i} to uint16"
| CliNumericType.Float32 _ ->
match popped with
| EvalStackValue.Float f -> CliType.Numeric (CliNumericType.Float32 (float32<float> f))
| i -> failwith $"todo: {i} to float32"
| CliNumericType.Float64 _ ->
match popped with
| EvalStackValue.Float f -> CliType.Numeric (CliNumericType.Float64 f)
| _ -> failwith "todo"
| _ -> failwith $"todo: {popped} to float64"
| CliType.ObjectRef _ ->
match popped with
| EvalStackValue.ManagedPointer ptrSource ->
@@ -186,6 +215,11 @@ module EvalStackValue =
| NativeIntSource.Verbatim 0L -> CliType.ObjectRef None
| NativeIntSource.Verbatim i -> failwith $"refusing to interpret verbatim native int {i} as a pointer"
| NativeIntSource.FunctionPointer _ -> failwith "TODO"
| NativeIntSource.ManagedPointer ptr ->
match ptr with
| ManagedPointerSource.Null -> CliType.ObjectRef None
| ManagedPointerSource.Heap s -> CliType.ObjectRef (Some s)
| _ -> failwith "TODO"
| EvalStackValue.UserDefinedValueType fields ->
match fields with
| [ esv ] -> toCliTypeCoerced target esv
@@ -213,6 +247,22 @@ module EvalStackValue =
CliRuntimePointerSource.Argument (sourceThread, methodFrame, var)
|> CliRuntimePointer.Managed
|> CliType.RuntimePointer
| EvalStackValue.NativeInt intSrc ->
match intSrc with
| NativeIntSource.Verbatim i -> CliType.RuntimePointer (CliRuntimePointer.Unmanaged i)
| NativeIntSource.ManagedPointer src ->
match src with
| ManagedPointerSource.Heap src ->
CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Heap src))
| ManagedPointerSource.Null -> failwith "TODO"
| ManagedPointerSource.LocalVariable (a, b, c) ->
CliType.RuntimePointer (
CliRuntimePointer.Managed (CliRuntimePointerSource.LocalVariable (a, b, c))
)
| ManagedPointerSource.Argument (a, b, c) ->
CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Argument (a, b, c)))
| NativeIntSource.FunctionPointer methodInfo ->
CliType.Numeric (CliNumericType.ProvenanceTrackedNativeInt64 methodInfo)
| _ -> failwith $"TODO: %O{popped}"
| CliType.Char _ ->
match popped with
@@ -231,7 +281,7 @@ module EvalStackValue =
| popped ->
match fields with
| [ target ] -> toCliTypeCoerced target popped
| _ -> failwith "TODO"
| _ -> failwith $"TODO: {popped} into value type {target}"
let rec ofCliType (v : CliType) : EvalStackValue =
match v with
@@ -242,11 +292,11 @@ module EvalStackValue =
| CliNumericType.NativeInt i -> failwith "TODO"
// Sign-extend types int8 and int16
// Zero-extend unsigned int8/unsigned int16
| CliNumericType.Int8 b -> int32 b |> EvalStackValue.Int32
| CliNumericType.UInt8 b -> int32 b |> EvalStackValue.Int32
| CliNumericType.Int16 s -> int32 s |> EvalStackValue.Int32
| CliNumericType.UInt16 s -> int32 s |> EvalStackValue.Int32
| CliNumericType.Float32 f -> failwith "todo"
| CliNumericType.Int8 b -> int32<int8> b |> EvalStackValue.Int32
| CliNumericType.UInt8 b -> int32<uint8> b |> EvalStackValue.Int32
| CliNumericType.Int16 s -> int32<int16> s |> EvalStackValue.Int32
| CliNumericType.UInt16 s -> int32<uint16> s |> EvalStackValue.Int32
| CliNumericType.Float32 f -> EvalStackValue.Float (float<float32> f)
| CliNumericType.Float64 f -> EvalStackValue.Float f
| CliNumericType.NativeFloat f -> EvalStackValue.Float f
| CliNumericType.ProvenanceTrackedNativeInt64 f ->
@@ -260,7 +310,7 @@ module EvalStackValue =
| CliType.Char (high, low) -> int32 high * 256 + int32 low |> EvalStackValue.Int32
| CliType.RuntimePointer ptr ->
match ptr with
| CliRuntimePointer.Unmanaged () -> failwith "todo: unmanaged"
| CliRuntimePointer.Unmanaged _ -> failwith "todo: unmanaged"
| CliRuntimePointer.Managed ptr ->
match ptr with
| CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, var) ->
@@ -269,6 +319,8 @@ module EvalStackValue =
| CliRuntimePointerSource.Argument (sourceThread, methodFrame, var) ->
ManagedPointerSource.Argument (sourceThread, methodFrame, var)
|> EvalStackValue.ManagedPointer
| CliRuntimePointerSource.Heap addr -> EvalStackValue.ObjectRef addr
| CliRuntimePointerSource.Null -> failwith "TODO"
| CliType.ValueType fields -> fields |> List.map ofCliType |> EvalStackValue.UserDefinedValueType
type EvalStack =

View File

@@ -1,5 +1,6 @@
namespace WoofWare.PawPrint
open System
open System.Collections.Immutable
open System.IO
open System.Reflection
@@ -401,6 +402,134 @@ module IlMachineState =
cliTypeZeroOf loggerFactory corelib assy ty typeGenerics methodGenerics state
let pushToEvalStack' (o : EvalStackValue) (thread : ThreadId) (state : IlMachineState) =
let activeThreadState = state.ThreadState.[thread]
let newThreadState =
activeThreadState
|> ThreadState.pushToEvalStack' o activeThreadState.ActiveMethodState
{ state with
ThreadState = state.ThreadState |> Map.add thread newThreadState
}
let pushToEvalStack (o : CliType) (thread : ThreadId) (state : IlMachineState) : IlMachineState =
let activeThreadState = state.ThreadState.[thread]
let newThreadState =
activeThreadState
|> ThreadState.pushToEvalStack o activeThreadState.ActiveMethodState
{ state with
ThreadState = state.ThreadState |> Map.add thread newThreadState
}
let peekEvalStack (thread : ThreadId) (state : IlMachineState) : EvalStackValue option =
ThreadState.peekEvalStack state.ThreadState.[thread]
let popEvalStack (thread : ThreadId) (state : IlMachineState) : EvalStackValue * IlMachineState =
let ret, popped = ThreadState.popFromEvalStack state.ThreadState.[thread]
let state =
{ state with
ThreadState = state.ThreadState |> Map.add thread popped
}
ret, state
let advanceProgramCounter (thread : ThreadId) (state : IlMachineState) : IlMachineState =
{ state with
ThreadState =
state.ThreadState
|> Map.change
thread
(fun state ->
match state with
| None -> failwith "expected state"
| Some (state : ThreadState) -> state |> ThreadState.advanceProgramCounter |> Some
)
}
/// There might be no stack frame to return to, so you might get None.
let returnStackFrame
(loggerFactory : ILoggerFactory)
(corelib : BaseClassTypes<DumpedAssembly>)
(currentThread : ThreadId)
(state : IlMachineState)
: IlMachineState option
=
let threadStateAtEndOfMethod = state.ThreadState.[currentThread]
match threadStateAtEndOfMethod.MethodState.ReturnState with
| None -> None
| Some returnState ->
let state =
match returnState.WasInitialisingType with
| None -> state
| Some finishedInitialising -> state.WithTypeEndInit currentThread finishedInitialising
// Return to previous stack frame
let state =
{ state with
ThreadState =
state.ThreadState
|> Map.add
currentThread
{ threadStateAtEndOfMethod with
ActiveMethodState = returnState.JumpTo
ActiveAssembly =
threadStateAtEndOfMethod.MethodStates.[returnState.JumpTo].ExecutingMethod.DeclaringType
.Assembly
}
}
match returnState.WasConstructingObj with
| Some constructing ->
// Assumption: a constructor can't also return a value.
// If we were constructing a reference type, we push a reference to it.
// Otherwise, extract the now-complete object from the heap and push it to the stack directly.
let constructed = state.ManagedHeap.NonArrayObjects.[constructing]
let resolvedBaseType =
DumpedAssembly.resolveBaseType
corelib
state._LoadedAssemblies
constructed.Type.Assembly
constructed.Type.BaseType
match resolvedBaseType with
| ResolvedBaseType.Delegate
| ResolvedBaseType.Object -> state |> pushToEvalStack (CliType.OfManagedObject constructing) currentThread
| ResolvedBaseType.ValueType ->
state
|> pushToEvalStack (CliType.ValueType (Seq.toList constructed.Fields.Values)) currentThread
| ResolvedBaseType.Enum -> failwith "TODO"
| None ->
match threadStateAtEndOfMethod.MethodState.EvaluationStack.Values with
| [] ->
// no return value
state
| [ retVal ] ->
let retType =
threadStateAtEndOfMethod.MethodState.ExecutingMethod.Signature.ReturnType
match retType with
| TypeDefn.Void -> state
| retType ->
// TODO: generics
let state, zero =
cliTypeZeroOf loggerFactory corelib (state.ActiveAssembly currentThread) retType None None state
let toPush = EvalStackValue.toCliTypeCoerced zero retVal
state |> pushToEvalStack toPush currentThread
| _ ->
failwith
"Unexpected interpretation result has a local evaluation stack with more than one element on RET"
|> Some
let private safeIntrinsics =
[
// The IL implementation is fine: https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs#L677
@@ -417,9 +546,13 @@ module IlMachineState =
let callIntrinsic
(baseClassTypes : BaseClassTypes<_>)
(methodToCall : WoofWare.PawPrint.MethodInfo<TypeDefn, WoofWare.PawPrint.GenericParameter>)
(currentThread : ThreadId)
(state : IlMachineState)
: IlMachineState option
=
let callerAssy =
state.ThreadState.[currentThread].MethodState.ExecutingMethod.DeclaringType.Assembly
if
methodToCall.DeclaringType.Assembly.Name = "System.Private.CoreLib"
&& methodToCall.DeclaringType.Name = "Volatile"
@@ -447,7 +580,32 @@ module IlMachineState =
let resultFieldType = resultField.Signature
failwith "TODO"
| "System.Private.CoreLib", "BitConverter", "SingleToInt32Bits" ->
let arg, state = popEvalStack currentThread state
let result =
match arg with
| EvalStackValue.Float f -> BitConverter.SingleToInt32Bits (float32<float> f) |> EvalStackValue.Int32
| _ -> failwith "TODO"
state
|> pushToEvalStack' result currentThread
|> advanceProgramCounter currentThread
|> Some
| "System.Private.CoreLib", "BitConverter", "DoubleToInt64Bits" ->
let arg, state = popEvalStack currentThread state
let result =
match arg with
| EvalStackValue.Float f -> BitConverter.DoubleToInt64Bits f |> EvalStackValue.Int64
| _ -> failwith "TODO"
state
|> pushToEvalStack' result currentThread
|> advanceProgramCounter currentThread
|> Some
| a, b, c -> failwith $"TODO: implement JIT intrinsic {a}.{b}.{c}"
|> Option.map (fun s -> s.WithThreadSwitchedToAssembly callerAssy currentThread |> fst)
let callMethod
(loggerFactory : ILoggerFactory)
@@ -477,7 +635,7 @@ module IlMachineState =
match
if isIntrinsic then
callIntrinsic corelib methodToCall state
callIntrinsic corelib methodToCall thread state
else
None
with
@@ -523,7 +681,7 @@ module IlMachineState =
let args, afterPop =
if methodToCall.IsStatic then
// Static method: pop args in reverse order
let args = ImmutableArray.CreateBuilder (methodToCall.Parameters.Length)
let args = ImmutableArray.CreateBuilder methodToCall.Parameters.Length
let mutable currentState = activeMethodState
for i = methodToCall.Parameters.Length - 1 downto 0 do
@@ -544,7 +702,9 @@ module IlMachineState =
// Constructor: `this` is on top of stack, by our own odd little calling convention
// where Newobj puts the object pointer on top
let thisArg, newState =
popAndCoerceArg (CliType.RuntimePointer (CliRuntimePointer.Unmanaged ())) currentState
popAndCoerceArg
(CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null))
currentState
args.Add thisArg
currentState <- newState
@@ -564,7 +724,9 @@ module IlMachineState =
currentState <- newState
let thisArg, newState =
popAndCoerceArg (CliType.RuntimePointer (CliRuntimePointer.Unmanaged ())) currentState
popAndCoerceArg
(CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null))
currentState
args.Add thisArg
currentState <- newState
@@ -936,28 +1098,6 @@ module IlMachineState =
alloc, state
let pushToEvalStack' (o : EvalStackValue) (thread : ThreadId) (state : IlMachineState) =
let activeThreadState = state.ThreadState.[thread]
let newThreadState =
activeThreadState
|> ThreadState.pushToEvalStack' o activeThreadState.ActiveMethodState
{ state with
ThreadState = state.ThreadState |> Map.add thread newThreadState
}
let pushToEvalStack (o : CliType) (thread : ThreadId) (state : IlMachineState) : IlMachineState =
let activeThreadState = state.ThreadState.[thread]
let newThreadState =
activeThreadState
|> ThreadState.pushToEvalStack o activeThreadState.ActiveMethodState
{ state with
ThreadState = state.ThreadState |> Map.add thread newThreadState
}
let popFromStackToLocalVariable
(thread : ThreadId)
(localVariableIndex : int)
@@ -984,19 +1124,6 @@ module IlMachineState =
}
}
let peekEvalStack (thread : ThreadId) (state : IlMachineState) : EvalStackValue option =
ThreadState.peekEvalStack state.ThreadState.[thread]
let popEvalStack (thread : ThreadId) (state : IlMachineState) : EvalStackValue * IlMachineState =
let ret, popped = ThreadState.popFromEvalStack state.ThreadState.[thread]
let state =
{ state with
ThreadState = state.ThreadState |> Map.add thread popped
}
ret, state
let setArrayValue
(arrayAllocation : ManagedHeapAddress)
(v : CliType)
@@ -1010,19 +1137,6 @@ module IlMachineState =
ManagedHeap = heap
}
let advanceProgramCounter (thread : ThreadId) (state : IlMachineState) : IlMachineState =
{ state with
ThreadState =
state.ThreadState
|> Map.change
thread
(fun state ->
match state with
| None -> failwith "expected state"
| Some (state : ThreadState) -> state |> ThreadState.advanceProgramCounter |> Some
)
}
let jumpProgramCounter (thread : ThreadId) (bytes : int) (state : IlMachineState) : IlMachineState =
{ state with
ThreadState =
@@ -1121,86 +1235,6 @@ module IlMachineState =
state, assy.Name, Choice1Of2 method
/// There might be no stack frame to return to, so you might get None.
let returnStackFrame
(loggerFactory : ILoggerFactory)
(corelib : BaseClassTypes<DumpedAssembly>)
(currentThread : ThreadId)
(state : IlMachineState)
: IlMachineState option
=
let threadStateAtEndOfMethod = state.ThreadState.[currentThread]
match threadStateAtEndOfMethod.MethodState.ReturnState with
| None -> None
| Some returnState ->
let state =
match returnState.WasInitialisingType with
| None -> state
| Some finishedInitialising -> state.WithTypeEndInit currentThread finishedInitialising
// Return to previous stack frame
let state =
{ state with
ThreadState =
state.ThreadState
|> Map.add
currentThread
{ threadStateAtEndOfMethod with
ActiveMethodState = returnState.JumpTo
ActiveAssembly =
threadStateAtEndOfMethod.MethodStates.[returnState.JumpTo].ExecutingMethod.DeclaringType
.Assembly
}
}
match returnState.WasConstructingObj with
| Some constructing ->
// Assumption: a constructor can't also return a value.
// If we were constructing a reference type, we push a reference to it.
// Otherwise, extract the now-complete object from the heap and push it to the stack directly.
let constructed = state.ManagedHeap.NonArrayObjects.[constructing]
let resolvedBaseType =
DumpedAssembly.resolveBaseType
corelib
state._LoadedAssemblies
constructed.Type.Assembly
constructed.Type.BaseType
match resolvedBaseType with
| ResolvedBaseType.Delegate
| ResolvedBaseType.Object -> state |> pushToEvalStack (CliType.OfManagedObject constructing) currentThread
| ResolvedBaseType.ValueType ->
state
|> pushToEvalStack (CliType.ValueType (Seq.toList constructed.Fields.Values)) currentThread
| ResolvedBaseType.Enum -> failwith "TODO"
| None ->
match threadStateAtEndOfMethod.MethodState.EvaluationStack.Values with
| [] ->
// no return value
state
| [ retVal ] ->
let retType =
threadStateAtEndOfMethod.MethodState.ExecutingMethod.Signature.ReturnType
match retType with
| TypeDefn.Void -> state
| retType ->
// TODO: generics
let state, zero =
cliTypeZeroOf loggerFactory corelib (state.ActiveAssembly currentThread) retType None None state
let toPush = EvalStackValue.toCliTypeCoerced zero retVal
state |> pushToEvalStack toPush currentThread
| _ ->
failwith
"Unexpected interpretation result has a local evaluation stack with more than one element on RET"
|> Some
let setLocalVariable
(thread : ThreadId)
(stackFrame : int)

View File

@@ -31,6 +31,78 @@ module private ArithmeticOperation =
[<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module NullaryIlOp =
type private LdindTargetType =
| LdindI
| LdindI1
| LdindI2
| LdindI4
| LdindI8
| LdindU1
| LdindU2
| LdindU4
| LdindU8
| LdindR4
| LdindR8
// Helper to get the target CliType for each Ldind variant
let private getTargetLdindCliType (targetType : LdindTargetType) : CliType =
match targetType with
| LdindI -> CliType.Numeric (CliNumericType.NativeInt 0L)
| LdindI1 -> CliType.Numeric (CliNumericType.Int8 0y)
| LdindI2 -> CliType.Numeric (CliNumericType.Int16 0s)
| LdindI4 -> CliType.Numeric (CliNumericType.Int32 0)
| LdindI8 -> CliType.Numeric (CliNumericType.Int64 0L)
| LdindU1 -> CliType.Numeric (CliNumericType.UInt8 0uy)
| LdindU2 -> CliType.Numeric (CliNumericType.UInt16 0us)
| LdindU4 ->
// This doesn't actually exist as a CLI type
CliType.Numeric (CliNumericType.Int32 0)
| LdindU8 ->
// This doesn't actually exist as a CLI type
CliType.Numeric (CliNumericType.Int64 0L)
| LdindR4 -> CliType.Numeric (CliNumericType.Float32 0.0f)
| LdindR8 -> CliType.Numeric (CliNumericType.Float64 0.0)
/// Retrieve a value from a pointer
let private loadFromPointerSource (state : IlMachineState) (src : ManagedPointerSource) : CliType =
match src with
| ManagedPointerSource.Null -> failwith "unexpected null pointer in Ldind operation"
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables.[int<uint16> whichVar]
| ManagedPointerSource.Heap managedHeapAddress -> failwith "TODO: Heap pointer dereferencing not implemented"
// Unified Ldind implementation
let private executeLdind
(targetType : LdindTargetType)
(currentThread : ThreadId)
(state : IlMachineState)
: ExecutionResult
=
let popped, state = IlMachineState.popEvalStack currentThread state
let loadedValue =
match popped with
| EvalStackValue.ManagedPointer src -> loadFromPointerSource state src
| EvalStackValue.NativeInt nativeIntSource ->
failwith $"TODO: Native int pointer dereferencing not implemented for {targetType}"
| EvalStackValue.ObjectRef managedHeapAddress ->
failwith "TODO: Object reference dereferencing not implemented"
| other -> failwith $"Unexpected eval stack value for Ldind operation: {other}"
let loadedValue = loadedValue |> EvalStackValue.ofCliType
let targetCliType = getTargetLdindCliType targetType
let coercedValue = EvalStackValue.toCliTypeCoerced targetCliType loadedValue
let state =
state
|> IlMachineState.pushToEvalStack coercedValue currentThread
|> IlMachineState.advanceProgramCounter currentThread
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
let private binaryArithmeticOperation
(op : IArithmeticOperation)
(currentThread : ThreadId)
@@ -255,7 +327,12 @@ module NullaryIlOp =
|> IlMachineState.advanceProgramCounter currentThread
|> Tuple.withRight WhatWeDid.Executed
|> ExecutionResult.Stepped
| LdcI4_m1 -> failwith "TODO: LdcI4_m1 unimplemented"
| LdcI4_m1 ->
state
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 -1)) currentThread
|> IlMachineState.advanceProgramCounter currentThread
|> Tuple.withRight WhatWeDid.Executed
|> ExecutionResult.Stepped
| LdNull ->
let state =
state
@@ -404,7 +481,20 @@ module NullaryIlOp =
| And -> failwith "TODO: And unimplemented"
| Or -> failwith "TODO: Or unimplemented"
| Xor -> failwith "TODO: Xor unimplemented"
| Conv_I -> failwith "TODO: Conv_I unimplemented"
| Conv_I ->
let popped, state = IlMachineState.popEvalStack currentThread state
let converted = EvalStackValue.toNativeInt popped
let state =
match converted with
| None -> failwith "TODO: Conv_I conversion failure unimplemented"
| Some conv ->
state
|> IlMachineState.pushToEvalStack' (EvalStackValue.NativeInt conv) currentThread
let state = state |> IlMachineState.advanceProgramCounter currentThread
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
| Conv_I1 -> failwith "TODO: Conv_I1 unimplemented"
| Conv_I2 -> failwith "TODO: Conv_I2 unimplemented"
| Conv_I4 ->
@@ -453,6 +543,7 @@ module NullaryIlOp =
(conv % uint64 System.Int64.MaxValue) |> int64 |> NativeIntSource.Verbatim
else
int64 conv |> NativeIntSource.Verbatim
| UnsignedNativeIntSource.FromManagedPointer ptr -> NativeIntSource.ManagedPointer ptr
state
|> IlMachineState.pushToEvalStack' (EvalStackValue.NativeInt conv) currentThread
@@ -656,96 +747,17 @@ module NullaryIlOp =
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
| Stind_R4 -> failwith "TODO: Stind_R4 unimplemented"
| Stind_R8 -> failwith "TODO: Stind_R8 unimplemented"
| Ldind_i -> failwith "TODO: Ldind_i unimplemented"
| Ldind_i1 -> failwith "TODO: Ldind_i1 unimplemented"
| Ldind_i2 -> failwith "TODO: Ldind_i2 unimplemented"
| Ldind_i4 ->
let popped, state = IlMachineState.popEvalStack currentThread state
let value =
let load (c : CliType) =
match c with
| CliType.Bool _ -> failwith "bool"
| CliType.Numeric numeric ->
match numeric with
| CliNumericType.Int32 i -> i
| _ -> failwith $"TODO: {numeric}"
| CliType.Char _ -> failwith "tried to load a Char as a i4"
| CliType.ObjectRef _ -> failwith "tried to load an ObjectRef as a i4"
| CliType.RuntimePointer _ -> failwith "tried to load a RuntimePointer as a i4"
| CliType.ValueType cliTypes -> failwith "todo"
match popped with
| EvalStackValue.ManagedPointer src ->
match src with
| ManagedPointerSource.Null -> failwith "unexpected null pointer in Ldind_i4"
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
let methodState =
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
load methodState
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
let methodState =
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables
.[int<uint16> whichVar]
load methodState
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
| s -> failwith $"TODO(Ldind_i4): {s}"
let state =
state
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 value)) currentThread
|> IlMachineState.advanceProgramCounter currentThread
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
| Ldind_i8 -> failwith "TODO: Ldind_i8 unimplemented"
| Ldind_u1 ->
let popped, state = IlMachineState.popEvalStack currentThread state
let value =
let load (c : CliType) =
match c with
| CliType.Bool b -> b
| CliType.Numeric numeric -> failwith $"tried to load a Numeric as a u8: {numeric}"
| CliType.Char _ -> failwith "tried to load a Char as a u8"
| CliType.ObjectRef _ -> failwith "tried to load an ObjectRef as a u8"
| CliType.RuntimePointer _ -> failwith "tried to load a RuntimePointer as a u8"
| CliType.ValueType cliTypes -> failwith "todo"
match popped with
| EvalStackValue.NativeInt nativeIntSource -> failwith $"TODO: in Ldind_u1, {nativeIntSource}"
| EvalStackValue.ManagedPointer src ->
match src with
| ManagedPointerSource.Null -> failwith "unexpected null pointer in Ldind_u1"
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
let methodState =
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
load methodState
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
let methodState =
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables
.[int<uint16> whichVar]
load methodState
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
| popped -> failwith $"unexpected Ldind_u1 input: {popped}"
let state =
state
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.UInt8 value)) currentThread
|> IlMachineState.advanceProgramCounter currentThread
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
| Ldind_u2 -> failwith "TODO: Ldind_u2 unimplemented"
| Ldind_u4 -> failwith "TODO: Ldind_u4 unimplemented"
| Ldind_i -> executeLdind LdindTargetType.LdindI currentThread state
| Ldind_i1 -> executeLdind LdindTargetType.LdindI1 currentThread state
| Ldind_i2 -> executeLdind LdindTargetType.LdindI2 currentThread state
| Ldind_i4 -> executeLdind LdindTargetType.LdindI4 currentThread state
| Ldind_i8 -> executeLdind LdindTargetType.LdindI8 currentThread state
| Ldind_u1 -> executeLdind LdindTargetType.LdindU1 currentThread state
| Ldind_u2 -> executeLdind LdindTargetType.LdindU2 currentThread state
| Ldind_u4 -> executeLdind LdindTargetType.LdindU4 currentThread state
| Ldind_u8 -> failwith "TODO: Ldind_u8 unimplemented"
| Ldind_r4 -> failwith "TODO: Ldind_r4 unimplemented"
| Ldind_r8 -> failwith "TODO: Ldind_r8 unimplemented"
| Ldind_r4 -> executeLdind LdindTargetType.LdindR4 currentThread state
| Ldind_r8 -> executeLdind LdindTargetType.LdindR8 currentThread state
| Rem -> failwith "TODO: Rem unimplemented"
| Rem_un -> failwith "TODO: Rem_un unimplemented"
| Volatile -> failwith "TODO: Volatile unimplemented"

View File

@@ -391,7 +391,15 @@ module internal UnaryConstIlOp =
| Bgt_un i -> failwith "TODO: Bgt_un unimplemented"
| Ble_un i -> failwith "TODO: Ble_un unimplemented"
| Blt_un i -> failwith "TODO: Blt_un unimplemented"
| Ldloc_s b -> failwith "TODO: Ldloc_s unimplemented"
| Ldloc_s b ->
let threadState = state.ThreadState.[currentThread]
let state =
state
|> IlMachineState.pushToEvalStack threadState.MethodState.LocalVariables.[int<uint8> b] currentThread
|> IlMachineState.advanceProgramCounter currentThread
state, WhatWeDid.Executed
| Ldloca_s b ->
let threadState = state.ThreadState.[currentThread]

View File

@@ -254,7 +254,12 @@ module internal UnaryMetadataIlOp =
| EvalStackValue.Int32 v -> v
| popped -> failwith $"unexpectedly popped value %O{popped} to serve as array len"
let elementType, assy =
let typeGenerics =
match newMethodState.ExecutingMethod.DeclaringType.Generics with
| [] -> None
| l -> Some (ImmutableArray.CreateRange l)
let state, elementType, assy =
match metadataToken with
| MetadataToken.TypeDefinition defn ->
let assy = state.LoadedAssembly currentState.ActiveAssembly |> Option.get
@@ -271,21 +276,48 @@ module internal UnaryMetadataIlOp =
| ResolvedBaseType.Object -> SignatureTypeKind.Class
| ResolvedBaseType.Delegate -> failwith "TODO: delegate"
let result =
TypeDefn.FromDefinition (
ComparableTypeDefinitionHandle.Make defn.TypeDefHandle,
defn.Assembly.Name,
signatureTypeKind
),
assy
)
state, result, assy
| MetadataToken.TypeSpecification spec ->
let assy = state.LoadedAssembly currentState.ActiveAssembly |> Option.get
assy.TypeSpecs.[spec].Signature, assy
| x -> failwith $"TODO: Newarr element type resolution unimplemented for {x}"
state, assy.TypeSpecs.[spec].Signature, assy
| MetadataToken.TypeReference ref ->
let ref = state.ActiveAssembly(thread).TypeRefs.[ref]
let typeGenerics =
match newMethodState.ExecutingMethod.DeclaringType.Generics with
| [] -> None
| l -> Some (ImmutableArray.CreateRange l)
let state, assy, resolved =
IlMachineState.resolveTypeFromRef
loggerFactory
(state.ActiveAssembly thread)
ref
typeGenerics
state
let baseType =
resolved.BaseType
|> DumpedAssembly.resolveBaseType baseClassTypes state._LoadedAssemblies assy.Name
let signatureTypeKind =
match baseType with
| ResolvedBaseType.Enum
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
| ResolvedBaseType.Object -> SignatureTypeKind.Class
| ResolvedBaseType.Delegate -> failwith "TODO: delegate"
let result =
TypeDefn.FromDefinition (
ComparableTypeDefinitionHandle.Make resolved.TypeDefHandle,
assy.Name.FullName,
signatureTypeKind
)
state, result, assy
| x -> failwith $"TODO: Newarr element type resolution unimplemented for {x}"
let state, zeroOfType =
IlMachineState.cliTypeZeroOf
@@ -427,7 +459,10 @@ module internal UnaryMetadataIlOp =
| EvalStackValue.ManagedPointer source ->
match source with
| ManagedPointerSource.Null -> failwith "TODO: raise NullReferenceException"
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> failwith "todo"
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
let threadState = state.ThreadState.[sourceThread]
let methodState = threadState.MethodStates.[methodFrame]
failwith "TODO"
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> failwith "todo"
| ManagedPointerSource.Heap addr ->
match state.ManagedHeap.NonArrayObjects.TryGetValue addr with