diff --git a/CSharpExample/CSharpExample.csproj b/CSharpExample/CSharpExample.csproj index 47f6818..6b0a667 100644 --- a/CSharpExample/CSharpExample.csproj +++ b/CSharpExample/CSharpExample.csproj @@ -4,6 +4,8 @@ net9.0 Exe true + false + false diff --git a/WoofWare.PawPrint.Domain/IlOp.fs b/WoofWare.PawPrint.Domain/IlOp.fs index ff2ef94..4a371c9 100644 --- a/WoofWare.PawPrint.Domain/IlOp.fs +++ b/WoofWare.PawPrint.Domain/IlOp.fs @@ -123,6 +123,7 @@ type NullaryIlOp = | Localloc /// Dereferences the pointer on top of the stack, and pushes the target to the stack as a type O (object reference). | Ldind_ref + /// Stores an object reference value at a supplied address. | Stind_ref | Stind_I | Stind_I1 @@ -166,18 +167,25 @@ type NullaryIlOp = | Ldelem_u8 | Ldelem_r4 | Ldelem_r8 + /// Loads the element containing an object reference at a specified array index onto the top of the evaluation stack as type O (object reference). | Ldelem_ref + /// Replaces the array element at a given index with the nativeint value on the evaluation stack. | Stelem_i + /// Replaces the array element at a given index with the int8 value on the evaluation stack. | Stelem_i1 | Stelem_u1 + /// Replaces the array element at a given index with the int16 value on the evaluation stack. | Stelem_i2 | Stelem_u2 + /// Replaces the array element at a given index with the int32 value on the evaluation stack. | Stelem_i4 | Stelem_u4 + /// Replaces the array element at a given index with the int64 value on the evaluation stack. | Stelem_i8 | Stelem_u8 | Stelem_r4 | Stelem_r8 + /// Replaces the array element at a given index with the object ref value (type O) on the evaluation stack. | Stelem_ref | Cpblk | Initblk @@ -384,6 +392,7 @@ type UnaryConstIlOp = | Bge_un_s of int8 | Bgt_un_s of int8 | Ble_un_s of int8 + /// Transfers control to a target instruction if the first value is less than the second value. | Blt_un_s of int8 | Bne_un of int32 | Bge_un of int32 @@ -529,6 +538,7 @@ type UnaryMetadataTokenIlOp = | Newobj | Newarr | Box + /// Loads the address of the array element at a specified array index onto the top of the evaluation stack as type "managed pointer" | Ldelema | Isinst /// Pop value from stack; pop object ref from stack; set specified field on that object to that value. diff --git a/WoofWare.PawPrint.Test/TestPureCases.fs b/WoofWare.PawPrint.Test/TestPureCases.fs index 827b7e4..0408375 100644 --- a/WoofWare.PawPrint.Test/TestPureCases.fs +++ b/WoofWare.PawPrint.Test/TestPureCases.fs @@ -311,6 +311,12 @@ module TestPureCases = |> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i)) |> Some } + { + FileName = "Ldelema.cs" + ExpectedReturnCode = 0 + NativeImpls = MockEnv.make () + LocalVariablesOfMain = None + } ] [] diff --git a/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj b/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj index a5d2826..3ca83b3 100644 --- a/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj +++ b/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj @@ -21,6 +21,7 @@ + diff --git a/WoofWare.PawPrint.Test/sourcesPure/Ldelema.cs b/WoofWare.PawPrint.Test/sourcesPure/Ldelema.cs new file mode 100644 index 0000000..735faa7 --- /dev/null +++ b/WoofWare.PawPrint.Test/sourcesPure/Ldelema.cs @@ -0,0 +1,66 @@ +using System; + +/// +/// A simple value type used for testing ldelema. +/// +public struct TestStruct +{ + public int Value; +} + +public class Program +{ + /// + /// Modifies a TestStruct instance by reference. Calling this with an array element + /// (e.g., `ModifyStruct(ref array[i], ...)` ) will cause the C# compiler to + /// generate an `ldelema` instruction. + /// + /// A reference to the TestStruct to modify. + /// The new value to assign. + public static void ModifyStruct(ref TestStruct s, int newValue) + { + s.Value = newValue; + } + + /// + /// Modifies a string reference. + /// + /// A reference to a string variable. + /// The new string to assign. + public static void ModifyStringRef(ref string s, string newValue) + { + s = newValue; + } + + /// + /// Main entry point for the ldelema test. + /// + /// 0 if all tests pass, otherwise a non-zero error code. + public static int Main(string[] args) + { + // --- Test 1: Modifying a value type element in an array --- + TestStruct[] structArray = new TestStruct[5]; + structArray[2].Value = 100; + + // This call should generate an `ldelema` instruction to get the address of structArray[2]. + ModifyStruct(ref structArray[2], 999); + + if (structArray[2].Value != 999) + { + return 301; // Unique error code for this test + } + + // --- Test 2: Modifying a reference type element in an array --- + string[] stringArray = new string[] { "alpha", "beta", "gamma" }; + + // This call should also generate an `ldelema` instruction. + ModifyStringRef(ref stringArray[1], "zeta"); + + if (stringArray[1] != "zeta") + { + return 302; // Unique error code for this test + } + + return 0; // Success + } +} diff --git a/WoofWare.PawPrint/BasicCliType.fs b/WoofWare.PawPrint/BasicCliType.fs index ec6f6f2..98e622f 100644 --- a/WoofWare.PawPrint/BasicCliType.fs +++ b/WoofWare.PawPrint/BasicCliType.fs @@ -41,6 +41,7 @@ type ManagedPointerSource = | LocalVariable of sourceThread : ThreadId * methodFrame : int * whichVar : uint16 | Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16 | Heap of ManagedHeapAddress + | ArrayIndex of arr : ManagedHeapAddress * index : int | Null override this.ToString () = @@ -51,6 +52,7 @@ type ManagedPointerSource = $"" | ManagedPointerSource.Argument (source, method, var) -> $"" + | ManagedPointerSource.ArrayIndex (arr, index) -> $"" [] type UnsignedNativeIntSource = @@ -129,6 +131,7 @@ type CliRuntimePointerSource = | LocalVariable of sourceThread : ThreadId * methodFrame : int * whichVar : uint16 | Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16 | Heap of ManagedHeapAddress + | ArrayIndex of arr : ManagedHeapAddress * index : int | Null type CliRuntimePointer = diff --git a/WoofWare.PawPrint/BinaryArithmetic.fs b/WoofWare.PawPrint/BinaryArithmetic.fs new file mode 100644 index 0000000..0d0e390 --- /dev/null +++ b/WoofWare.PawPrint/BinaryArithmetic.fs @@ -0,0 +1,50 @@ +namespace WoofWare.PawPrint + +#nowarn "42" + +type IArithmeticOperation = + abstract Int32Int32 : int32 -> int32 -> int32 + abstract Int64Int64 : int64 -> int64 -> int64 + abstract FloatFloat : float -> float -> float + abstract Name : string + +[] +module ArithmeticOperation = + let add = + { new IArithmeticOperation with + member _.Int32Int32 a b = (# "add" a b : int32 #) + member _.Int64Int64 a b = (# "add" a b : int64 #) + member _.FloatFloat a b = (# "add" a b : float #) + member _.Name = "add" + } + + let mul = + { new IArithmeticOperation with + member _.Int32Int32 a b = (# "mul" a b : int32 #) + member _.Int64Int64 a b = (# "mul" a b : int64 #) + member _.FloatFloat a b = (# "mul" a b : float #) + member _.Name = "mul" + } + +[] +module BinaryArithmetic = + let execute (op : IArithmeticOperation) (val1 : EvalStackValue) (val2 : EvalStackValue) : EvalStackValue = + // see table at https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.add?view=net-9.0 + match val1, val2 with + | EvalStackValue.Int32 val1, EvalStackValue.Int32 val2 -> op.Int32Int32 val1 val2 |> EvalStackValue.Int32 + | EvalStackValue.Int32 val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.NativeInt + | EvalStackValue.Int32 val1, EvalStackValue.ManagedPointer val2 -> failwith "" |> EvalStackValue.ManagedPointer + | EvalStackValue.Int32 val1, EvalStackValue.ObjectRef val2 -> failwith "" |> EvalStackValue.ObjectRef + | EvalStackValue.Int64 val1, EvalStackValue.Int64 val2 -> op.Int64Int64 val1 val2 |> EvalStackValue.Int64 + | EvalStackValue.NativeInt val1, EvalStackValue.Int32 val2 -> failwith "" |> EvalStackValue.NativeInt + | EvalStackValue.NativeInt val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.NativeInt + | EvalStackValue.NativeInt val1, EvalStackValue.ManagedPointer val2 -> + failwith "" |> EvalStackValue.ManagedPointer + | EvalStackValue.NativeInt val1, EvalStackValue.ObjectRef val2 -> failwith "" |> EvalStackValue.ObjectRef + | EvalStackValue.Float val1, EvalStackValue.Float val2 -> op.FloatFloat val1 val2 |> EvalStackValue.Float + | EvalStackValue.ManagedPointer val1, EvalStackValue.NativeInt val2 -> + failwith "" |> EvalStackValue.ManagedPointer + | EvalStackValue.ObjectRef val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.ObjectRef + | EvalStackValue.ManagedPointer val1, EvalStackValue.Int32 val2 -> failwith "" |> EvalStackValue.ManagedPointer + | EvalStackValue.ObjectRef val1, EvalStackValue.Int32 val2 -> failwith "" |> EvalStackValue.ObjectRef + | val1, val2 -> failwith $"invalid %s{op.Name} operation: {val1} and {val2}" diff --git a/WoofWare.PawPrint/EvalStack.fs b/WoofWare.PawPrint/EvalStack.fs index c964435..0c66eef 100644 --- a/WoofWare.PawPrint/EvalStack.fs +++ b/WoofWare.PawPrint/EvalStack.fs @@ -162,6 +162,8 @@ module EvalStackValue = |> 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))) | EvalStackValue.NativeInt nativeIntSource -> match nativeIntSource with | NativeIntSource.Verbatim 0L -> CliType.ObjectRef None @@ -200,6 +202,7 @@ module EvalStackValue = CliRuntimePointerSource.Argument (sourceThread, methodFrame, var) |> CliRuntimePointer.Managed |> CliType.RuntimePointer + | ManagedPointerSource.ArrayIndex _ -> failwith "TODO" | EvalStackValue.NativeInt intSrc -> match intSrc with | NativeIntSource.Verbatim i -> CliType.RuntimePointer (CliRuntimePointer.Unmanaged i) @@ -215,6 +218,7 @@ module EvalStackValue = ) | ManagedPointerSource.Argument (a, b, c) -> CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Argument (a, b, c))) + | ManagedPointerSource.ArrayIndex _ -> failwith "TODO" | NativeIntSource.FunctionPointer methodInfo -> CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer methodInfo)) | NativeIntSource.TypeHandlePtr int64 -> failwith "todo" @@ -269,6 +273,8 @@ module EvalStackValue = | CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, var) -> ManagedPointerSource.LocalVariable (sourceThread, methodFrame, var) |> EvalStackValue.ManagedPointer + | CliRuntimePointerSource.ArrayIndex (arr, ind) -> + ManagedPointerSource.ArrayIndex (arr, ind) |> EvalStackValue.ManagedPointer | CliRuntimePointerSource.Argument (sourceThread, methodFrame, var) -> ManagedPointerSource.Argument (sourceThread, methodFrame, var) |> EvalStackValue.ManagedPointer diff --git a/WoofWare.PawPrint/EvalStackValueComparisons.fs b/WoofWare.PawPrint/EvalStackValueComparisons.fs new file mode 100644 index 0000000..188dfdc --- /dev/null +++ b/WoofWare.PawPrint/EvalStackValueComparisons.fs @@ -0,0 +1,71 @@ +namespace WoofWare.PawPrint + +[] +module EvalStackValueComparisons = + + let clt (var1 : EvalStackValue) (var2 : EvalStackValue) : bool = + match var1, var2 with + | EvalStackValue.Int64 var1, EvalStackValue.Int64 var2 -> var1 < var2 + | EvalStackValue.Float var1, EvalStackValue.Float var2 -> failwith "TODO: Clt float comparison unimplemented" + | EvalStackValue.ObjectRef var1, EvalStackValue.ObjectRef var2 -> + failwith $"Clt instruction invalid for comparing object refs, {var1} vs {var2}" + | EvalStackValue.ObjectRef var1, other -> failwith $"invalid comparison, ref %O{var1} vs %O{other}" + | other, EvalStackValue.ObjectRef var2 -> failwith $"invalid comparison, %O{other} vs ref %O{var2}" + | EvalStackValue.Float i, other -> failwith $"invalid comparison, float %f{i} vs %O{other}" + | other, EvalStackValue.Float i -> failwith $"invalid comparison, %O{other} vs float %f{i}" + | EvalStackValue.Int64 i, other -> failwith $"invalid comparison, int64 %i{i} vs %O{other}" + | other, EvalStackValue.Int64 i -> failwith $"invalid comparison, %O{other} vs int64 %i{i}" + | EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> var1 < var2 + | EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 -> + failwith "TODO: Clt Int32 vs NativeInt comparison unimplemented" + | EvalStackValue.Int32 i, other -> failwith $"invalid comparison, int32 %i{i} vs %O{other}" + | EvalStackValue.NativeInt var1, EvalStackValue.Int32 var2 -> + failwith "TODO: Clt NativeInt vs Int32 comparison unimplemented" + | other, EvalStackValue.Int32 var2 -> failwith $"invalid comparison, {other} vs int32 {var2}" + | EvalStackValue.NativeInt var1, EvalStackValue.NativeInt var2 -> NativeIntSource.isLess var1 var2 + | EvalStackValue.NativeInt var1, other -> failwith $"invalid comparison, nativeint {var1} vs %O{other}" + | EvalStackValue.ManagedPointer managedPointerSource, NativeInt int64 -> + failwith "TODO: Clt ManagedPointer vs NativeInt comparison unimplemented" + | EvalStackValue.ManagedPointer managedPointerSource, ManagedPointer pointerSource -> + failwith "TODO: Clt ManagedPointer vs ManagedPointer comparison unimplemented" + | EvalStackValue.ManagedPointer managedPointerSource, UserDefinedValueType _ -> + failwith "TODO: Clt ManagedPointer vs UserDefinedValueType comparison unimplemented" + | EvalStackValue.UserDefinedValueType _, NativeInt int64 -> + failwith "TODO: Clt UserDefinedValueType vs NativeInt comparison unimplemented" + | EvalStackValue.UserDefinedValueType _, ManagedPointer managedPointerSource -> + failwith "TODO: Clt UserDefinedValueType vs ManagedPointer comparison unimplemented" + | EvalStackValue.UserDefinedValueType _, UserDefinedValueType _ -> + failwith "TODO: Clt UserDefinedValueType vs UserDefinedValueType comparison unimplemented" + + let ceq var1 var2 : bool = + // Table III.4 + match var1, var2 with + | EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> var1 = var2 + | EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 -> failwith "TODO: int32 CEQ nativeint" + | EvalStackValue.Int32 _, _ -> failwith $"bad ceq: Int32 vs {var2}" + | EvalStackValue.Int64 var1, EvalStackValue.Int64 var2 -> var1 = var2 + | EvalStackValue.Int64 _, _ -> failwith $"bad ceq: Int64 vs {var2}" + | EvalStackValue.Float var1, EvalStackValue.Float var2 -> failwith "TODO: float CEQ float" + | EvalStackValue.Float _, _ -> failwith $"bad ceq: Float vs {var2}" + | EvalStackValue.NativeInt var1, EvalStackValue.NativeInt var2 -> + match var1, var2 with + | NativeIntSource.FunctionPointer f1, NativeIntSource.FunctionPointer f2 -> + if f1 = f2 then + true + else + failwith $"TODO(CEQ): nativeint vs nativeint, {f1} vs {f2}" + | NativeIntSource.TypeHandlePtr f1, NativeIntSource.TypeHandlePtr f2 -> f1 = f2 + | NativeIntSource.Verbatim f1, NativeIntSource.Verbatim f2 -> f1 = f2 + | NativeIntSource.ManagedPointer f1, NativeIntSource.ManagedPointer f2 -> f1 = f2 + | _, _ -> failwith $"TODO (CEQ): nativeint vs nativeint, {var1} vs {var2}" + | EvalStackValue.NativeInt var1, EvalStackValue.Int32 var2 -> failwith $"TODO (CEQ): nativeint vs int32" + | EvalStackValue.NativeInt var1, EvalStackValue.ManagedPointer var2 -> + failwith $"TODO (CEQ): nativeint vs managed pointer" + | EvalStackValue.NativeInt _, _ -> failwith $"bad ceq: NativeInt vs {var2}" + | EvalStackValue.ObjectRef var1, EvalStackValue.ObjectRef var2 -> var1 = var2 + | EvalStackValue.ObjectRef _, _ -> failwith $"bad ceq: ObjectRef vs {var2}" + | EvalStackValue.ManagedPointer var1, EvalStackValue.ManagedPointer var2 -> var1 = var2 + | EvalStackValue.ManagedPointer var1, EvalStackValue.NativeInt var2 -> + failwith $"TODO (CEQ): managed pointer vs nativeint" + | EvalStackValue.ManagedPointer _, _ -> failwith $"bad ceq: ManagedPointer vs {var2}" + | EvalStackValue.UserDefinedValueType _, _ -> failwith $"bad ceq: UserDefinedValueType vs {var2}" diff --git a/WoofWare.PawPrint/ExternImplementations/System.Threading.Monitor.fs b/WoofWare.PawPrint/ExternImplementations/System.Threading.Monitor.fs index 497ad47..de2acd2 100644 --- a/WoofWare.PawPrint/ExternImplementations/System.Threading.Monitor.fs +++ b/WoofWare.PawPrint/ExternImplementations/System.Threading.Monitor.fs @@ -86,6 +86,7 @@ module System_Threading_Monitor = | ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> failwith "not really expecting to *edit* an argument..." | ManagedPointerSource.Heap addr -> failwith "todo: managed heap" + | ManagedPointerSource.ArrayIndex _ -> failwith "todo: array index" (state, WhatWeDid.Executed) |> ExecutionResult.Stepped diff --git a/WoofWare.PawPrint/IlMachineState.fs b/WoofWare.PawPrint/IlMachineState.fs index aa10bca..6cedfe0 100644 --- a/WoofWare.PawPrint/IlMachineState.fs +++ b/WoofWare.PawPrint/IlMachineState.fs @@ -587,6 +587,7 @@ module IlMachineState = | ManagedPointerSource.Heap managedHeapAddress -> CliRuntimePointer.Managed (CliRuntimePointerSource.Heap managedHeapAddress) | ManagedPointerSource.Null -> failwith "todo" + | ManagedPointerSource.ArrayIndex _ -> failwith "TODO" | x -> failwith $"TODO: Unsafe.AsPointer(%O{x})" pushToEvalStack (CliType.RuntimePointer toPush) currentThread state @@ -616,6 +617,34 @@ module IlMachineState = |> pushToEvalStack' result currentThread |> advanceProgramCounter currentThread |> Some + | "System.Private.CoreLib", "String", "Equals" -> + let arg1, state = popEvalStack currentThread state + + let arg1 = + match arg1 with + | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap h) -> h + | EvalStackValue.Int32 _ + | EvalStackValue.Int64 _ + | EvalStackValue.Float _ -> failwith $"this isn't a string! {arg1}" + | _ -> failwith $"TODO: %O{arg1}" + + let arg2, state = popEvalStack currentThread state + + let arg2 = + match arg2 with + | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap h) -> h + | EvalStackValue.Int32 _ + | EvalStackValue.Int64 _ + | EvalStackValue.Float _ -> failwith $"this isn't a string! {arg2}" + | _ -> failwith $"TODO: %O{arg2}" + + if arg1 = arg2 then + state + |> pushToEvalStack (CliType.OfBool true) currentThread + |> advanceProgramCounter currentThread + |> Some + else + failwith "TODO" | a, b, c -> failwith $"TODO: implement JIT intrinsic {a}.{b}.{c}" |> Option.map (fun s -> s.WithThreadSwitchedToAssembly callerAssy currentThread |> fst) @@ -1150,6 +1179,9 @@ module IlMachineState = ManagedHeap = heap } + let getArrayValue (arrayAllocation : ManagedHeapAddress) (index : int) (state : IlMachineState) : CliType = + ManagedHeap.GetArrayValue arrayAllocation index state.ManagedHeap + let jumpProgramCounter (thread : ThreadId) (bytes : int) (state : IlMachineState) : IlMachineState = { state with ThreadState = diff --git a/WoofWare.PawPrint/ManagedHeap.fs b/WoofWare.PawPrint/ManagedHeap.fs index 7765458..0de1a88 100644 --- a/WoofWare.PawPrint/ManagedHeap.fs +++ b/WoofWare.PawPrint/ManagedHeap.fs @@ -109,6 +109,16 @@ type ManagedHeap = ManagedHeapAddress addr, heap + static member GetArrayValue (alloc : ManagedHeapAddress) (offset : int) (heap : ManagedHeap) : CliType = + match heap.Arrays.TryGetValue alloc with + | false, _ -> failwith "TODO: array not on heap" + | true, arr -> + + if offset < 0 || offset >= arr.Length then + failwith "TODO: raise IndexOutOfBoundsException" + + arr.Elements.[offset] + static member SetArrayValue (alloc : ManagedHeapAddress) (offset : int) @@ -124,6 +134,9 @@ type ManagedHeap = match arr with | None -> failwith "tried to change element of nonexistent array" | Some arr -> + if offset < 0 || offset >= arr.Elements.Length then + failwith "TODO: throw somehow" + { arr with Elements = arr.Elements.SetItem (offset, v) } diff --git a/WoofWare.PawPrint/NullaryIlOp.fs b/WoofWare.PawPrint/NullaryIlOp.fs index c6c01bc..fcd304e 100644 --- a/WoofWare.PawPrint/NullaryIlOp.fs +++ b/WoofWare.PawPrint/NullaryIlOp.fs @@ -1,33 +1,7 @@ namespace WoofWare.PawPrint -#nowarn "42" - open Microsoft.Extensions.Logging -type private IArithmeticOperation = - abstract Int32Int32 : int32 -> int32 -> int32 - abstract Int64Int64 : int64 -> int64 -> int64 - abstract FloatFloat : float -> float -> float - abstract Name : string - -[] -module private ArithmeticOperation = - let add = - { new IArithmeticOperation with - member _.Int32Int32 a b = (# "add" a b : int32 #) - member _.Int64Int64 a b = (# "add" a b : int64 #) - member _.FloatFloat a b = (# "add" a b : float #) - member _.Name = "add" - } - - let mul = - { new IArithmeticOperation with - member _.Int32Int32 a b = (# "mul" a b : int32 #) - member _.Int64Int64 a b = (# "mul" a b : int64 #) - member _.FloatFloat a b = (# "mul" a b : float #) - member _.Name = "mul" - } - [] [] module NullaryIlOp = @@ -72,6 +46,7 @@ module NullaryIlOp = | 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 _ -> failwith "TODO: array index pointer dereferencing not implemented" // Unified Ldind implementation let private executeLdind @@ -103,41 +78,6 @@ module NullaryIlOp = (state, WhatWeDid.Executed) |> ExecutionResult.Stepped - let private binaryArithmeticOperation - (op : IArithmeticOperation) - (currentThread : ThreadId) - (state : IlMachineState) - = - let val1, state = IlMachineState.popEvalStack currentThread state - let val2, state = IlMachineState.popEvalStack currentThread state - // see table at https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.add?view=net-9.0 - let result = - match val1, val2 with - | EvalStackValue.Int32 val1, EvalStackValue.Int32 val2 -> - (# "add" val1 val2 : int32 #) |> EvalStackValue.Int32 - | EvalStackValue.Int32 val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.NativeInt - | EvalStackValue.Int32 val1, EvalStackValue.ManagedPointer val2 -> - failwith "" |> EvalStackValue.ManagedPointer - | EvalStackValue.Int32 val1, EvalStackValue.ObjectRef val2 -> failwith "" |> EvalStackValue.ObjectRef - | EvalStackValue.Int64 val1, EvalStackValue.Int64 val2 -> - (# "add" val1 val2 : int64 #) |> EvalStackValue.Int64 - | EvalStackValue.NativeInt val1, EvalStackValue.Int32 val2 -> failwith "" |> EvalStackValue.NativeInt - | EvalStackValue.NativeInt val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.NativeInt - | EvalStackValue.NativeInt val1, EvalStackValue.ManagedPointer val2 -> - failwith "" |> EvalStackValue.ManagedPointer - | EvalStackValue.NativeInt val1, EvalStackValue.ObjectRef val2 -> failwith "" |> EvalStackValue.ObjectRef - | EvalStackValue.Float val1, EvalStackValue.Float val2 -> - (# "add" val1 val2 : float #) |> EvalStackValue.Float - | EvalStackValue.ManagedPointer val1, EvalStackValue.NativeInt val2 -> - failwith "" |> EvalStackValue.ManagedPointer - | EvalStackValue.ObjectRef val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.ObjectRef - | EvalStackValue.ManagedPointer val1, EvalStackValue.Int32 val2 -> - failwith "" |> EvalStackValue.ManagedPointer - | EvalStackValue.ObjectRef val1, EvalStackValue.Int32 val2 -> failwith "" |> EvalStackValue.ObjectRef - | val1, val2 -> failwith $"invalid %s{op.Name} operation: {val1} and {val2}" - - result, state - let private stind (varType : CliType) (currentThread : ThreadId) (state : IlMachineState) : IlMachineState = // TODO: throw NullReferenceException if unaligned target let valueToStore, state = IlMachineState.popEvalStack currentThread state @@ -182,8 +122,84 @@ module NullaryIlOp = ) } | ManagedPointerSource.Heap managedHeapAddress -> failwith "todo" + | ManagedPointerSource.ArrayIndex _ -> failwith "todo" | EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo" + let internal ldElem + (targetCliTypeZero : CliType) + (index : EvalStackValue) + (arr : EvalStackValue) + (currentThread : ThreadId) + (state : IlMachineState) + : ExecutionResult + = + let index = + match index with + | EvalStackValue.NativeInt src -> + match src with + | NativeIntSource.FunctionPointer _ + | NativeIntSource.TypeHandlePtr _ + | NativeIntSource.ManagedPointer _ -> failwith "Refusing to treat a pointer as an array index" + | NativeIntSource.Verbatim i -> i |> int32 + | EvalStackValue.Int32 i -> i + | _ -> failwith $"Invalid index: {index}" + + let arrAddr = + match arr with + | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) + | EvalStackValue.ObjectRef addr -> addr + | EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE" + | _ -> failwith $"Invalid array: %O{arr}" + + let value = IlMachineState.getArrayValue arrAddr index state + + let state = + state + |> IlMachineState.pushToEvalStack value currentThread + |> IlMachineState.advanceProgramCounter currentThread + + ExecutionResult.Stepped (state, WhatWeDid.Executed) + + let internal stElem + (targetCliTypeZero : CliType) + (value : EvalStackValue) + (index : EvalStackValue) + (arr : EvalStackValue) + (currentThread : ThreadId) + (state : IlMachineState) + : ExecutionResult + = + let index = + match index with + | EvalStackValue.NativeInt src -> + match src with + | NativeIntSource.FunctionPointer _ + | NativeIntSource.TypeHandlePtr _ + | NativeIntSource.ManagedPointer _ -> failwith "Refusing to treat a pointer as an array index" + | NativeIntSource.Verbatim i -> i |> int32 + | EvalStackValue.Int32 i -> i + | _ -> failwith $"Invalid index: {index}" + + let arrAddr = + match arr with + | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) + | EvalStackValue.ObjectRef addr -> addr + | EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE" + | _ -> failwith $"Invalid array: %O{arr}" + // TODO: throw ArrayTypeMismatchException if incorrect types + + let arr = state.ManagedHeap.Arrays.[arrAddr] + + if index < 0 || index >= arr.Length then + failwith "TODO: throw IndexOutOfRangeException" + + let state = + state + |> IlMachineState.setArrayValue arrAddr (EvalStackValue.toCliTypeCoerced targetCliTypeZero value) index + |> IlMachineState.advanceProgramCounter currentThread + + ExecutionResult.Stepped (state, WhatWeDid.Executed) + let internal execute (loggerFactory : ILoggerFactory) (corelib : BaseClassTypes) @@ -346,38 +362,7 @@ module NullaryIlOp = let var2, state = state |> IlMachineState.popEvalStack currentThread let var1, state = state |> IlMachineState.popEvalStack currentThread - let comparisonResult = - // Table III.4 - match var1, var2 with - | EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> if var1 = var2 then 1 else 0 - | EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 -> failwith "TODO: int32 CEQ nativeint" - | EvalStackValue.Int32 _, _ -> failwith $"bad ceq: Int32 vs {var2}" - | EvalStackValue.Int64 var1, EvalStackValue.Int64 var2 -> if var1 = var2 then 1 else 0 - | EvalStackValue.Int64 _, _ -> failwith $"bad ceq: Int64 vs {var2}" - | EvalStackValue.Float var1, EvalStackValue.Float var2 -> failwith "TODO: float CEQ float" - | EvalStackValue.Float _, _ -> failwith $"bad ceq: Float vs {var2}" - | EvalStackValue.NativeInt var1, EvalStackValue.NativeInt var2 -> - match var1, var2 with - | NativeIntSource.FunctionPointer f1, NativeIntSource.FunctionPointer f2 -> - if f1 = f2 then - 1 - else - failwith $"TODO(CEQ): nativeint vs nativeint, {f1} vs {f2}" - | NativeIntSource.TypeHandlePtr f1, NativeIntSource.TypeHandlePtr f2 -> if f1 = f2 then 1 else 0 - | NativeIntSource.Verbatim f1, NativeIntSource.Verbatim f2 -> if f1 = f2 then 1 else 0 - | NativeIntSource.ManagedPointer f1, NativeIntSource.ManagedPointer f2 -> if f1 = f2 then 1 else 0 - | _, _ -> failwith $"TODO (CEQ): nativeint vs nativeint, {var1} vs {var2}" - | EvalStackValue.NativeInt var1, EvalStackValue.Int32 var2 -> failwith $"TODO (CEQ): nativeint vs int32" - | EvalStackValue.NativeInt var1, EvalStackValue.ManagedPointer var2 -> - failwith $"TODO (CEQ): nativeint vs managed pointer" - | EvalStackValue.NativeInt _, _ -> failwith $"bad ceq: NativeInt vs {var2}" - | EvalStackValue.ObjectRef var1, EvalStackValue.ObjectRef var2 -> if var1 = var2 then 1 else 0 - | EvalStackValue.ObjectRef _, _ -> failwith $"bad ceq: ObjectRef vs {var2}" - | EvalStackValue.ManagedPointer var1, EvalStackValue.ManagedPointer var2 -> if var1 = var2 then 1 else 0 - | EvalStackValue.ManagedPointer var1, EvalStackValue.NativeInt var2 -> - failwith $"TODO (CEQ): managed pointer vs nativeint" - | EvalStackValue.ManagedPointer _, _ -> failwith $"bad ceq: ManagedPointer vs {var2}" - | EvalStackValue.UserDefinedValueType _, _ -> failwith $"bad ceq: UserDefinedValueType vs {var2}" + let comparisonResult = if EvalStackValueComparisons.ceq var1 var2 then 1 else 0 state |> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 comparisonResult) currentThread @@ -390,41 +375,7 @@ module NullaryIlOp = let var2, state = state |> IlMachineState.popEvalStack currentThread let var1, state = state |> IlMachineState.popEvalStack currentThread - let comparisonResult = - match var1, var2 with - | EvalStackValue.Int64 var1, EvalStackValue.Int64 var2 -> if var1 < var2 then 1 else 0 - | EvalStackValue.Float var1, EvalStackValue.Float var2 -> - failwith "TODO: Clt float comparison unimplemented" - | EvalStackValue.ObjectRef var1, EvalStackValue.ObjectRef var2 -> - failwith $"Clt instruction invalid for comparing object refs, {var1} vs {var2}" - | EvalStackValue.ObjectRef var1, other -> failwith $"invalid comparison, ref %O{var1} vs %O{other}" - | other, EvalStackValue.ObjectRef var2 -> failwith $"invalid comparison, %O{other} vs ref %O{var2}" - | EvalStackValue.Float i, other -> failwith $"invalid comparison, float %f{i} vs %O{other}" - | other, EvalStackValue.Float i -> failwith $"invalid comparison, %O{other} vs float %f{i}" - | EvalStackValue.Int64 i, other -> failwith $"invalid comparison, int64 %i{i} vs %O{other}" - | other, EvalStackValue.Int64 i -> failwith $"invalid comparison, %O{other} vs int64 %i{i}" - | EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> if var1 < var2 then 1 else 0 - | EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 -> - failwith "TODO: Clt Int32 vs NativeInt comparison unimplemented" - | EvalStackValue.Int32 i, other -> failwith $"invalid comparison, int32 %i{i} vs %O{other}" - | EvalStackValue.NativeInt var1, EvalStackValue.Int32 var2 -> - failwith "TODO: Clt NativeInt vs Int32 comparison unimplemented" - | other, EvalStackValue.Int32 var2 -> failwith $"invalid comparison, {other} vs int32 {var2}" - | EvalStackValue.NativeInt var1, EvalStackValue.NativeInt var2 -> - if NativeIntSource.isLess var1 var2 then 1 else 0 - | EvalStackValue.NativeInt var1, other -> failwith $"invalid comparison, nativeint {var1} vs %O{other}" - | EvalStackValue.ManagedPointer managedPointerSource, NativeInt int64 -> - failwith "TODO: Clt ManagedPointer vs NativeInt comparison unimplemented" - | EvalStackValue.ManagedPointer managedPointerSource, ManagedPointer pointerSource -> - failwith "TODO: Clt ManagedPointer vs ManagedPointer comparison unimplemented" - | EvalStackValue.ManagedPointer managedPointerSource, UserDefinedValueType _ -> - failwith "TODO: Clt ManagedPointer vs UserDefinedValueType comparison unimplemented" - | EvalStackValue.UserDefinedValueType _, NativeInt int64 -> - failwith "TODO: Clt UserDefinedValueType vs NativeInt comparison unimplemented" - | EvalStackValue.UserDefinedValueType _, ManagedPointer managedPointerSource -> - failwith "TODO: Clt UserDefinedValueType vs ManagedPointer comparison unimplemented" - | EvalStackValue.UserDefinedValueType _, UserDefinedValueType _ -> - failwith "TODO: Clt UserDefinedValueType vs UserDefinedValueType comparison unimplemented" + let comparisonResult = if EvalStackValueComparisons.clt var1 var2 then 1 else 0 state |> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 comparisonResult) currentThread @@ -460,8 +411,9 @@ module NullaryIlOp = | Sub_ovf -> failwith "TODO: Sub_ovf unimplemented" | Sub_ovf_un -> failwith "TODO: Sub_ovf_un unimplemented" | Add -> - let result, state = - binaryArithmeticOperation ArithmeticOperation.add currentThread state + let val1, state = IlMachineState.popEvalStack currentThread state + let val2, state = IlMachineState.popEvalStack currentThread state + let result = BinaryArithmetic.execute ArithmeticOperation.add val1 val2 state |> IlMachineState.pushToEvalStack' result currentThread @@ -471,8 +423,9 @@ module NullaryIlOp = | Add_ovf -> failwith "TODO: Add_ovf unimplemented" | Add_ovf_un -> failwith "TODO: Add_ovf_un unimplemented" | Mul -> - let result, state = - binaryArithmeticOperation ArithmeticOperation.mul currentThread state + let val1, state = IlMachineState.popEvalStack currentThread state + let val2, state = IlMachineState.popEvalStack currentThread state + let result = BinaryArithmetic.execute ArithmeticOperation.mul val1 val2 state |> IlMachineState.pushToEvalStack' result currentThread @@ -798,6 +751,7 @@ module NullaryIlOp = | ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int whichVar] | ManagedPointerSource.Heap managedHeapAddress -> failwith "todo" + | ManagedPointerSource.ArrayIndex _ -> failwith "todo" | a -> failwith $"TODO: {a}" let state = @@ -807,7 +761,29 @@ module NullaryIlOp = |> IlMachineState.advanceProgramCounter currentThread (state, WhatWeDid.Executed) |> ExecutionResult.Stepped - | Stind_ref -> failwith "TODO: Stind_ref unimplemented" + | Stind_ref -> + let value, state = IlMachineState.popEvalStack currentThread state + let addr, state = IlMachineState.popEvalStack currentThread state + + let state = + match addr with + | EvalStackValue.ManagedPointer src -> + match src with + | ManagedPointerSource.Null -> failwith "TODO: throw NRE" + | 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.setArrayValue + arr + (EvalStackValue.toCliTypeCoerced (CliType.ObjectRef None) value) + index + | addr -> failwith $"TODO: {addr}" + + let state = state |> IlMachineState.advanceProgramCounter currentThread + + (state, WhatWeDid.Executed) |> ExecutionResult.Stepped | Ldelem_i -> failwith "TODO: Ldelem_i unimplemented" | Ldelem_i1 -> failwith "TODO: Ldelem_i1 unimplemented" | Ldelem_u1 -> failwith "TODO: Ldelem_u1 unimplemented" @@ -819,19 +795,54 @@ module NullaryIlOp = | Ldelem_u8 -> failwith "TODO: Ldelem_u8 unimplemented" | Ldelem_r4 -> failwith "TODO: Ldelem_r4 unimplemented" | Ldelem_r8 -> failwith "TODO: Ldelem_r8 unimplemented" - | Ldelem_ref -> failwith "TODO: Ldelem_ref unimplemented" - | Stelem_i -> failwith "TODO: Stelem_i unimplemented" - | Stelem_i1 -> failwith "TODO: Stelem_i1 unimplemented" + | Ldelem_ref -> + let index, state = IlMachineState.popEvalStack currentThread state + let arr, state = IlMachineState.popEvalStack currentThread state + + ldElem (CliType.ObjectRef None) index arr currentThread state + | Stelem_i -> + let value, state = IlMachineState.popEvalStack currentThread state + let index, state = IlMachineState.popEvalStack currentThread state + let arr, state = IlMachineState.popEvalStack currentThread state + + stElem + (CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.Verbatim 0L))) + value + index + arr + currentThread + state + | Stelem_i1 -> + let value, state = IlMachineState.popEvalStack currentThread state + let index, state = IlMachineState.popEvalStack currentThread state + let arr, state = IlMachineState.popEvalStack currentThread state + stElem (CliType.Numeric (CliNumericType.Int8 0y)) value index arr currentThread state | Stelem_u1 -> failwith "TODO: Stelem_u1 unimplemented" - | Stelem_i2 -> failwith "TODO: Stelem_i2 unimplemented" + | Stelem_i2 -> + let value, state = IlMachineState.popEvalStack currentThread state + let index, state = IlMachineState.popEvalStack currentThread state + let arr, state = IlMachineState.popEvalStack currentThread state + stElem (CliType.Numeric (CliNumericType.Int16 0s)) value index arr currentThread state | Stelem_u2 -> failwith "TODO: Stelem_u2 unimplemented" - | Stelem_i4 -> failwith "TODO: Stelem_i4 unimplemented" + | Stelem_i4 -> + let value, state = IlMachineState.popEvalStack currentThread state + let index, state = IlMachineState.popEvalStack currentThread state + let arr, state = IlMachineState.popEvalStack currentThread state + stElem (CliType.Numeric (CliNumericType.Int32 0)) value index arr currentThread state | Stelem_u4 -> failwith "TODO: Stelem_u4 unimplemented" - | Stelem_i8 -> failwith "TODO: Stelem_i8 unimplemented" + | Stelem_i8 -> + let value, state = IlMachineState.popEvalStack currentThread state + let index, state = IlMachineState.popEvalStack currentThread state + let arr, state = IlMachineState.popEvalStack currentThread state + stElem (CliType.Numeric (CliNumericType.Int64 0L)) value index arr currentThread state | Stelem_u8 -> failwith "TODO: Stelem_u8 unimplemented" | Stelem_r4 -> failwith "TODO: Stelem_r4 unimplemented" | Stelem_r8 -> failwith "TODO: Stelem_r8 unimplemented" - | Stelem_ref -> failwith "TODO: Stelem_ref unimplemented" + | Stelem_ref -> + let value, state = IlMachineState.popEvalStack currentThread state + let index, state = IlMachineState.popEvalStack currentThread state + let arr, state = IlMachineState.popEvalStack currentThread state + stElem (CliType.ObjectRef None) value index arr currentThread state | Cpblk -> failwith "TODO: Cpblk unimplemented" | Initblk -> failwith "TODO: Initblk unimplemented" | Conv_ovf_u1 -> failwith "TODO: Conv_ovf_u1 unimplemented" diff --git a/WoofWare.PawPrint/UnaryMetadataIlOp.fs b/WoofWare.PawPrint/UnaryMetadataIlOp.fs index b93b339..5e07426 100644 --- a/WoofWare.PawPrint/UnaryMetadataIlOp.fs +++ b/WoofWare.PawPrint/UnaryMetadataIlOp.fs @@ -340,7 +340,38 @@ module internal UnaryMetadataIlOp = state, WhatWeDid.Executed | Box -> failwith "TODO: Box unimplemented" - | Ldelema -> failwith "TODO: Ldelema unimplemented" + | Ldelema -> + let index, state = IlMachineState.popEvalStack thread state + let arr, state = IlMachineState.popEvalStack thread state + + let index = + match index with + | EvalStackValue.Int32 i -> i + | _ -> failwith $"TODO: {index}" + + let arrAddr = + match arr with + | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) + | EvalStackValue.ObjectRef addr -> addr + | EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE" + | _ -> failwith $"Invalid array: %O{arr}" + + // TODO: throw ArrayTypeMismatchException if incorrect types + + let arr = state.ManagedHeap.Arrays.[arrAddr] + + if index < 0 || index >= arr.Length then + failwith "TODO: throw IndexOutOfRangeException" + + let result = + ManagedPointerSource.ArrayIndex (arrAddr, index) + |> EvalStackValue.ManagedPointer + + let state = + IlMachineState.pushToEvalStack' result thread state + |> IlMachineState.advanceProgramCounter thread + + state, WhatWeDid.Executed | Isinst -> let actualObj, state = IlMachineState.popEvalStack thread state @@ -464,6 +495,8 @@ module internal UnaryMetadataIlOp = state |> IlMachineState.setLocalVariable sourceThread methodFrame whichVar valueToStore | ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> failwith "todo" + | ManagedPointerSource.ArrayIndex (arr, index) -> + state |> IlMachineState.setArrayValue arr valueToStore index | ManagedPointerSource.Heap addr -> match state.ManagedHeap.NonArrayObjects.TryGetValue addr with | false, _ -> failwith $"todo: array {addr}" @@ -641,6 +674,9 @@ module internal UnaryMetadataIlOp = match state.ManagedHeap.NonArrayObjects.TryGetValue managedHeapAddress with | false, _ -> failwith $"todo: array {managedHeapAddress}" | true, v -> IlMachineState.pushToEvalStack v.Fields.[field.Name] thread state + | ManagedPointerSource.ArrayIndex (arr, index) -> + let currentValue = state |> IlMachineState.getArrayValue arr index + IlMachineState.pushToEvalStack currentValue thread state | ManagedPointerSource.Null -> failwith "TODO: raise NullReferenceException" | EvalStackValue.ObjectRef managedHeapAddress -> failwith $"todo: {managedHeapAddress}" | EvalStackValue.UserDefinedValueType _ as udvt -> IlMachineState.pushToEvalStack' udvt thread state diff --git a/WoofWare.PawPrint/WoofWare.PawPrint.fsproj b/WoofWare.PawPrint/WoofWare.PawPrint.fsproj index 3e8a007..948d72c 100644 --- a/WoofWare.PawPrint/WoofWare.PawPrint.fsproj +++ b/WoofWare.PawPrint/WoofWare.PawPrint.fsproj @@ -16,6 +16,8 @@ + +