Merge branch 'main' into castclass

This commit is contained in:
Smaug123
2025-06-27 15:04:28 +01:00
15 changed files with 451 additions and 141 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

@@ -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
}
]
[<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

@@ -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
[<RequireQualifiedAccess>]
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"
}
[<RequireQualifiedAccess>]
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}"

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

@@ -0,0 +1,71 @@
namespace WoofWare.PawPrint
[<RequireQualifiedAccess>]
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}"

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

@@ -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
[<RequireQualifiedAccess>]
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"
}
[<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module NullaryIlOp =
@@ -72,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
@@ -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<DumpedAssembly>)
@@ -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<uint16> 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"

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

View File

@@ -16,6 +16,8 @@
<Compile Include="TypeInitialisation.fs" />
<Compile Include="Exceptions.fs" />
<Compile Include="EvalStack.fs" />
<Compile Include="EvalStackValueComparisons.fs" />
<Compile Include="BinaryArithmetic.fs" />
<Compile Include="MethodState.fs" />
<Compile Include="ThreadState.fs" />
<Compile Include="IlMachineState.fs" />