From 064deee8d5f415c84ea2f2b33eb30f88b720aece Mon Sep 17 00:00:00 2001 From: Smaug123 <3138005+Smaug123@users.noreply.github.com> Date: Mon, 11 Aug 2025 22:12:37 +0100 Subject: [PATCH] WIP --- .../sourcesPure/UnsafeAs.cs | 388 ++++++++++-------- WoofWare.PawPrint/IlMachineState.fs | 5 +- WoofWare.PawPrint/Intrinsics.fs | 10 +- WoofWare.PawPrint/NullaryIlOp.fs | 2 +- WoofWare.PawPrint/UnaryMetadataIlOp.fs | 76 +++- 5 files changed, 305 insertions(+), 176 deletions(-) diff --git a/WoofWare.PawPrint.Test/sourcesPure/UnsafeAs.cs b/WoofWare.PawPrint.Test/sourcesPure/UnsafeAs.cs index 6b996ff..e7ab6b1 100644 --- a/WoofWare.PawPrint.Test/sourcesPure/UnsafeAs.cs +++ b/WoofWare.PawPrint.Test/sourcesPure/UnsafeAs.cs @@ -33,181 +33,241 @@ public class TestUnsafeAs Value2 = -1 } + // Test 1: Int32 -> UInt32 reinterpretation + public static int Test1() + { + int original = -1; + ref uint reinterpreted = ref Unsafe.As(ref original); + + if (reinterpreted != 0xFFFFFFFF) + return 1; + + reinterpreted = 0x12345678; + if (original != 0x12345678) + return 2; + + original = int.MinValue; + if (reinterpreted != 0x80000000) + return 3; + + return 0; + } + + // Test 2: Struct -> Struct reinterpretation + public static int Test2() + { + Int32Wrapper wrapper = new Int32Wrapper { Value = 0x01020304 }; + ref FourBytes bytes = ref Unsafe.As(ref wrapper); + + if (BitConverter.IsLittleEndian) + { + if (bytes.B0 != 0x04) return 10; + if (bytes.B1 != 0x03) return 11; + if (bytes.B2 != 0x02) return 12; + if (bytes.B3 != 0x01) return 13; + } + else + { + if (bytes.B0 != 0x01) return 14; + if (bytes.B1 != 0x02) return 15; + if (bytes.B2 != 0x03) return 16; + if (bytes.B3 != 0x04) return 17; + } + + bytes.B0 = 0xFF; + int expectedValue = BitConverter.IsLittleEndian ? 0x010203FF : unchecked((int)0xFF020304); + if (wrapper.Value != expectedValue) + return 18; + + return 0; + } + + // Test 3: Int32 -> Two Int16s + public static int Test3() + { + int value = 0x12345678; + ref TwoInt16s halves = ref Unsafe.As(ref value); + + if (BitConverter.IsLittleEndian) + { + if (halves.First != unchecked((short)0x5678)) return 20; + if (halves.Second != 0x1234) return 21; + } + else + { + if (halves.First != 0x1234) return 22; + if (halves.Second != unchecked((short)0x5678)) return 23; + } + + halves.First = -1; + int expectedValue = BitConverter.IsLittleEndian ? 0x1234FFFF : unchecked((int)0xFFFF5678); + if (value != expectedValue) + return 24; + + return 0; + } + + // Test 4: Array element reinterpretation + public static int Test4() + { + int[] intArray = new int[] { 0x01020304, 0x05060708 }; + ref uint uintRef = ref Unsafe.As(ref intArray[0]); + + if (uintRef != 0x01020304u) + return 30; + + uintRef = 0xAABBCCDD; + if (intArray[0] != unchecked((int)0xAABBCCDD)) + return 31; + if (intArray[1] != 0x05060708) + return 32; + + return 0; + } + + // Test 5: Bool -> Byte + public static int Test5() + { + bool trueValue = true; + bool falseValue = false; + + ref byte trueByte = ref Unsafe.As(ref trueValue); + ref byte falseByte = ref Unsafe.As(ref falseValue); + + if (trueByte != 1) + return 40; + if (falseByte != 0) + return 41; + + // Modify through byte reference + trueByte = 0; + if (trueValue != false) + return 42; + + falseByte = 1; + if (falseValue != true) + return 43; + + return 0; + } + + // Test 6: Char -> UInt16 + public static int Test6() + { + char ch = 'A'; + ref ushort asUInt16 = ref Unsafe.As(ref ch); + + if (asUInt16 != 65) + return 50; + + asUInt16 = 0x03B1; // Greek lowercase alpha + if (ch != 'α') + return 51; + + return 0; + } + + // Test 7: Float -> Int32 + public static int Test7() + { + float floatValue = 1.0f; + ref int intBits = ref Unsafe.As(ref floatValue); + + // IEEE 754: 1.0f = 0x3F800000 + if (intBits != 0x3F800000) + return 60; + + intBits = 0x40000000; // 2.0f in IEEE 754 + if (floatValue != 2.0f) + return 61; + + floatValue = -0.0f; + if (intBits != unchecked((int)0x80000000)) + return 62; + + return 0; + } + + // Test 8: Double -> Int64 + public static int Test8() + { + double doubleValue = 1.0; + ref long longBits = ref Unsafe.As(ref doubleValue); + + // IEEE 754: 1.0 = 0x3FF0000000000000 + if (longBits != 0x3FF0000000000000L) + return 70; + + longBits = 0x4000000000000000L; // 2.0 in IEEE 754 + if (doubleValue != 2.0) + return 71; + + return 0; + } + + // Test 9: Enum -> Underlying type + public static int Test9() + { + TestEnum enumValue = TestEnum.Value1; + ref int underlying = ref Unsafe.As(ref enumValue); + + if (underlying != 0x12345678) + return 80; + + underlying = -1; + if (enumValue != TestEnum.Value2) + return 81; + + return 0; + } + + // Test 10: Local variable reinterpretation + public static int Test10() + { + int local = unchecked((int)0xDEADBEEF); + ref uint localAsUint = ref Unsafe.As(ref local); + + if (localAsUint != 0xDEADBEEF) + return 90; + + localAsUint = 0xCAFEBABE; + if (local != unchecked((int)0xCAFEBABE)) + return 91; + + return 0; + } + public static int Main(string[] argv) { - // Test 1: Int32 -> UInt32 reinterpretation - { - int original = -1; - ref uint reinterpreted = ref Unsafe.As(ref original); + var result = Test1(); + if (result != 0) return result; - if (reinterpreted != 0xFFFFFFFF) - return 1; + result = Test2(); + if (result != 0) return result; - reinterpreted = 0x12345678; - if (original != 0x12345678) - return 2; + result = Test3(); + if (result != 0) return result; - original = int.MinValue; - if (reinterpreted != 0x80000000) - return 3; - } + result = Test4(); + if (result != 0) return result; - // Test 2: Struct -> Struct reinterpretation - { - Int32Wrapper wrapper = new Int32Wrapper { Value = 0x01020304 }; - ref FourBytes bytes = ref Unsafe.As(ref wrapper); + result = Test5(); + if (result != 0) return result; - if (BitConverter.IsLittleEndian) - { - if (bytes.B0 != 0x04) return 10; - if (bytes.B1 != 0x03) return 11; - if (bytes.B2 != 0x02) return 12; - if (bytes.B3 != 0x01) return 13; - } - else - { - if (bytes.B0 != 0x01) return 14; - if (bytes.B1 != 0x02) return 15; - if (bytes.B2 != 0x03) return 16; - if (bytes.B3 != 0x04) return 17; - } + result = Test6(); + if (result != 0) return result; - bytes.B0 = 0xFF; - int expectedValue = BitConverter.IsLittleEndian ? 0x010203FF : unchecked((int)0xFF020304); - if (wrapper.Value != expectedValue) - return 18; - } + result = Test7(); + if (result != 0) return result; - // Test 3: Int32 -> Two Int16s - { - int value = 0x12345678; - ref TwoInt16s halves = ref Unsafe.As(ref value); + result = Test8(); + if (result != 0) return result; - if (BitConverter.IsLittleEndian) - { - if (halves.First != unchecked((short)0x5678)) return 20; - if (halves.Second != 0x1234) return 21; - } - else - { - if (halves.First != 0x1234) return 22; - if (halves.Second != unchecked((short)0x5678)) return 23; - } + result = Test9(); + if (result != 0) return result; - halves.First = -1; - int expectedValue = BitConverter.IsLittleEndian ? 0x1234FFFF : unchecked((int)0xFFFF5678); - if (value != expectedValue) - return 24; - } - - // Test 4: Array element reinterpretation - { - int[] intArray = new int[] { 0x01020304, 0x05060708 }; - ref uint uintRef = ref Unsafe.As(ref intArray[0]); - - if (uintRef != 0x01020304u) - return 30; - - uintRef = 0xAABBCCDD; - if (intArray[0] != unchecked((int)0xAABBCCDD)) - return 31; - if (intArray[1] != 0x05060708) - return 32; - } - - // Test 5: Bool -> Byte - { - bool trueValue = true; - bool falseValue = false; - - ref byte trueByte = ref Unsafe.As(ref trueValue); - ref byte falseByte = ref Unsafe.As(ref falseValue); - - if (trueByte != 1) - return 40; - if (falseByte != 0) - return 41; - - // Modify through byte reference - trueByte = 0; - if (trueValue != false) - return 42; - - falseByte = 1; - if (falseValue != true) - return 43; - } - - // Test 6: Char -> UInt16 - { - char ch = 'A'; - ref ushort asUInt16 = ref Unsafe.As(ref ch); - - if (asUInt16 != 65) - return 50; - - asUInt16 = 0x03B1; // Greek lowercase alpha - if (ch != 'α') - return 51; - } - - // Test 7: Float -> Int32 - { - float floatValue = 1.0f; - ref int intBits = ref Unsafe.As(ref floatValue); - - // IEEE 754: 1.0f = 0x3F800000 - if (intBits != 0x3F800000) - return 60; - - intBits = 0x40000000; // 2.0f in IEEE 754 - if (floatValue != 2.0f) - return 61; - - floatValue = -0.0f; - if (intBits != unchecked((int)0x80000000)) - return 62; - } - - // Test 8: Double -> Int64 - { - double doubleValue = 1.0; - ref long longBits = ref Unsafe.As(ref doubleValue); - - // IEEE 754: 1.0 = 0x3FF0000000000000 - if (longBits != 0x3FF0000000000000L) - return 70; - - longBits = 0x4000000000000000L; // 2.0 in IEEE 754 - if (doubleValue != 2.0) - return 71; - } - - // Test 9: Enum -> Underlying type - { - TestEnum enumValue = TestEnum.Value1; - ref int underlying = ref Unsafe.As(ref enumValue); - - if (underlying != 0x12345678) - return 80; - - underlying = -1; - if (enumValue != TestEnum.Value2) - return 81; - } - - // Test 10: Local variable reinterpretation - { - int local = unchecked((int)0xDEADBEEF); - ref uint localAsUint = ref Unsafe.As(ref local); - - if (localAsUint != 0xDEADBEEF) - return 90; - - localAsUint = 0xCAFEBABE; - if (local != unchecked((int)0xCAFEBABE)) - return 91; - } + result = Test10(); + if (result != 0) return result; // All tests passed return 0; diff --git a/WoofWare.PawPrint/IlMachineState.fs b/WoofWare.PawPrint/IlMachineState.fs index 21f4bd4..0a0f90a 100644 --- a/WoofWare.PawPrint/IlMachineState.fs +++ b/WoofWare.PawPrint/IlMachineState.fs @@ -1194,6 +1194,7 @@ module IlMachineState = (baseClassTypes : BaseClassTypes) (currentThread : ThreadId) (assy : DumpedAssembly) + (genericMethodTypeArgs : ImmutableArray) (m : MemberReferenceHandle) (state : IlMachineState) : IlMachineState * @@ -1330,7 +1331,7 @@ module IlMachineState = state (state.ActiveAssembly(currentThread).Name) concreteExtractedTypeArgs - ImmutableArray.Empty + genericMethodTypeArgs ty ) @@ -1347,7 +1348,7 @@ module IlMachineState = state assy.Name concreteExtractedTypeArgs - ImmutableArray.Empty + genericMethodTypeArgs ty ) diff --git a/WoofWare.PawPrint/Intrinsics.fs b/WoofWare.PawPrint/Intrinsics.fs index d54eaa4..80724f7 100644 --- a/WoofWare.PawPrint/Intrinsics.fs +++ b/WoofWare.PawPrint/Intrinsics.fs @@ -316,12 +316,12 @@ module Intrinsics = | [ input ], ret -> input, ret | _ -> failwith "bad signature Unsafe.As" - let genericArg = Seq.exactlyOne methodToCall.Generics + let from, to_ = + match Seq.toList methodToCall.Generics with + | [ from ; to_ ] -> from, to_ + | _ -> failwith "bad generics" - let state = - state - |> IlMachineState.loadArgument currentThread 0 - |> IlMachineState.advanceProgramCounter currentThread + let state = state |> IlMachineState.advanceProgramCounter currentThread Some state | a, b, c -> failwith $"TODO: implement JIT intrinsic {a}.{b}.{c}" diff --git a/WoofWare.PawPrint/NullaryIlOp.fs b/WoofWare.PawPrint/NullaryIlOp.fs index dacd518..ce66dae 100644 --- a/WoofWare.PawPrint/NullaryIlOp.fs +++ b/WoofWare.PawPrint/NullaryIlOp.fs @@ -52,7 +52,7 @@ module NullaryIlOp = | EvalStackValue.NativeInt nativeIntSource -> failwith $"TODO: Native int pointer dereferencing not implemented for {targetType}" | EvalStackValue.ObjectRef managedHeapAddress -> - failwith "TODO: Object reference dereferencing not implemented" + IlMachineState.dereferencePointer state (ManagedPointerSource.Heap managedHeapAddress) | other -> failwith $"Unexpected eval stack value for Ldind operation: {other}" let loadedValue = loadedValue |> EvalStackValue.ofCliType diff --git a/WoofWare.PawPrint/UnaryMetadataIlOp.fs b/WoofWare.PawPrint/UnaryMetadataIlOp.fs index 0d436ee..819786e 100644 --- a/WoofWare.PawPrint/UnaryMetadataIlOp.fs +++ b/WoofWare.PawPrint/UnaryMetadataIlOp.fs @@ -122,6 +122,23 @@ module internal UnaryMetadataIlOp = | MetadataToken.MethodSpecification h -> let spec = activeAssy.MethodSpecs.[h] + let state, methodGenerics = + ((state, []), spec.Signature) + ||> Seq.fold (fun (state, acc) typeDefn -> + let state, concreteType = + IlMachineState.concretizeType + baseClassTypes + state + (state.ActiveAssembly thread).Name + currentMethod.DeclaringType.Generics + ImmutableArray.Empty + typeDefn + + state, concreteType :: acc + ) + + let methodGenerics = List.rev methodGenerics |> ImmutableArray.CreateRange + match spec.Method with | MetadataToken.MethodDef token -> let method = @@ -136,6 +153,7 @@ module internal UnaryMetadataIlOp = baseClassTypes thread (state.ActiveAssembly thread) + methodGenerics ref state @@ -150,6 +168,7 @@ module internal UnaryMetadataIlOp = baseClassTypes thread (state.ActiveAssembly thread) + ImmutableArray.Empty h state @@ -205,6 +224,23 @@ module internal UnaryMetadataIlOp = | MetadataToken.MethodSpecification h -> let spec = activeAssy.MethodSpecs.[h] + let state, methodGenerics = + ((state, []), spec.Signature) + ||> Seq.fold (fun (state, acc) typeDefn -> + let state, concreteType = + IlMachineState.concretizeType + baseClassTypes + state + (state.ActiveAssembly thread).Name + currentMethod.DeclaringType.Generics + ImmutableArray.Empty + typeDefn + + state, concreteType :: acc + ) + + let methodGenerics = List.rev methodGenerics |> ImmutableArray.CreateRange + match spec.Method with | MetadataToken.MethodDef token -> let method = @@ -219,6 +255,7 @@ module internal UnaryMetadataIlOp = baseClassTypes thread (state.ActiveAssembly thread) + methodGenerics ref state @@ -233,6 +270,7 @@ module internal UnaryMetadataIlOp = baseClassTypes thread (state.ActiveAssembly thread) + ImmutableArray.Empty h state @@ -293,6 +331,7 @@ module internal UnaryMetadataIlOp = baseClassTypes thread (state.ActiveAssembly thread) + ImmutableArray.Empty mr state @@ -537,7 +576,14 @@ module internal UnaryMetadataIlOp = state, field | MetadataToken.MemberReference mr -> let state, _, field, _ = - IlMachineState.resolveMember loggerFactory baseClassTypes thread activeAssy mr state + IlMachineState.resolveMember + loggerFactory + baseClassTypes + thread + activeAssy + ImmutableArray.Empty + mr + state match field with | Choice1Of2 _method -> failwith "member reference was unexpectedly a method" @@ -650,6 +696,7 @@ module internal UnaryMetadataIlOp = baseClassTypes thread (state.ActiveAssembly thread) + ImmutableArray.Empty mr state @@ -709,7 +756,14 @@ module internal UnaryMetadataIlOp = state, field | MetadataToken.MemberReference mr -> let state, assyName, field, _ = - IlMachineState.resolveMember loggerFactory baseClassTypes thread activeAssy mr state + IlMachineState.resolveMember + loggerFactory + baseClassTypes + thread + activeAssy + ImmutableArray.Empty + mr + state match field with | Choice1Of2 _method -> failwith "member reference was unexpectedly a method" @@ -816,7 +870,14 @@ module internal UnaryMetadataIlOp = state, mappedField | MetadataToken.MemberReference m -> let state, _, resolved, _ = - IlMachineState.resolveMember loggerFactory baseClassTypes thread activeAssy m state + IlMachineState.resolveMember + loggerFactory + baseClassTypes + thread + activeAssy + ImmutableArray.Empty + m + state match resolved with | Choice2Of2 field -> state, field @@ -876,7 +937,14 @@ module internal UnaryMetadataIlOp = state, field | MetadataToken.MemberReference mr -> let state, _, field, _ = - IlMachineState.resolveMember loggerFactory baseClassTypes thread activeAssy mr state + IlMachineState.resolveMember + loggerFactory + baseClassTypes + thread + activeAssy + ImmutableArray.Empty + mr + state match field with | Choice1Of2 _method -> failwith "member reference was unexpectedly a method"