Implement Ldelem etc (#72)

This commit is contained in:
Patrick Stevens
2025-06-27 14:28:38 +01:00
committed by GitHub
parent 4352bfa218
commit 7c636b61a7
12 changed files with 320 additions and 9 deletions

View File

@@ -4,6 +4,8 @@
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<WarningsAsErrors>false</WarningsAsErrors>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
</Project>

View File

@@ -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.

View File

@@ -185,6 +185,12 @@ module TestPureCases =
|> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i))
|> Some
}
{
FileName = "Ldelema.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
]
[<TestCaseSource(nameof cases)>]

View File

@@ -21,6 +21,7 @@
<ItemGroup>
<EmbeddedResource Include="sourcesPure\BasicLock.cs" />
<EmbeddedResource Include="sourcesPure\NoOp.cs" />
<EmbeddedResource Include="sourcesPure\Ldelema.cs" />
<EmbeddedResource Include="sourcesPure\ExceptionWithNoOpCatch.cs" />
<EmbeddedResource Include="sourcesPure\ExceptionWithNoOpFinally.cs" />
<EmbeddedResource Include="sourcesPure\TryCatchWithThrowInBody.cs" />

View File

@@ -0,0 +1,66 @@
using System;
/// <summary>
/// A simple value type used for testing ldelema.
/// </summary>
public struct TestStruct
{
public int Value;
}
public class Program
{
/// <summary>
/// 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.
/// </summary>
/// <param name="s">A reference to the TestStruct to modify.</param>
/// <param name="newValue">The new value to assign.</param>
public static void ModifyStruct(ref TestStruct s, int newValue)
{
s.Value = newValue;
}
/// <summary>
/// Modifies a string reference.
/// </summary>
/// <param name="s">A reference to a string variable.</param>
/// <param name="newValue">The new string to assign.</param>
public static void ModifyStringRef(ref string s, string newValue)
{
s = newValue;
}
/// <summary>
/// Main entry point for the ldelema test.
/// </summary>
/// <returns>0 if all tests pass, otherwise a non-zero error code.</returns>
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
}
}

View File

@@ -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 =
$"<variable %i{var} in method frame %i{method} of thread %O{source}>"
| ManagedPointerSource.Argument (source, method, var) ->
$"<argument %i{var} in method frame %i{method} of thread %O{source}>"
| ManagedPointerSource.ArrayIndex (arr, index) -> $"<index %i{index} of array %O{arr}>"
[<RequireQualifiedAccess>]
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 =

View File

@@ -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

View File

@@ -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

View File

@@ -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 =

View File

@@ -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)
}

View File

@@ -46,6 +46,7 @@ module NullaryIlOp =
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables.[int<uint16> 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
@@ -121,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<DumpedAssembly>)
@@ -674,6 +751,7 @@ module NullaryIlOp =
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
| ManagedPointerSource.ArrayIndex _ -> failwith "todo"
| a -> failwith $"TODO: {a}"
let state =
@@ -683,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"
@@ -695,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"

View File

@@ -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