diff --git a/CSharpExample/Class1.cs b/CSharpExample/Class1.cs index c238f1b..f2f9431 100644 --- a/CSharpExample/Class1.cs +++ b/CSharpExample/Class1.cs @@ -1,15 +1,364 @@ using System; -using System.Collections.Generic; -using System.Linq; -namespace HelloWorldApp +unsafe class LdindTest { - class Program + static int Main(string[] args) { - 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) { - Console.WriteLine("Hello"); - return 0; + 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 = new int[3]; + array[0] = 10; + array[1] = 20; + array[2] = 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; } } diff --git a/WoofWare.PawPrint.Test/TestHarness.fs b/WoofWare.PawPrint.Test/TestHarness.fs index bcaa7e5..b59aa4e 100644 --- a/WoofWare.PawPrint.Test/TestHarness.fs +++ b/WoofWare.PawPrint.Test/TestHarness.fs @@ -30,5 +30,4 @@ type TestCase = FileName : string ExpectedReturnCode : int NativeImpls : NativeImpls - LocalVariablesOfMain : CliType list option } diff --git a/WoofWare.PawPrint.Test/TestImpureCases.fs b/WoofWare.PawPrint.Test/TestImpureCases.fs index 5c136a3..638fbc1 100644 --- a/WoofWare.PawPrint.Test/TestImpureCases.fs +++ b/WoofWare.PawPrint.Test/TestImpureCases.fs @@ -17,7 +17,29 @@ module TestImpureCases = FileName = "WriteLine.cs" ExpectedReturnCode = 1 NativeImpls = NativeImpls.PassThru () - LocalVariablesOfMain = [] |> Some + } + + { + FileName = "ConsoleColor.cs" + ExpectedReturnCode = 1 + NativeImpls = + let mock = MockEnv.make () + + { mock with + System_Environment = + { System_EnvironmentMock.Empty with + GetProcessorCount = + fun thread state -> + let state = + state |> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 1) thread + + (state, WhatWeDid.Executed) |> ExecutionResult.Stepped + _Exit = + fun thread state -> + let state = state |> IlMachineState.loadArgument thread 0 + ExecutionResult.Terminated (state, thread) + } + } } ] @@ -44,7 +66,6 @@ module TestImpureCases = ExecutionResult.Terminated (state, thread) } } - LocalVariablesOfMain = [] |> Some } ] @@ -71,15 +92,6 @@ module TestImpureCases = | ret -> failwith $"expected program to return an int, but it returned %O{ret}" exitCode |> shouldEqual case.ExpectedReturnCode - - let finalVariables = - terminalState.ThreadState.[terminatingThread].MethodState.LocalVariables - |> Seq.toList - - match case.LocalVariablesOfMain with - | None -> () - | Some expected -> finalVariables |> shouldEqual expected - with _ -> for message in messages () do System.Console.Error.WriteLine $"{message}" diff --git a/WoofWare.PawPrint.Test/TestPureCases.fs b/WoofWare.PawPrint.Test/TestPureCases.fs index adab284..b6f2d7d 100644 --- a/WoofWare.PawPrint.Test/TestPureCases.fs +++ b/WoofWare.PawPrint.Test/TestPureCases.fs @@ -17,73 +17,36 @@ module TestPureCases = FileName = "CrossAssemblyTypes.cs" ExpectedReturnCode = 0 NativeImpls = MockEnv.make () - LocalVariablesOfMain = None } { FileName = "InitializeArray.cs" ExpectedReturnCode = 0 NativeImpls = MockEnv.make () - LocalVariablesOfMain = None - } - { - FileName = "GenericEdgeCases.cs" - ExpectedReturnCode = 0 - NativeImpls = MockEnv.make () - LocalVariablesOfMain = None } { FileName = "Threads.cs" ExpectedReturnCode = 3 NativeImpls = MockEnv.make () - LocalVariablesOfMain = [] |> Some } { FileName = "ComplexTryCatch.cs" ExpectedReturnCode = 14 NativeImpls = NativeImpls.PassThru () - LocalVariablesOfMain = - [ - 4 - 20 - 115 - 12 - 1 - 10 - 2 - 112 - 12 - 1111 - 42 - 99 - 25 - 50 - 123 - 20 - 35 - 5 - 11111 - 100001 - ] - |> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i)) - |> Some } { FileName = "ResizeArray.cs" ExpectedReturnCode = 109 NativeImpls = MockEnv.make () - LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ] |> Some } { FileName = "Sizeof.cs" ExpectedReturnCode = 0 NativeImpls = MockEnv.make () - LocalVariablesOfMain = None } { FileName = "LdtokenField.cs" ExpectedReturnCode = 0 NativeImpls = MockEnv.make () - LocalVariablesOfMain = None } ] @@ -93,61 +56,41 @@ module TestPureCases = FileName = "NoOp.cs" ExpectedReturnCode = 1 NativeImpls = MockEnv.make () - LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 1) ] |> Some + } + { + FileName = "GenericEdgeCases.cs" + ExpectedReturnCode = 0 + NativeImpls = MockEnv.make () } { FileName = "TestShl.cs" ExpectedReturnCode = 0 NativeImpls = MockEnv.make () - LocalVariablesOfMain = None } { FileName = "TestShr.cs" ExpectedReturnCode = 0 NativeImpls = MockEnv.make () - LocalVariablesOfMain = None } { FileName = "StaticVariables.cs" ExpectedReturnCode = 0 NativeImpls = MockEnv.make () - LocalVariablesOfMain = None } { FileName = "Ldind.cs" ExpectedReturnCode = 0 NativeImpls = MockEnv.make () - LocalVariablesOfMain = - [ - // `failures` - CliType.Numeric (CliNumericType.Int32 0) - // Return value - CliType.Numeric (CliNumericType.Int32 0) - ] - |> Some } { FileName = "CustomDelegate.cs" ExpectedReturnCode = 8 NativeImpls = MockEnv.make () - LocalVariablesOfMain = - [ - // filter - CliType.ObjectRef (Some (ManagedHeapAddress 2)) - // result - CliType.ofBool true - // result, cloned for "if(result)" check - CliType.ofBool true - // ret - CliType.Numeric (CliNumericType.Int32 8) - ] - |> Some } { FileName = "ArgumentOrdering.cs" ExpectedReturnCode = 0 NativeImpls = MockEnv.make () - LocalVariablesOfMain = None } { FileName = "BasicLock.cs" @@ -158,92 +101,46 @@ module TestPureCases = { mock with System_Threading_Monitor = System_Threading_Monitor.passThru } - LocalVariablesOfMain = - [ - // Four variables: - // locker - CliType.ObjectRef (Some (ManagedHeapAddress 2)) - // a copy of locker, taken so that the contents of the implicit `finally` have a stable copy - CliType.ObjectRef (Some (ManagedHeapAddress 2)) - // out param of `ReliableEnter` - CliType.ofBool true - // return value - CliType.Numeric (CliNumericType.Int32 1) - ] - |> Some } { FileName = "TriangleNumber.cs" ExpectedReturnCode = 10 NativeImpls = MockEnv.make () - LocalVariablesOfMain = - [ - // answer - CliType.Numeric (CliNumericType.Int32 10) - // i - CliType.Numeric (CliNumericType.Int32 5) - // End-loop condition - CliType.ofBool false - // Ret - CliType.Numeric (CliNumericType.Int32 10) - ] - |> Some } { FileName = "ExceptionWithNoOpFinally.cs" ExpectedReturnCode = 3 NativeImpls = MockEnv.make () - LocalVariablesOfMain = - [ - // Variable 1 is `x`, variable 2 is the implicit return value - 4 - 3 - ] - |> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i)) - |> Some } { FileName = "ExceptionWithNoOpCatch.cs" ExpectedReturnCode = 10 NativeImpls = MockEnv.make () - LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ] |> Some } { FileName = "Floats.cs" ExpectedReturnCode = 0 NativeImpls = MockEnv.make () - LocalVariablesOfMain = None } { FileName = "TryCatchWithThrowInBody.cs" ExpectedReturnCode = 4 NativeImpls = MockEnv.make () - LocalVariablesOfMain = - [ - // one variable is x, one variable is the return value which also happens to have the same value - 4 - 4 - ] - |> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i)) - |> Some } { FileName = "Ldelema.cs" ExpectedReturnCode = 0 NativeImpls = MockEnv.make () - LocalVariablesOfMain = None } { FileName = "TypeConcretization.cs" ExpectedReturnCode = 0 NativeImpls = MockEnv.make () - LocalVariablesOfMain = None } { FileName = "TestOr.cs" ExpectedReturnCode = 0 NativeImpls = MockEnv.make () - LocalVariablesOfMain = None } ] @@ -274,14 +171,6 @@ module TestPureCases = exitCode |> shouldEqual realResult.ExitCode exitCode |> shouldEqual case.ExpectedReturnCode - - let finalVariables = - terminalState.ThreadState.[terminatingThread].MethodState.LocalVariables - |> Seq.toList - - match case.LocalVariablesOfMain with - | None -> () - | Some expected -> finalVariables |> shouldEqual expected with _ -> for message in messages () do System.Console.Error.WriteLine $"{message}" diff --git a/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj b/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj index bb485bd..4c64d84 100644 --- a/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj +++ b/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj @@ -45,6 +45,7 @@ + diff --git a/WoofWare.PawPrint.Test/sourcesImpure/ConsoleColor.cs b/WoofWare.PawPrint.Test/sourcesImpure/ConsoleColor.cs new file mode 100644 index 0000000..c453f95 --- /dev/null +++ b/WoofWare.PawPrint.Test/sourcesImpure/ConsoleColor.cs @@ -0,0 +1,13 @@ +using System; + +namespace HelloWorldApp +{ + class Program + { + static int Main(string[] args) + { + Console.WriteLine("Hello, world!"); + return 1; + } + } +} diff --git a/WoofWare.PawPrint.Test/sourcesImpure/WriteLine.cs b/WoofWare.PawPrint.Test/sourcesImpure/WriteLine.cs index c453f95..5907c52 100644 --- a/WoofWare.PawPrint.Test/sourcesImpure/WriteLine.cs +++ b/WoofWare.PawPrint.Test/sourcesImpure/WriteLine.cs @@ -6,7 +6,8 @@ namespace HelloWorldApp { static int Main(string[] args) { - Console.WriteLine("Hello, world!"); + var c = Console.BackgroundColor; + var d = Console.ForegroundColor; return 1; } } diff --git a/WoofWare.PawPrint/AbstractMachine.fs b/WoofWare.PawPrint/AbstractMachine.fs index c063560..24e5fcf 100644 --- a/WoofWare.PawPrint/AbstractMachine.fs +++ b/WoofWare.PawPrint/AbstractMachine.fs @@ -55,18 +55,19 @@ module AbstractMachine = // We've been instructed to run a delegate. let delegateToRunAddr = match instruction.Arguments.[0] with + | CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Heap addr)) | CliType.ObjectRef (Some addr) -> addr | _ -> failwith "expected a managed object ref to delegate" let delegateToRun = state.ManagedHeap.NonArrayObjects.[delegateToRunAddr] let target = - match delegateToRun.Fields.["_target"] with + match delegateToRun.Fields |> List.find (fun (x, _) -> x = "_target") |> snd with | CliType.ObjectRef addr -> addr | x -> failwith $"TODO: delegate target wasn't an object ref: %O{x}" let methodPtr = - match delegateToRun.Fields.["_methodPtr"] with + match delegateToRun.Fields |> List.find (fun (x, _) -> x = "_methodPtr") |> snd with | CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer mi)) -> mi | d -> failwith $"unexpectedly not a method pointer in delegate invocation: {d}" diff --git a/WoofWare.PawPrint/BasicCliType.fs b/WoofWare.PawPrint/BasicCliType.fs index 7ae763c..623a923 100644 --- a/WoofWare.PawPrint/BasicCliType.fs +++ b/WoofWare.PawPrint/BasicCliType.fs @@ -41,6 +41,7 @@ type ManagedPointerSource = | Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16 | Heap of ManagedHeapAddress | ArrayIndex of arr : ManagedHeapAddress * index : int + | Field of ManagedPointerSource * fieldName : string | Null override this.ToString () = @@ -52,6 +53,7 @@ type ManagedPointerSource = | ManagedPointerSource.Argument (source, method, var) -> $"" | ManagedPointerSource.ArrayIndex (arr, index) -> $"" + | ManagedPointerSource.Field (source, name) -> $"" [] type UnsignedNativeIntSource = @@ -117,10 +119,27 @@ type CliNumericType = type CliRuntimePointerSource = | LocalVariable of sourceThread : ThreadId * methodFrame : int * whichVar : uint16 | Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16 + | Field of source : CliRuntimePointerSource * fieldName : string | Heap of ManagedHeapAddress | ArrayIndex of arr : ManagedHeapAddress * index : int | Null +[] +module CliRuntimePointerSource = + let rec ofManagedPointerSource (ptrSource : ManagedPointerSource) : CliRuntimePointerSource = + match ptrSource with + | ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> + CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, whichVar) + | ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> + CliRuntimePointerSource.Argument (sourceThread, methodFrame, whichVar) + | ManagedPointerSource.Heap managedHeapAddress -> CliRuntimePointerSource.Heap managedHeapAddress + | ManagedPointerSource.Null -> CliRuntimePointerSource.Null + | ManagedPointerSource.ArrayIndex (arr, ind) -> CliRuntimePointerSource.ArrayIndex (arr, ind) + | ManagedPointerSource.Field (a, ind) -> + let a = ofManagedPointerSource a + CliRuntimePointerSource.Field (a, ind) + + type CliRuntimePointer = | Unmanaged of int64 | Managed of CliRuntimePointerSource @@ -204,8 +223,22 @@ module CliType = | PrimitiveType.Double -> CliType.Numeric (CliNumericType.Float64 0.0) | PrimitiveType.String -> CliType.ObjectRef None | PrimitiveType.TypedReference -> failwith "todo" - | PrimitiveType.IntPtr -> CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null) - | PrimitiveType.UIntPtr -> CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null) + | PrimitiveType.IntPtr -> + { + Fields = + [ + "_value", CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null) + ] + } + |> CliType.ValueType + | PrimitiveType.UIntPtr -> + { + Fields = + [ + "_value", CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null) + ] + } + |> CliType.ValueType | PrimitiveType.Object -> CliType.ObjectRef None let rec zeroOf diff --git a/WoofWare.PawPrint/EvalStack.fs b/WoofWare.PawPrint/EvalStack.fs index 8366dfb..87651df 100644 --- a/WoofWare.PawPrint/EvalStack.fs +++ b/WoofWare.PawPrint/EvalStack.fs @@ -156,19 +156,13 @@ module EvalStackValue = | CliType.ObjectRef _ -> match popped with | EvalStackValue.ManagedPointer ptrSource -> - match ptrSource with - | ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> - CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, whichVar) - |> CliRuntimePointer.Managed - |> CliType.RuntimePointer - | ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> - CliRuntimePointerSource.Argument (sourceThread, methodFrame, whichVar) - |> CliRuntimePointer.Managed - |> CliType.RuntimePointer - | ManagedPointerSource.Heap managedHeapAddress -> CliType.ObjectRef (Some managedHeapAddress) - | ManagedPointerSource.Null -> CliType.ObjectRef None - | ManagedPointerSource.ArrayIndex (arr, ind) -> - CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.ArrayIndex (arr, ind))) + CliRuntimePointerSource.ofManagedPointerSource ptrSource + |> CliRuntimePointer.Managed + |> CliType.RuntimePointer + | EvalStackValue.ObjectRef ptr -> + CliRuntimePointerSource.Heap ptr + |> CliRuntimePointer.Managed + |> CliType.RuntimePointer | EvalStackValue.NativeInt nativeIntSource -> match nativeIntSource with | NativeIntSource.Verbatim 0L -> CliType.ObjectRef None @@ -196,40 +190,23 @@ module EvalStackValue = | CliType.RuntimePointer _ -> match popped with | EvalStackValue.ManagedPointer src -> - match src with - | ManagedPointerSource.Heap addr -> CliType.ofManagedObject addr - | ManagedPointerSource.Null -> CliType.ObjectRef None - | ManagedPointerSource.LocalVariable (sourceThread, methodFrame, var) -> - CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, var) - |> CliRuntimePointer.Managed - |> CliType.RuntimePointer - | ManagedPointerSource.Argument (sourceThread, methodFrame, var) -> - CliRuntimePointerSource.Argument (sourceThread, methodFrame, var) - |> CliRuntimePointer.Managed - |> CliType.RuntimePointer - | ManagedPointerSource.ArrayIndex (arr, index) -> - CliRuntimePointerSource.ArrayIndex (arr, index) - |> CliRuntimePointer.Managed - |> CliType.RuntimePointer + CliRuntimePointerSource.ofManagedPointerSource src + |> 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 -> - CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null) - | 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))) - | ManagedPointerSource.ArrayIndex _ -> failwith "TODO" + CliRuntimePointerSource.ofManagedPointerSource src + |> CliRuntimePointer.Managed + |> CliType.RuntimePointer | NativeIntSource.FunctionPointer methodInfo -> CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer methodInfo)) | NativeIntSource.TypeHandlePtr int64 -> failwith "todo" + | EvalStackValue.ObjectRef addr -> + CliRuntimePointerSource.Heap addr + |> CliRuntimePointer.Managed + |> CliType.RuntimePointer | _ -> failwith $"TODO: %O{popped}" | CliType.Char _ -> match popped with @@ -303,6 +280,9 @@ module EvalStackValue = |> EvalStackValue.ManagedPointer | CliRuntimePointerSource.Heap addr -> EvalStackValue.ObjectRef addr | CliRuntimePointerSource.Null -> EvalStackValue.ManagedPointer ManagedPointerSource.Null + | CliRuntimePointerSource.Field (source, fieldName) -> + ManagedPointerSource.Field (failwith "TODO", fieldName) + |> EvalStackValue.ManagedPointer | CliType.ValueType fields -> fields.Fields |> List.map (fun (name, f) -> name, ofCliType f) diff --git a/WoofWare.PawPrint/ExternImplementations/System.Threading.Monitor.fs b/WoofWare.PawPrint/ExternImplementations/System.Threading.Monitor.fs index f765601..9b139a5 100644 --- a/WoofWare.PawPrint/ExternImplementations/System.Threading.Monitor.fs +++ b/WoofWare.PawPrint/ExternImplementations/System.Threading.Monitor.fs @@ -62,6 +62,7 @@ module System_Threading_Monitor = match lockObj with | EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw ArgumentNullException" + | EvalStackValue.ObjectRef addr | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> match IlMachineState.getSyncBlock addr state with | SyncBlock.Free -> @@ -87,6 +88,7 @@ module System_Threading_Monitor = failwith "not really expecting to *edit* an argument..." | ManagedPointerSource.Heap addr -> failwith "todo: managed heap" | ManagedPointerSource.ArrayIndex _ -> failwith "todo: array index" + | ManagedPointerSource.Field (managedPointerSource, fieldName) -> failwith "todo" (state, WhatWeDid.Executed) |> ExecutionResult.Stepped @@ -100,6 +102,7 @@ module System_Threading_Monitor = match lockObj with | EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw ArgumentNullException" + | EvalStackValue.ObjectRef addr | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> match IlMachineState.getSyncBlock addr state with | SyncBlock.Free -> failwith "TODO: throw SynchronizationLockException" diff --git a/WoofWare.PawPrint/IlMachineState.fs b/WoofWare.PawPrint/IlMachineState.fs index 3dee5ee..21f4bd4 100644 --- a/WoofWare.PawPrint/IlMachineState.fs +++ b/WoofWare.PawPrint/IlMachineState.fs @@ -674,14 +674,14 @@ module IlMachineState = (state : IlMachineState) : IlMachineState = - let heap = ManagedHeap.SetArrayValue arrayAllocation index v state.ManagedHeap + let heap = ManagedHeap.setArrayValue arrayAllocation index v state.ManagedHeap { state with ManagedHeap = heap } let getArrayValue (arrayAllocation : ManagedHeapAddress) (index : int) (state : IlMachineState) : CliType = - ManagedHeap.GetArrayValue arrayAllocation index state.ManagedHeap + ManagedHeap.getArrayValue arrayAllocation index state.ManagedHeap /// There might be no stack frame to return to, so you might get None. let returnStackFrame @@ -737,7 +737,7 @@ module IlMachineState = | ResolvedBaseType.ValueType -> let vt = { - Fields = Map.toList constructed.Fields + Fields = constructed.Fields } state @@ -1043,7 +1043,7 @@ module IlMachineState = Logger = logger NextThreadId = 0 // CallStack = [] - ManagedHeap = ManagedHeap.Empty + ManagedHeap = ManagedHeap.empty ThreadState = Map.empty InternedStrings = ImmutableDictionary.Empty _LoadedAssemblies = ImmutableDictionary.Empty @@ -1089,7 +1089,7 @@ module IlMachineState = Elements = initialisation } - let alloc, heap = state.ManagedHeap |> ManagedHeap.AllocateArray o + let alloc, heap = state.ManagedHeap |> ManagedHeap.allocateArray o let state = { state with @@ -1099,7 +1099,7 @@ module IlMachineState = alloc, state let allocateStringData (len : int) (state : IlMachineState) : int * IlMachineState = - let addr, heap = state.ManagedHeap |> ManagedHeap.AllocateString len + let addr, heap = state.ManagedHeap |> ManagedHeap.allocateString len let state = { state with @@ -1109,7 +1109,7 @@ module IlMachineState = addr, state let setStringData (addr : int) (contents : string) (state : IlMachineState) : IlMachineState = - let heap = ManagedHeap.SetStringData addr contents state.ManagedHeap + let heap = ManagedHeap.setStringData addr contents state.ManagedHeap { state with ManagedHeap = heap @@ -1123,12 +1123,12 @@ module IlMachineState = = let o = { - Fields = Map.ofList fields + Fields = fields Type = TypeInfoCrate.make typeInfo SyncBlock = SyncBlock.Free } - let alloc, heap = state.ManagedHeap |> ManagedHeap.AllocateNonArray o + let alloc, heap = state.ManagedHeap |> ManagedHeap.allocateNonArray o let state = { state with @@ -1399,11 +1399,11 @@ module IlMachineState = : IlMachineState = { state with - ManagedHeap = state.ManagedHeap |> ManagedHeap.SetSyncBlock addr syncBlockValue + ManagedHeap = state.ManagedHeap |> ManagedHeap.setSyncBlock addr syncBlockValue } let getSyncBlock (addr : ManagedHeapAddress) (state : IlMachineState) : SyncBlock = - state.ManagedHeap |> ManagedHeap.GetSyncBlock addr + state.ManagedHeap |> ManagedHeap.getSyncBlock addr let executeDelegateConstructor (instruction : MethodState) (state : IlMachineState) : IlMachineState = // We've been called with arguments already popped from the stack into local arguments. @@ -1413,12 +1413,17 @@ module IlMachineState = let targetObj = match targetObj with - | CliType.ObjectRef target -> target + | CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Heap target)) + | CliType.ObjectRef (Some target) -> Some target + | CliType.ObjectRef None + | CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null) -> None | _ -> failwith $"Unexpected target type for delegate: {targetObj}" let constructing = match constructing with + | CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null) | CliType.ObjectRef None -> failwith "unexpectedly constructing the null delegate" + | CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Heap target)) | CliType.ObjectRef (Some target) -> target | _ -> failwith $"Unexpectedly not constructing a managed object: {constructing}" @@ -1430,9 +1435,10 @@ module IlMachineState = // Standard delegate fields in .NET are _target and _methodPtr // Update the fields with the target object and method pointer let updatedFields = - heapObj.Fields - |> Map.add "_target" (CliType.ObjectRef targetObj) - |> Map.add "_methodPtr" methodPtr + // TODO: field ordering here is probably wrong + ("_target", CliType.ObjectRef targetObj) + :: ("_methodPtr", methodPtr) + :: heapObj.Fields let updatedObj = { heapObj with @@ -1533,3 +1539,19 @@ module IlMachineState = match v.TryGetValue field with | false, _ -> None | true, v -> Some v + + let rec dereferencePointer (state : IlMachineState) (src : ManagedPointerSource) : CliType = + match src with + | ManagedPointerSource.Null -> failwith "TODO: throw NRE" + | ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> + state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables.[int whichVar] + | ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> + state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int whichVar] + | ManagedPointerSource.Heap addr -> failwith "todo" + | ManagedPointerSource.ArrayIndex (arr, index) -> getArrayValue arr index state + | ManagedPointerSource.Field (addr, name) -> + let obj = dereferencePointer state addr + + match obj with + | CliType.ValueType vt -> vt.Fields |> Map.ofList |> Map.find name + | v -> failwith $"could not find field {name} on object {v}" diff --git a/WoofWare.PawPrint/Intrinsics.fs b/WoofWare.PawPrint/Intrinsics.fs index 37b09ce..e8be9bf 100644 --- a/WoofWare.PawPrint/Intrinsics.fs +++ b/WoofWare.PawPrint/Intrinsics.fs @@ -90,6 +90,7 @@ module Intrinsics = CliRuntimePointer.Managed (CliRuntimePointerSource.Heap managedHeapAddress) | ManagedPointerSource.Null -> failwith "todo" | ManagedPointerSource.ArrayIndex _ -> failwith "TODO" + | ManagedPointerSource.Field _ -> failwith "TODO" | x -> failwith $"TODO: Unsafe.AsPointer(%O{x})" IlMachineState.pushToEvalStack (CliType.RuntimePointer toPush) currentThread state @@ -154,6 +155,7 @@ module Intrinsics = let arg1 = match arg1 with + | EvalStackValue.ObjectRef h | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap h) -> h | EvalStackValue.Int32 _ | EvalStackValue.Int64 _ @@ -164,6 +166,7 @@ module Intrinsics = let arg2 = match arg2 with + | EvalStackValue.ObjectRef h | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap h) -> h | EvalStackValue.Int32 _ | EvalStackValue.Int64 _ @@ -183,14 +186,7 @@ module Intrinsics = let v : CliType = let rec go ptr = match ptr with - | EvalStackValue.ManagedPointer src -> - match src with - | ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> failwith "todo" - | ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> failwith "todo" - | ManagedPointerSource.Heap managedHeapAddress -> failwith "todo" - | ManagedPointerSource.ArrayIndex (arr, index) -> - state |> IlMachineState.getArrayValue arr index - | ManagedPointerSource.Null -> failwith "TODO: throw NRE" + | EvalStackValue.ManagedPointer src -> IlMachineState.dereferencePointer state src | EvalStackValue.NativeInt src -> failwith "TODO" | EvalStackValue.ObjectRef ptr -> failwith "TODO" | EvalStackValue.UserDefinedValueType [ _, field ] -> go field @@ -234,8 +230,14 @@ module Intrinsics = else failwith "TODO: unexpected params to String.op_Implicit" | _ -> failwith "TODO: unexpected params to String.op_Implicit" + | "System.Private.CoreLib", "RuntimeHelpers", "IsReferenceOrContainsReferences" -> + // https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs#L207 + failwith "TODO: get generic type parameter and then do the thing" | "System.Private.CoreLib", "RuntimeHelpers", "InitializeArray" -> // https://github.com/dotnet/runtime/blob/9e5e6aa7bc36aeb2a154709a9d1192030c30a2ef/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs#L18 failwith "TODO: array initialization" + | "System.Private.CoreLib", "RuntimeHelpers", "CreateSpan" -> + // https://github.com/dotnet/runtime/blob/9e5e6aa7bc36aeb2a154709a9d1192030c30a2ef/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs#L153 + None | a, b, c -> failwith $"TODO: implement JIT intrinsic {a}.{b}.{c}" |> Option.map (fun s -> s.WithThreadSwitchedToAssembly callerAssy currentThread |> fst) diff --git a/WoofWare.PawPrint/List.fs b/WoofWare.PawPrint/List.fs new file mode 100644 index 0000000..33a4d9e --- /dev/null +++ b/WoofWare.PawPrint/List.fs @@ -0,0 +1,12 @@ +namespace WoofWare.PawPrint + +[] +module List = + let replaceWhere (f : 'a -> 'a option) (l : 'a list) : 'a list = + ([], l) + ||> List.fold (fun acc x -> + match f x with + | None -> x :: acc + | Some y -> y :: acc + ) + |> List.rev diff --git a/WoofWare.PawPrint/ManagedHeap.fs b/WoofWare.PawPrint/ManagedHeap.fs index 0de1a88..c037537 100644 --- a/WoofWare.PawPrint/ManagedHeap.fs +++ b/WoofWare.PawPrint/ManagedHeap.fs @@ -8,7 +8,7 @@ type SyncBlock = type AllocatedNonArrayObject = { - Fields : Map + Fields : (string * CliType) list Type : WoofWare.PawPrint.TypeInfoCrate SyncBlock : SyncBlock } @@ -29,7 +29,9 @@ type ManagedHeap = StringArrayData : ImmutableArray } - static member Empty : ManagedHeap = +[] +module ManagedHeap = + let empty : ManagedHeap = { NonArrayObjects = Map.empty FirstAvailableAddress = 1 @@ -37,12 +39,12 @@ type ManagedHeap = StringArrayData = ImmutableArray.Empty } - static member GetSyncBlock (addr : ManagedHeapAddress) (heap : ManagedHeap) : SyncBlock = + let getSyncBlock (addr : ManagedHeapAddress) (heap : ManagedHeap) : SyncBlock = match heap.NonArrayObjects.TryGetValue addr with | false, _ -> failwith "TODO: getting sync block of array" | true, v -> v.SyncBlock - static member SetSyncBlock (addr : ManagedHeapAddress) (syncValue : SyncBlock) (heap : ManagedHeap) : ManagedHeap = + let setSyncBlock (addr : ManagedHeapAddress) (syncValue : SyncBlock) (heap : ManagedHeap) : ManagedHeap = match heap.NonArrayObjects.TryGetValue addr with | false, _ -> failwith "TODO: locked on an array object" | true, v -> @@ -55,7 +57,7 @@ type ManagedHeap = NonArrayObjects = heap.NonArrayObjects |> Map.add addr newV } - static member AllocateArray (ty : AllocatedArray) (heap : ManagedHeap) : ManagedHeapAddress * ManagedHeap = + let allocateArray (ty : AllocatedArray) (heap : ManagedHeap) : ManagedHeapAddress * ManagedHeap = let addr = heap.FirstAvailableAddress let heap = @@ -68,7 +70,7 @@ type ManagedHeap = ManagedHeapAddress addr, heap - static member AllocateString (len : int) (heap : ManagedHeap) : int * ManagedHeap = + let allocateString (len : int) (heap : ManagedHeap) : int * ManagedHeap = let addr = heap.StringArrayData.Length let heap = @@ -80,7 +82,7 @@ type ManagedHeap = addr, heap - static member SetStringData (addr : int) (contents : string) (heap : ManagedHeap) : ManagedHeap = + let setStringData (addr : int) (contents : string) (heap : ManagedHeap) : ManagedHeap = let newArr = (heap.StringArrayData, seq { 0 .. contents.Length - 1 }) ||> Seq.fold (fun data count -> data.SetItem (addr + count, contents.[count])) @@ -92,11 +94,7 @@ type ManagedHeap = heap - static member AllocateNonArray - (ty : AllocatedNonArrayObject) - (heap : ManagedHeap) - : ManagedHeapAddress * ManagedHeap - = + let allocateNonArray (ty : AllocatedNonArrayObject) (heap : ManagedHeap) : ManagedHeapAddress * ManagedHeap = let addr = heap.FirstAvailableAddress let heap = @@ -109,7 +107,7 @@ type ManagedHeap = ManagedHeapAddress addr, heap - static member GetArrayValue (alloc : ManagedHeapAddress) (offset : int) (heap : ManagedHeap) : CliType = + let getArrayValue (alloc : ManagedHeapAddress) (offset : int) (heap : ManagedHeap) : CliType = match heap.Arrays.TryGetValue alloc with | false, _ -> failwith "TODO: array not on heap" | true, arr -> @@ -119,13 +117,7 @@ type ManagedHeap = arr.Elements.[offset] - static member SetArrayValue - (alloc : ManagedHeapAddress) - (offset : int) - (v : CliType) - (heap : ManagedHeap) - : ManagedHeap - = + let setArrayValue (alloc : ManagedHeapAddress) (offset : int) (v : CliType) (heap : ManagedHeap) : ManagedHeap = let newArrs = heap.Arrays |> Map.change diff --git a/WoofWare.PawPrint/NullaryIlOp.fs b/WoofWare.PawPrint/NullaryIlOp.fs index 8e85aae..dacd518 100644 --- a/WoofWare.PawPrint/NullaryIlOp.fs +++ b/WoofWare.PawPrint/NullaryIlOp.fs @@ -37,19 +37,6 @@ module NullaryIlOp = | 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 whichVar] - | ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> - state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables.[int whichVar] - | ManagedPointerSource.Heap managedHeapAddress -> failwith "TODO: Heap pointer dereferencing not implemented" - | ManagedPointerSource.ArrayIndex (arr, index) -> - let arr = state.ManagedHeap.Arrays.[arr] - arr.Elements.[index] - // Unified Ldind implementation let private executeLdind (targetType : LdindTargetType) @@ -61,7 +48,7 @@ module NullaryIlOp = let loadedValue = match popped with - | EvalStackValue.ManagedPointer src -> loadFromPointerSource state src + | EvalStackValue.ManagedPointer src -> IlMachineState.dereferencePointer state src | EvalStackValue.NativeInt nativeIntSource -> failwith $"TODO: Native int pointer dereferencing not implemented for {targetType}" | EvalStackValue.ObjectRef managedHeapAddress -> @@ -125,6 +112,7 @@ module NullaryIlOp = } | ManagedPointerSource.Heap managedHeapAddress -> failwith "todo" | ManagedPointerSource.ArrayIndex _ -> failwith "todo" + | Field (managedPointerSource, fieldName) -> failwith "todo" | EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo" let internal ldElem @@ -684,6 +672,7 @@ module NullaryIlOp = let popped = match popped with | EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE" + | EvalStackValue.ObjectRef addr | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> addr | _ -> failwith $"can't get len of {popped}" @@ -891,16 +880,7 @@ module NullaryIlOp = let referenced = match addr with - | EvalStackValue.ManagedPointer src -> - match src with - | ManagedPointerSource.Null -> failwith "TODO: throw NRE" - | ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> - state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables - .[int whichVar] - | ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> - state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int whichVar] - | ManagedPointerSource.Heap managedHeapAddress -> failwith "todo" - | ManagedPointerSource.ArrayIndex _ -> failwith "todo" + | EvalStackValue.ManagedPointer src -> IlMachineState.dereferencePointer state src | a -> failwith $"TODO: {a}" let state = @@ -928,6 +908,7 @@ module NullaryIlOp = arr (EvalStackValue.toCliTypeCoerced (CliType.ObjectRef None) value) index + | ManagedPointerSource.Field _ -> failwith "TODO" | addr -> failwith $"TODO: {addr}" let state = state |> IlMachineState.advanceProgramCounter currentThread diff --git a/WoofWare.PawPrint/UnaryConstIlOp.fs b/WoofWare.PawPrint/UnaryConstIlOp.fs index b19b384..22b789d 100644 --- a/WoofWare.PawPrint/UnaryConstIlOp.fs +++ b/WoofWare.PawPrint/UnaryConstIlOp.fs @@ -129,8 +129,8 @@ module internal UnaryConstIlOp = | EvalStackValue.NativeInt i -> not (NativeIntSource.isZero i) | EvalStackValue.Float f -> failwith "TODO: Brtrue_s float semantics undocumented" | EvalStackValue.ManagedPointer ManagedPointerSource.Null -> false - | EvalStackValue.ManagedPointer _ -> true - | EvalStackValue.ObjectRef _ -> failwith "TODO: Brtrue_s ObjectRef comparison unimplemented" + | EvalStackValue.ManagedPointer _ + | EvalStackValue.ObjectRef _ -> true | EvalStackValue.UserDefinedValueType _ -> failwith "TODO: Brtrue_s UserDefinedValueType comparison unimplemented" diff --git a/WoofWare.PawPrint/UnaryMetadataIlOp.fs b/WoofWare.PawPrint/UnaryMetadataIlOp.fs index f0164e7..7a709bc 100644 --- a/WoofWare.PawPrint/UnaryMetadataIlOp.fs +++ b/WoofWare.PawPrint/UnaryMetadataIlOp.fs @@ -554,6 +554,7 @@ module internal UnaryMetadataIlOp = ) let valueToStore, state = IlMachineState.popEvalStack thread state + let currentObj, state = IlMachineState.popEvalStack thread state let state, declaringTypeHandle, typeGenerics = IlMachineState.concretizeFieldForExecution loggerFactory baseClassTypes thread field state @@ -570,8 +571,6 @@ module internal UnaryMetadataIlOp = let valueToStore = EvalStackValue.toCliTypeCoerced zero valueToStore - let currentObj, state = IlMachineState.popEvalStack thread state - if field.Attributes.HasFlag FieldAttributes.Static then let state = IlMachineState.setStatic declaringTypeHandle field.Name valueToStore state @@ -589,10 +588,17 @@ module internal UnaryMetadataIlOp = match source with | ManagedPointerSource.Null -> failwith "TODO: raise NullReferenceException" | ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> + let var = IlMachineState.getLocalVariable sourceThread methodFrame whichVar state + + failwith + $"TODO: compute value with its field set. %O{var} field %s{field.Name} to %O{valueToStore}" + state |> IlMachineState.setLocalVariable sourceThread methodFrame whichVar valueToStore | ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> failwith "todo" | ManagedPointerSource.ArrayIndex (arr, index) -> + let var = IlMachineState.getArrayValue arr index state + failwith "TODO: compute value with its field set" state |> IlMachineState.setArrayValue arr valueToStore index | ManagedPointerSource.Heap addr -> match state.ManagedHeap.NonArrayObjects.TryGetValue addr with @@ -600,7 +606,11 @@ module internal UnaryMetadataIlOp = | true, v -> let v = { v with - Fields = v.Fields |> Map.add field.Name valueToStore + Fields = + v.Fields + |> List.replaceWhere (fun (x, _) -> + if x = field.Name then Some (x, valueToStore) else None + ) } let heap = @@ -611,6 +621,7 @@ module internal UnaryMetadataIlOp = { state with ManagedHeap = heap } + | ManagedPointerSource.Field (managedPointerSource, fieldName) -> failwith "todo" | EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo" | EvalStackValue.UserDefinedValueType _ -> failwith "todo" @@ -757,20 +768,30 @@ module internal UnaryMetadataIlOp = state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables .[int whichVar] + failwith $"TODO: need to get a field on {currentValue}" + IlMachineState.pushToEvalStack currentValue thread state | ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> let currentValue = state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int whichVar] + failwith $"TODO: need to get a field on {currentValue}" + IlMachineState.pushToEvalStack currentValue thread state | ManagedPointerSource.Heap managedHeapAddress -> match state.ManagedHeap.NonArrayObjects.TryGetValue managedHeapAddress with | false, _ -> failwith $"todo: array {managedHeapAddress}" - | true, v -> IlMachineState.pushToEvalStack v.Fields.[field.Name] thread state + | true, v -> + IlMachineState.pushToEvalStack + (v.Fields |> List.find (fun (x, _) -> field.Name = x) |> snd) + thread + state | ManagedPointerSource.ArrayIndex (arr, index) -> let currentValue = state |> IlMachineState.getArrayValue arr index + failwith $"TODO: need to get a field on {currentValue}" IlMachineState.pushToEvalStack currentValue thread state | ManagedPointerSource.Null -> failwith "TODO: raise NullReferenceException" + | ManagedPointerSource.Field _ -> failwith "TODO: get a field on a field ptr" | EvalStackValue.ObjectRef managedHeapAddress -> failwith $"todo: {managedHeapAddress}" | EvalStackValue.UserDefinedValueType fields -> let result = @@ -782,7 +803,64 @@ module internal UnaryMetadataIlOp = |> IlMachineState.advanceProgramCounter thread |> Tuple.withRight WhatWeDid.Executed - | Ldflda -> failwith "TODO: Ldflda unimplemented" + | Ldflda -> + let state, field = + match metadataToken with + | MetadataToken.FieldDefinition f -> + let field = activeAssy.Fields.[f] + // Map the field to have TypeDefn type parameters + let mappedField = + field |> FieldInfo.mapTypeGenerics (fun i _ -> TypeDefn.GenericTypeParameter i) + + state, mappedField + | MetadataToken.MemberReference m -> + let state, _, resolved, _ = + IlMachineState.resolveMember loggerFactory baseClassTypes thread activeAssy m state + + match resolved with + | Choice2Of2 field -> state, field + | Choice1Of2 _ -> failwith "Expected field in Ldflda but got method" + | _ -> failwith $"Unexpected in Ldflda: {metadataToken}" + + let source, state = IlMachineState.popEvalStack thread state + + // Ldflda needs to return a pointer to the field within the object/struct + let toPush = + match source with + | EvalStackValue.ObjectRef heapAddr -> + // For object references, we need to create a pointer to the field + // TODO: The current ManagedPointerSource doesn't have a case for field pointers + // We're using Heap pointer for now, but this doesn't capture the field offset + // This will need to be enhanced to support field-specific pointers + ManagedPointerSource.Heap heapAddr |> EvalStackValue.ManagedPointer + | EvalStackValue.ManagedPointer ptr -> + // If we already have a managed pointer, we need to handle field access + // through that pointer. For now, return the same pointer type + // TODO: This needs to track the field offset within the pointed-to object + match ptr with + | ManagedPointerSource.Null -> failwith "TODO: NullReferenceException in Ldflda" + | _ -> ptr |> EvalStackValue.ManagedPointer + | EvalStackValue.NativeInt (NativeIntSource.ManagedPointer ptr) -> + // Unmanaged pointer input produces unmanaged pointer output + // TODO: This also needs field offset tracking + match ptr with + | ManagedPointerSource.Null -> failwith "TODO: NullReferenceException in Ldflda" + | _ -> ptr |> NativeIntSource.ManagedPointer |> EvalStackValue.NativeInt + | EvalStackValue.NativeInt (NativeIntSource.Verbatim _) -> + // Native int that's not from a managed pointer + // This represents an unmanaged pointer scenario + failwith "TODO: Ldflda with unmanaged pointer - not allowed in verifiable code" + | EvalStackValue.UserDefinedValueType vt -> + // For value types on the stack, we need to store them somewhere + // and return a pointer to the field + // This is complex because we need to materialize the value type + failwith "TODO: Ldflda on value type - need to allocate temporary storage and create field pointer" + | _ -> failwith $"unexpected Ldflda source: {source}" + + state + |> IlMachineState.pushToEvalStack' toPush thread + |> IlMachineState.advanceProgramCounter thread + |> Tuple.withRight WhatWeDid.Executed | Ldsfld -> let state, field = match metadataToken with @@ -978,7 +1056,44 @@ module internal UnaryMetadataIlOp = IlMachineState.pushToEvalStack toPush thread state |> IlMachineState.advanceProgramCounter thread |> Tuple.withRight WhatWeDid.Executed - | Initobj -> failwith "TODO: Initobj unimplemented" + | Initobj -> + let addr, state = IlMachineState.popEvalStack thread state + let declaringTypeGenerics = currentMethod.DeclaringType.Generics + + let state, assy, ty = + match metadataToken with + | MetadataToken.TypeSpecification spec -> + let state, assy, ty = + IlMachineState.resolveTypeFromSpecConcrete + loggerFactory + baseClassTypes + spec + activeAssy + declaringTypeGenerics + currentMethod.Generics + state + + state, assy, ty + | x -> failwith $"unexpected in Initobj: %O{x}" + + let state = + match addr with + | EvalStackValue.ManagedPointer src -> + match src with + | ManagedPointerSource.Null -> failwith "TODO: probably NRE here" + | ManagedPointerSource.Heap _ -> failwith "TODO: heap" + | ManagedPointerSource.LocalVariable (thread, frame, var) -> + let oldValue = state |> IlMachineState.getLocalVariable thread frame var + let newValue = failwith "TODO" + state |> IlMachineState.setLocalVariable thread frame var newValue + | ManagedPointerSource.Argument (thread, frame, arg) -> failwith "TODO: Argument" + | ManagedPointerSource.ArrayIndex (arr, index) -> failwith "todo: array index" + | Field (managedPointerSource, fieldName) -> failwith "todo" + | addr -> failwith $"Bad address in Initobj: {addr}" + + state + |> IlMachineState.advanceProgramCounter thread + |> Tuple.withRight WhatWeDid.Executed | Ldsflda -> // TODO: check whether we should throw FieldAccessException @@ -1073,6 +1188,35 @@ module internal UnaryMetadataIlOp = | Stobj -> failwith "TODO: Stobj unimplemented" | Constrained -> failwith "TODO: Constrained unimplemented" | Ldtoken -> + // Helper function to handle type tokens and create RuntimeTypeHandle + let handleTypeToken (typeDefn : TypeDefn) (state : IlMachineState) : IlMachineState = + let ty = baseClassTypes.RuntimeTypeHandle + let field = ty.Fields |> List.exactlyOne + + if field.Name <> "m_type" then + failwith $"unexpected field name ${field.Name} for BCL type RuntimeTypeHandle" + + let methodGenerics = currentMethod.Generics + let typeGenerics = currentMethod.DeclaringType.Generics + + let state, handle = + IlMachineState.concretizeType + baseClassTypes + state + activeAssy.Name + typeGenerics + methodGenerics + typeDefn + + let alloc, state = IlMachineState.getOrAllocateType baseClassTypes handle state + + let vt = + { + Fields = [ "m_type", CliType.ObjectRef (Some alloc) ] + } + + IlMachineState.pushToEvalStack (CliType.ValueType vt) thread state + let state = match metadataToken with | MetadataToken.FieldDefinition h -> @@ -1086,35 +1230,12 @@ module internal UnaryMetadataIlOp = let field = ty.Fields |> List.exactlyOne failwith "" | MetadataToken.TypeDefinition h -> - let ty = baseClassTypes.RuntimeTypeHandle - let field = ty.Fields |> List.exactlyOne - - if field.Name <> "m_type" then - failwith $"unexpected field name ${field.Name} for BCL type RuntimeTypeHandle" - - let methodGenerics = currentMethod.Generics - - let typeGenerics = currentMethod.DeclaringType.Generics - let state, typeDefn = lookupTypeDefn baseClassTypes state activeAssy h - - let state, handle = - IlMachineState.concretizeType - baseClassTypes - state - activeAssy.Name - typeGenerics - methodGenerics - typeDefn - - let alloc, state = IlMachineState.getOrAllocateType baseClassTypes handle state - - let vt = - { - Fields = [ "m_type", CliType.ObjectRef (Some alloc) ] - } - - IlMachineState.pushToEvalStack (CliType.ValueType vt) thread state + handleTypeToken typeDefn state + | MetadataToken.TypeSpecification h -> + // Get the TypeSpec from the assembly and use its signature + let typeSpec = activeAssy.TypeSpecs.[h] + handleTypeToken typeSpec.Signature state | _ -> failwith $"Unexpected metadata token %O{metadataToken} in LdToken" state diff --git a/WoofWare.PawPrint/WoofWare.PawPrint.fsproj b/WoofWare.PawPrint/WoofWare.PawPrint.fsproj index b86ab97..299d17d 100644 --- a/WoofWare.PawPrint/WoofWare.PawPrint.fsproj +++ b/WoofWare.PawPrint/WoofWare.PawPrint.fsproj @@ -7,6 +7,7 @@ +