mirror of
https://github.com/Smaug123/WoofWare.PawPrint
synced 2025-10-07 06:58:39 +00:00
Centralise the Ldind logic (#61)
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -43,6 +43,7 @@ type NullaryIlOp =
|
||||
| LdcI4_7
|
||||
/// Push the int32 value 8 to the eval stack.
|
||||
| LdcI4_8
|
||||
/// Push the int32 value -1 to the eval stack.
|
||||
| LdcI4_m1
|
||||
/// Push a null object reference onto the stack.
|
||||
| LdNull
|
||||
@@ -388,6 +389,7 @@ type UnaryConstIlOp =
|
||||
| Bgt_un of int32
|
||||
| Ble_un of int32
|
||||
| Blt_un of int32
|
||||
/// Loads the local variable at a specific index onto the evaluation stack.
|
||||
| Ldloc_s of uint8
|
||||
| Ldloca_s of uint8
|
||||
/// Load the address of an argument onto the stack.
|
||||
|
@@ -23,6 +23,7 @@ module LoggerFactory =
|
||||
let makeTest () : (unit -> LogLine list) * ILoggerFactory =
|
||||
// Shared sink for all loggers created by the factory.
|
||||
let sink = ResizeArray ()
|
||||
let isEnabled (logLevel : LogLevel) : bool = logLevel >= LogLevel.Information
|
||||
|
||||
let createLogger (category : string) : ILogger =
|
||||
{ new ILogger with
|
||||
@@ -31,9 +32,13 @@ module LoggerFactory =
|
||||
member _.Dispose () = ()
|
||||
}
|
||||
|
||||
member _.IsEnabled _logLevel = true
|
||||
member _.IsEnabled l = isEnabled l
|
||||
|
||||
member _.Log (logLevel, eventId, state, ex, formatter) =
|
||||
if not (isEnabled logLevel) then
|
||||
()
|
||||
else
|
||||
|
||||
let message =
|
||||
try
|
||||
formatter.Invoke (state, ex)
|
||||
|
@@ -31,7 +31,8 @@ module Roslyn =
|
||||
Directory.GetFiles (runtimeDir, "*.dll")
|
||||
|> Array.map (fun path -> MetadataReference.CreateFromFile path :> MetadataReference)
|
||||
|
||||
let compilationOptions = CSharpCompilationOptions OutputKind.ConsoleApplication
|
||||
let compilationOptions =
|
||||
CSharpCompilationOptions(OutputKind.ConsoleApplication).WithAllowUnsafe (true)
|
||||
|
||||
let compilation =
|
||||
CSharpCompilation.Create (
|
||||
|
@@ -73,6 +73,18 @@ module TestCases =
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 1) ]
|
||||
}
|
||||
{
|
||||
FileName = "Ldind.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain =
|
||||
[
|
||||
// `failures`
|
||||
CliType.Numeric (CliNumericType.Int32 0)
|
||||
// Return value
|
||||
CliType.Numeric (CliNumericType.Int32 0)
|
||||
]
|
||||
}
|
||||
{
|
||||
FileName = "CustomDelegate.cs"
|
||||
ExpectedReturnCode = 8
|
||||
|
@@ -29,6 +29,7 @@
|
||||
<EmbeddedResource Include="sources\ResizeArray.cs" />
|
||||
<EmbeddedResource Include="sources\ArgumentOrdering.cs" />
|
||||
<EmbeddedResource Include="sources\CustomDelegate.cs" />
|
||||
<EmbeddedResource Include="sources\Ldind.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
361
WoofWare.PawPrint.Test/sources/Ldind.cs
Normal file
361
WoofWare.PawPrint.Test/sources/Ldind.cs
Normal file
@@ -0,0 +1,361 @@
|
||||
using System;
|
||||
|
||||
unsafe class LdindTest
|
||||
{
|
||||
static int Main(string[] args)
|
||||
{
|
||||
var failures = 0;
|
||||
// Test Ldind.i1 (signed byte)
|
||||
failures += TestLdindI1();
|
||||
|
||||
// Test Ldind.u1 (unsigned byte)
|
||||
failures += TestLdindU1();
|
||||
|
||||
// Test Ldind.i2 (signed short)
|
||||
failures += TestLdindI2();
|
||||
|
||||
// Test Ldind.u2 (unsigned short)
|
||||
failures += TestLdindU2();
|
||||
|
||||
// Test Ldind.i4 (signed int)
|
||||
failures += TestLdindI4();
|
||||
|
||||
// Test Ldind.u4 (unsigned int)
|
||||
failures += TestLdindU4();
|
||||
|
||||
// Test Ldind.i8 (signed long)
|
||||
failures += TestLdindI8();
|
||||
|
||||
// Test Ldind.i8 via u8 (there's no Ldind.u8)
|
||||
failures += TestLdindI8ViaU8();
|
||||
|
||||
// Test Ldind.r4 (float)
|
||||
failures += TestLdindR4();
|
||||
|
||||
// Test Ldind.r8 (double)
|
||||
failures += TestLdindR8();
|
||||
|
||||
// Test truncation behavior
|
||||
failures += TestTruncation();
|
||||
|
||||
// Test with managed pointers (ref)
|
||||
// failures += TestManagedPointers();
|
||||
|
||||
// Test Ldind.i (native int)
|
||||
// failures += TestLdindI();
|
||||
|
||||
return failures;
|
||||
}
|
||||
|
||||
static int TestLdindI1()
|
||||
{
|
||||
sbyte value = -128;
|
||||
sbyte* ptr = &value;
|
||||
sbyte loaded = *ptr; // This generates ldind.i1
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
value = 127;
|
||||
loaded = *ptr;
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int TestLdindU1()
|
||||
{
|
||||
byte value = 255;
|
||||
byte* ptr = &value;
|
||||
byte loaded = *ptr; // This generates ldind.u1
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
value = 0;
|
||||
loaded = *ptr;
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int TestLdindI2()
|
||||
{
|
||||
short value = -32768;
|
||||
short* ptr = &value;
|
||||
short loaded = *ptr; // This generates ldind.i2
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
value = 32767;
|
||||
loaded = *ptr;
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int TestLdindU2()
|
||||
{
|
||||
ushort value = 65535;
|
||||
ushort* ptr = &value;
|
||||
ushort loaded = *ptr; // This generates ldind.u2
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
value = 0;
|
||||
loaded = *ptr;
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int TestLdindI4()
|
||||
{
|
||||
int value = int.MinValue;
|
||||
int* ptr = &value;
|
||||
int loaded = *ptr; // This generates ldind.i4
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
value = int.MaxValue;
|
||||
loaded = *ptr;
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int TestLdindU4()
|
||||
{
|
||||
uint value = uint.MaxValue;
|
||||
uint* ptr = &value;
|
||||
uint loaded = *ptr; // This generates ldind.u4
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
value = 0;
|
||||
loaded = *ptr;
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int TestLdindI8()
|
||||
{
|
||||
long value = long.MinValue;
|
||||
long* ptr = &value;
|
||||
long loaded = *ptr; // This generates ldind.i8
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
value = long.MaxValue;
|
||||
loaded = *ptr;
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int TestLdindI8ViaU8()
|
||||
{
|
||||
ulong value = ulong.MaxValue;
|
||||
ulong* ptr = &value;
|
||||
ulong loaded = *ptr; // This generates ldind.i8 again, because there's no ldind.u8
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
value = 0;
|
||||
loaded = *ptr;
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int TestLdindR4()
|
||||
{
|
||||
float value = float.MinValue;
|
||||
float* ptr = &value;
|
||||
float loaded = *ptr; // This generates ldind.r4
|
||||
if (BitConverter.SingleToInt32Bits(value) != BitConverter.SingleToInt32Bits(loaded))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
value = float.MaxValue;
|
||||
loaded = *ptr;
|
||||
if (BitConverter.SingleToInt32Bits(value) != BitConverter.SingleToInt32Bits(loaded))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
value = float.NaN;
|
||||
loaded = *ptr;
|
||||
if (BitConverter.SingleToInt32Bits(value) != BitConverter.SingleToInt32Bits(loaded))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int TestLdindR8()
|
||||
{
|
||||
double value = double.MinValue;
|
||||
double* ptr = &value;
|
||||
double loaded = *ptr; // This generates ldind.r8
|
||||
if (BitConverter.DoubleToInt64Bits(value) != BitConverter.DoubleToInt64Bits(loaded))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
value = double.MaxValue;
|
||||
loaded = *ptr;
|
||||
if (BitConverter.DoubleToInt64Bits(value) != BitConverter.DoubleToInt64Bits(loaded))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
value = double.NaN;
|
||||
loaded = *ptr;
|
||||
if (BitConverter.DoubleToInt64Bits(value) != BitConverter.DoubleToInt64Bits(loaded))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int TestLdindI()
|
||||
{
|
||||
IntPtr value = new IntPtr(42);
|
||||
IntPtr* ptr = &value;
|
||||
IntPtr loaded = *ptr; // This generates ldind.i
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
value = IntPtr.Zero;
|
||||
loaded = *ptr;
|
||||
if (value != loaded)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int TestTruncation()
|
||||
{
|
||||
// Store a larger value and load as smaller type
|
||||
int largeValue = 0x1234ABCD;
|
||||
void* ptr = &largeValue;
|
||||
|
||||
byte byteValue = *(byte*)ptr; // Should truncate to 0xCD
|
||||
if (byteValue != 0xCD)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
short shortValue = *(short*)ptr; // Should truncate to 0xABCD
|
||||
if (shortValue != -21555)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Test sign extension
|
||||
sbyte signedByte = *(sbyte*)ptr; // 0xCD as signed byte is -51
|
||||
if (signedByte != -51)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int TestManagedPointers()
|
||||
{
|
||||
int value = 42;
|
||||
ref int refValue = ref value;
|
||||
var expected42 = TestRefParameter(ref refValue);
|
||||
if (expected42 != 42)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (refValue != 84)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Test with array element
|
||||
int[] array = { 10, 20, 30 };
|
||||
ref int element = ref array[1];
|
||||
if (element != 20)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Test with local variable
|
||||
int local = 100;
|
||||
var expected100 = TestRefLocal(ref local);
|
||||
if (expected100 != 100)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int TestRefParameter(ref int param)
|
||||
{
|
||||
var result = 0;
|
||||
// This will use ldind instructions when accessing param
|
||||
result += param;
|
||||
param = 84;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int TestRefLocal(ref int local)
|
||||
{
|
||||
// Create a ref local
|
||||
ref int refLocal = ref local;
|
||||
return refLocal;
|
||||
}
|
||||
}
|
@@ -66,7 +66,7 @@ module AbstractMachine =
|
||||
let methodPtr =
|
||||
match delegateToRun.Fields.["_methodPtr"] with
|
||||
| CliType.Numeric (CliNumericType.ProvenanceTrackedNativeInt64 mi) -> mi
|
||||
| _ -> failwith "unexpectedly not a method pointer in delegate invocation"
|
||||
| d -> failwith $"unexpectedly not a method pointer in delegate invocation: {d}"
|
||||
|
||||
let typeGenerics =
|
||||
instruction.ExecutingMethod.DeclaringType.Generics |> ImmutableArray.CreateRange
|
||||
@@ -86,6 +86,9 @@ module AbstractMachine =
|
||||
(state, instruction.Arguments)
|
||||
||> Seq.fold (fun state arg -> IlMachineState.pushToEvalStack arg thread state)
|
||||
|
||||
let state, _ =
|
||||
state.WithThreadSwitchedToAssembly methodPtr.DeclaringType.Assembly thread
|
||||
|
||||
// Don't advance the program counter again on return; that was already done by the Callvirt that
|
||||
// caused this delegate to be invoked.
|
||||
let state, result =
|
||||
@@ -160,7 +163,9 @@ module AbstractMachine =
|
||||
| Some instructions ->
|
||||
|
||||
match instructions.Locations.TryGetValue instruction.IlOpIndex with
|
||||
| false, _ -> failwith "Wanted to execute a nonexistent instruction"
|
||||
| false, _ ->
|
||||
failwith
|
||||
$"Wanted to execute a nonexistent instruction in {instruction.ExecutingMethod.DeclaringType.Name}.{instruction.ExecutingMethod.Name}"
|
||||
| true, executingInstruction ->
|
||||
|
||||
let executingInType =
|
||||
@@ -172,8 +177,9 @@ module AbstractMachine =
|
||||
| false, _ -> "<unrecognised type>"
|
||||
|
||||
logger.LogInformation (
|
||||
"Executing one step (index {ExecutingIlOpIndex} in method {ExecutingMethodType}.{ExecutingMethodName}): {ExecutingIlOp}",
|
||||
"Executing one step (index {ExecutingIlOpIndex}, max {MaxIlOpIndex}, in method {ExecutingMethodType}.{ExecutingMethodName}): {ExecutingIlOp}",
|
||||
instruction.IlOpIndex,
|
||||
(Map.maxKeyValue instruction.ExecutingMethod.Instructions.Value.Locations |> fst),
|
||||
executingInType,
|
||||
instruction.ExecutingMethod.Name,
|
||||
executingInstruction
|
||||
|
@@ -75,9 +75,11 @@ type CliValueType =
|
||||
type CliRuntimePointerSource =
|
||||
| LocalVariable of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
|
||||
| Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
|
||||
| Heap of ManagedHeapAddress
|
||||
| Null
|
||||
|
||||
type CliRuntimePointer =
|
||||
| Unmanaged of unit
|
||||
| Unmanaged of int64
|
||||
| Managed of CliRuntimePointerSource
|
||||
|
||||
/// This is the kind of type that can be stored in arguments, local variables, statics, array elements, fields.
|
||||
@@ -120,15 +122,19 @@ module CliType =
|
||||
| PrimitiveType.Int16 -> CliType.Numeric (CliNumericType.Int16 0s)
|
||||
| PrimitiveType.UInt16 -> CliType.Numeric (CliNumericType.UInt16 0us)
|
||||
| PrimitiveType.Int32 -> CliType.Numeric (CliNumericType.Int32 0)
|
||||
| PrimitiveType.UInt32 -> failwith "todo"
|
||||
| PrimitiveType.UInt32 ->
|
||||
// uint32 doesn't exist; the spec has them stored on the stack as if signed, with two's complement wraparound
|
||||
CliType.Numeric (CliNumericType.Int32 0)
|
||||
| PrimitiveType.Int64 -> CliType.Numeric (CliNumericType.Int64 0L)
|
||||
| PrimitiveType.UInt64 -> failwith "todo"
|
||||
| PrimitiveType.UInt64 ->
|
||||
// uint64 doesn't exist; the spec has them stored on the stack as if signed, with two's complement wraparound
|
||||
CliType.Numeric (CliNumericType.Int64 0L)
|
||||
| PrimitiveType.Single -> CliType.Numeric (CliNumericType.Float32 0.0f)
|
||||
| PrimitiveType.Double -> CliType.Numeric (CliNumericType.Float64 0.0)
|
||||
| PrimitiveType.String -> CliType.ObjectRef None
|
||||
| PrimitiveType.TypedReference -> failwith "todo"
|
||||
| PrimitiveType.IntPtr -> CliType.Numeric (CliNumericType.Int64 0L)
|
||||
| PrimitiveType.UIntPtr -> CliType.Numeric (CliNumericType.Int64 0L)
|
||||
| PrimitiveType.IntPtr -> CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
|
||||
| PrimitiveType.UIntPtr -> CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
|
||||
| PrimitiveType.Object -> CliType.ObjectRef None
|
||||
|
||||
let rec zeroOf
|
||||
@@ -144,7 +150,9 @@ module CliType =
|
||||
| TypeDefn.PrimitiveType primitiveType -> CliTypeResolutionResult.Resolved (zeroOfPrimitive primitiveType)
|
||||
| TypeDefn.Array _ -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
|
||||
| TypeDefn.Pinned typeDefn -> failwith "todo"
|
||||
| TypeDefn.Pointer _ -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
|
||||
| TypeDefn.Pointer _ ->
|
||||
CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
|
||||
|> CliTypeResolutionResult.Resolved
|
||||
| TypeDefn.Byref _ -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
|
||||
| TypeDefn.OneDimensionalArrayLowerBoundZero _ -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
|
||||
| TypeDefn.Modified (original, afterMod, modificationRequired) -> failwith "todo"
|
||||
|
@@ -1,7 +1,5 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open Microsoft.FSharp.Core
|
||||
|
||||
type ManagedPointerSource =
|
||||
| LocalVariable of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
|
||||
| Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
|
||||
@@ -20,12 +18,14 @@ type ManagedPointerSource =
|
||||
[<RequireQualifiedAccess>]
|
||||
type NativeIntSource =
|
||||
| Verbatim of int64
|
||||
| ManagedPointer of ManagedPointerSource
|
||||
| FunctionPointer of MethodInfo<FakeUnit, GenericParameter>
|
||||
|
||||
override this.ToString () : string =
|
||||
match this with
|
||||
| NativeIntSource.Verbatim int64 -> $"%i{int64}"
|
||||
| NativeIntSource.FunctionPointer (methodDefinition) ->
|
||||
| NativeIntSource.ManagedPointer ptr -> $"<managed pointer {ptr}>"
|
||||
| NativeIntSource.FunctionPointer methodDefinition ->
|
||||
$"<pointer to {methodDefinition.Name} in {methodDefinition.DeclaringType.Assembly.Name}>"
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -34,11 +34,16 @@ module NativeIntSource =
|
||||
match n with
|
||||
| NativeIntSource.Verbatim i -> i = 0L
|
||||
| NativeIntSource.FunctionPointer _ -> failwith "TODO"
|
||||
| NativeIntSource.ManagedPointer src ->
|
||||
match src with
|
||||
| ManagedPointerSource.Null -> true
|
||||
| _ -> false
|
||||
|
||||
let isNonnegative (n : NativeIntSource) : bool =
|
||||
match n with
|
||||
| NativeIntSource.Verbatim i -> i >= 0L
|
||||
| NativeIntSource.FunctionPointer _ -> failwith "TODO"
|
||||
| NativeIntSource.ManagedPointer _ -> true
|
||||
|
||||
/// True if a < b.
|
||||
let isLess (a : NativeIntSource) (b : NativeIntSource) : bool =
|
||||
@@ -47,7 +52,9 @@ module NativeIntSource =
|
||||
| _, _ -> failwith "TODO"
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type UnsignedNativeIntSource = | Verbatim of uint64
|
||||
type UnsignedNativeIntSource =
|
||||
| Verbatim of uint64
|
||||
| FromManagedPointer of ManagedPointerSource
|
||||
|
||||
/// See I.12.3.2.1 for definition
|
||||
type EvalStackValue =
|
||||
@@ -97,12 +104,21 @@ module EvalStackValue =
|
||||
uint64 i |> UnsignedNativeIntSource.Verbatim |> Some
|
||||
else
|
||||
failwith "todo"
|
||||
| NativeIntSource.ManagedPointer _ -> failwith "TODO"
|
||||
| NativeIntSource.FunctionPointer _ -> failwith "TODO"
|
||||
| EvalStackValue.Float f -> failwith "todo"
|
||||
| EvalStackValue.ManagedPointer managedPointerSource -> failwith "todo"
|
||||
| EvalStackValue.ManagedPointer managedPointerSource ->
|
||||
UnsignedNativeIntSource.FromManagedPointer managedPointerSource |> Some
|
||||
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
|
||||
| EvalStackValue.UserDefinedValueType _ -> failwith "todo"
|
||||
|
||||
/// The conversion performed by Conv_i.
|
||||
let toNativeInt (value : EvalStackValue) : NativeIntSource option =
|
||||
match value with
|
||||
| EvalStackValue.Int64 i -> Some (NativeIntSource.Verbatim i)
|
||||
| EvalStackValue.Int32 i -> Some (NativeIntSource.Verbatim (int64<int> i))
|
||||
| value -> failwith $"{value}"
|
||||
|
||||
let convToInt32 (value : EvalStackValue) : int32 option =
|
||||
match value with
|
||||
| EvalStackValue.Int32 i -> Some i
|
||||
@@ -150,6 +166,7 @@ module EvalStackValue =
|
||||
| EvalStackValue.NativeInt src ->
|
||||
match src with
|
||||
| NativeIntSource.Verbatim i -> CliType.Numeric (CliNumericType.Int64 i)
|
||||
| NativeIntSource.ManagedPointer ptr -> failwith "TODO"
|
||||
| NativeIntSource.FunctionPointer f ->
|
||||
CliType.Numeric (CliNumericType.ProvenanceTrackedNativeInt64 f)
|
||||
| i -> failwith $"TODO: %O{i}"
|
||||
@@ -159,14 +176,26 @@ module EvalStackValue =
|
||||
match popped with
|
||||
| EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.Int8 (i % 256 |> int8))
|
||||
| i -> failwith $"TODO: %O{i}"
|
||||
| CliNumericType.Int16 s -> failwith "todo"
|
||||
| CliNumericType.UInt8 b -> failwith "todo"
|
||||
| CliNumericType.UInt16 s -> failwith "todo"
|
||||
| CliNumericType.Float32 f -> failwith "todo"
|
||||
| CliNumericType.Int16 _ ->
|
||||
match popped with
|
||||
| EvalStackValue.Int32 popped -> CliType.Numeric (CliNumericType.Int16 (popped % 65536 |> int16<int>))
|
||||
| _ -> failwith $"TODO: {popped}"
|
||||
| CliNumericType.UInt8 _ ->
|
||||
match popped with
|
||||
| EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.UInt8 (i % 256 |> uint8))
|
||||
| i -> failwith $"todo: {i} to uint8"
|
||||
| CliNumericType.UInt16 _ ->
|
||||
match popped with
|
||||
| EvalStackValue.Int32 popped -> CliType.Numeric (CliNumericType.UInt16 (uint16<int32> popped))
|
||||
| i -> failwith $"todo: {i} to uint16"
|
||||
| CliNumericType.Float32 _ ->
|
||||
match popped with
|
||||
| EvalStackValue.Float f -> CliType.Numeric (CliNumericType.Float32 (float32<float> f))
|
||||
| i -> failwith $"todo: {i} to float32"
|
||||
| CliNumericType.Float64 _ ->
|
||||
match popped with
|
||||
| EvalStackValue.Float f -> CliType.Numeric (CliNumericType.Float64 f)
|
||||
| _ -> failwith "todo"
|
||||
| _ -> failwith $"todo: {popped} to float64"
|
||||
| CliType.ObjectRef _ ->
|
||||
match popped with
|
||||
| EvalStackValue.ManagedPointer ptrSource ->
|
||||
@@ -186,6 +215,11 @@ module EvalStackValue =
|
||||
| NativeIntSource.Verbatim 0L -> CliType.ObjectRef None
|
||||
| NativeIntSource.Verbatim i -> failwith $"refusing to interpret verbatim native int {i} as a pointer"
|
||||
| NativeIntSource.FunctionPointer _ -> failwith "TODO"
|
||||
| NativeIntSource.ManagedPointer ptr ->
|
||||
match ptr with
|
||||
| ManagedPointerSource.Null -> CliType.ObjectRef None
|
||||
| ManagedPointerSource.Heap s -> CliType.ObjectRef (Some s)
|
||||
| _ -> failwith "TODO"
|
||||
| EvalStackValue.UserDefinedValueType fields ->
|
||||
match fields with
|
||||
| [ esv ] -> toCliTypeCoerced target esv
|
||||
@@ -213,6 +247,22 @@ module EvalStackValue =
|
||||
CliRuntimePointerSource.Argument (sourceThread, methodFrame, var)
|
||||
|> CliRuntimePointer.Managed
|
||||
|> CliType.RuntimePointer
|
||||
| EvalStackValue.NativeInt intSrc ->
|
||||
match intSrc with
|
||||
| NativeIntSource.Verbatim i -> CliType.RuntimePointer (CliRuntimePointer.Unmanaged i)
|
||||
| NativeIntSource.ManagedPointer src ->
|
||||
match src with
|
||||
| ManagedPointerSource.Heap src ->
|
||||
CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Heap src))
|
||||
| ManagedPointerSource.Null -> failwith "TODO"
|
||||
| ManagedPointerSource.LocalVariable (a, b, c) ->
|
||||
CliType.RuntimePointer (
|
||||
CliRuntimePointer.Managed (CliRuntimePointerSource.LocalVariable (a, b, c))
|
||||
)
|
||||
| ManagedPointerSource.Argument (a, b, c) ->
|
||||
CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Argument (a, b, c)))
|
||||
| NativeIntSource.FunctionPointer methodInfo ->
|
||||
CliType.Numeric (CliNumericType.ProvenanceTrackedNativeInt64 methodInfo)
|
||||
| _ -> failwith $"TODO: %O{popped}"
|
||||
| CliType.Char _ ->
|
||||
match popped with
|
||||
@@ -231,7 +281,7 @@ module EvalStackValue =
|
||||
| popped ->
|
||||
match fields with
|
||||
| [ target ] -> toCliTypeCoerced target popped
|
||||
| _ -> failwith "TODO"
|
||||
| _ -> failwith $"TODO: {popped} into value type {target}"
|
||||
|
||||
let rec ofCliType (v : CliType) : EvalStackValue =
|
||||
match v with
|
||||
@@ -242,11 +292,11 @@ module EvalStackValue =
|
||||
| CliNumericType.NativeInt i -> failwith "TODO"
|
||||
// Sign-extend types int8 and int16
|
||||
// Zero-extend unsigned int8/unsigned int16
|
||||
| CliNumericType.Int8 b -> int32 b |> EvalStackValue.Int32
|
||||
| CliNumericType.UInt8 b -> int32 b |> EvalStackValue.Int32
|
||||
| CliNumericType.Int16 s -> int32 s |> EvalStackValue.Int32
|
||||
| CliNumericType.UInt16 s -> int32 s |> EvalStackValue.Int32
|
||||
| CliNumericType.Float32 f -> failwith "todo"
|
||||
| CliNumericType.Int8 b -> int32<int8> b |> EvalStackValue.Int32
|
||||
| CliNumericType.UInt8 b -> int32<uint8> b |> EvalStackValue.Int32
|
||||
| CliNumericType.Int16 s -> int32<int16> s |> EvalStackValue.Int32
|
||||
| CliNumericType.UInt16 s -> int32<uint16> s |> EvalStackValue.Int32
|
||||
| CliNumericType.Float32 f -> EvalStackValue.Float (float<float32> f)
|
||||
| CliNumericType.Float64 f -> EvalStackValue.Float f
|
||||
| CliNumericType.NativeFloat f -> EvalStackValue.Float f
|
||||
| CliNumericType.ProvenanceTrackedNativeInt64 f ->
|
||||
@@ -260,7 +310,7 @@ module EvalStackValue =
|
||||
| CliType.Char (high, low) -> int32 high * 256 + int32 low |> EvalStackValue.Int32
|
||||
| CliType.RuntimePointer ptr ->
|
||||
match ptr with
|
||||
| CliRuntimePointer.Unmanaged () -> failwith "todo: unmanaged"
|
||||
| CliRuntimePointer.Unmanaged _ -> failwith "todo: unmanaged"
|
||||
| CliRuntimePointer.Managed ptr ->
|
||||
match ptr with
|
||||
| CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, var) ->
|
||||
@@ -269,6 +319,8 @@ module EvalStackValue =
|
||||
| CliRuntimePointerSource.Argument (sourceThread, methodFrame, var) ->
|
||||
ManagedPointerSource.Argument (sourceThread, methodFrame, var)
|
||||
|> EvalStackValue.ManagedPointer
|
||||
| CliRuntimePointerSource.Heap addr -> EvalStackValue.ObjectRef addr
|
||||
| CliRuntimePointerSource.Null -> failwith "TODO"
|
||||
| CliType.ValueType fields -> fields |> List.map ofCliType |> EvalStackValue.UserDefinedValueType
|
||||
|
||||
type EvalStack =
|
||||
|
@@ -1,5 +1,6 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System
|
||||
open System.Collections.Immutable
|
||||
open System.IO
|
||||
open System.Reflection
|
||||
@@ -401,6 +402,134 @@ module IlMachineState =
|
||||
|
||||
cliTypeZeroOf loggerFactory corelib assy ty typeGenerics methodGenerics state
|
||||
|
||||
let pushToEvalStack' (o : EvalStackValue) (thread : ThreadId) (state : IlMachineState) =
|
||||
let activeThreadState = state.ThreadState.[thread]
|
||||
|
||||
let newThreadState =
|
||||
activeThreadState
|
||||
|> ThreadState.pushToEvalStack' o activeThreadState.ActiveMethodState
|
||||
|
||||
{ state with
|
||||
ThreadState = state.ThreadState |> Map.add thread newThreadState
|
||||
}
|
||||
|
||||
let pushToEvalStack (o : CliType) (thread : ThreadId) (state : IlMachineState) : IlMachineState =
|
||||
let activeThreadState = state.ThreadState.[thread]
|
||||
|
||||
let newThreadState =
|
||||
activeThreadState
|
||||
|> ThreadState.pushToEvalStack o activeThreadState.ActiveMethodState
|
||||
|
||||
{ state with
|
||||
ThreadState = state.ThreadState |> Map.add thread newThreadState
|
||||
}
|
||||
|
||||
let peekEvalStack (thread : ThreadId) (state : IlMachineState) : EvalStackValue option =
|
||||
ThreadState.peekEvalStack state.ThreadState.[thread]
|
||||
|
||||
let popEvalStack (thread : ThreadId) (state : IlMachineState) : EvalStackValue * IlMachineState =
|
||||
let ret, popped = ThreadState.popFromEvalStack state.ThreadState.[thread]
|
||||
|
||||
let state =
|
||||
{ state with
|
||||
ThreadState = state.ThreadState |> Map.add thread popped
|
||||
}
|
||||
|
||||
ret, state
|
||||
|
||||
let advanceProgramCounter (thread : ThreadId) (state : IlMachineState) : IlMachineState =
|
||||
{ state with
|
||||
ThreadState =
|
||||
state.ThreadState
|
||||
|> Map.change
|
||||
thread
|
||||
(fun state ->
|
||||
match state with
|
||||
| None -> failwith "expected state"
|
||||
| Some (state : ThreadState) -> state |> ThreadState.advanceProgramCounter |> Some
|
||||
)
|
||||
}
|
||||
|
||||
/// There might be no stack frame to return to, so you might get None.
|
||||
let returnStackFrame
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||
(currentThread : ThreadId)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState option
|
||||
=
|
||||
let threadStateAtEndOfMethod = state.ThreadState.[currentThread]
|
||||
|
||||
match threadStateAtEndOfMethod.MethodState.ReturnState with
|
||||
| None -> None
|
||||
| Some returnState ->
|
||||
|
||||
let state =
|
||||
match returnState.WasInitialisingType with
|
||||
| None -> state
|
||||
| Some finishedInitialising -> state.WithTypeEndInit currentThread finishedInitialising
|
||||
|
||||
// Return to previous stack frame
|
||||
let state =
|
||||
{ state with
|
||||
ThreadState =
|
||||
state.ThreadState
|
||||
|> Map.add
|
||||
currentThread
|
||||
{ threadStateAtEndOfMethod with
|
||||
ActiveMethodState = returnState.JumpTo
|
||||
ActiveAssembly =
|
||||
threadStateAtEndOfMethod.MethodStates.[returnState.JumpTo].ExecutingMethod.DeclaringType
|
||||
.Assembly
|
||||
}
|
||||
}
|
||||
|
||||
match returnState.WasConstructingObj with
|
||||
| Some constructing ->
|
||||
// Assumption: a constructor can't also return a value.
|
||||
// If we were constructing a reference type, we push a reference to it.
|
||||
// Otherwise, extract the now-complete object from the heap and push it to the stack directly.
|
||||
let constructed = state.ManagedHeap.NonArrayObjects.[constructing]
|
||||
|
||||
let resolvedBaseType =
|
||||
DumpedAssembly.resolveBaseType
|
||||
corelib
|
||||
state._LoadedAssemblies
|
||||
constructed.Type.Assembly
|
||||
constructed.Type.BaseType
|
||||
|
||||
match resolvedBaseType with
|
||||
| ResolvedBaseType.Delegate
|
||||
| ResolvedBaseType.Object -> state |> pushToEvalStack (CliType.OfManagedObject constructing) currentThread
|
||||
| ResolvedBaseType.ValueType ->
|
||||
state
|
||||
|> pushToEvalStack (CliType.ValueType (Seq.toList constructed.Fields.Values)) currentThread
|
||||
| ResolvedBaseType.Enum -> failwith "TODO"
|
||||
| None ->
|
||||
match threadStateAtEndOfMethod.MethodState.EvaluationStack.Values with
|
||||
| [] ->
|
||||
// no return value
|
||||
state
|
||||
| [ retVal ] ->
|
||||
let retType =
|
||||
threadStateAtEndOfMethod.MethodState.ExecutingMethod.Signature.ReturnType
|
||||
|
||||
match retType with
|
||||
| TypeDefn.Void -> state
|
||||
| retType ->
|
||||
// TODO: generics
|
||||
let state, zero =
|
||||
cliTypeZeroOf loggerFactory corelib (state.ActiveAssembly currentThread) retType None None state
|
||||
|
||||
let toPush = EvalStackValue.toCliTypeCoerced zero retVal
|
||||
|
||||
state |> pushToEvalStack toPush currentThread
|
||||
| _ ->
|
||||
failwith
|
||||
"Unexpected interpretation result has a local evaluation stack with more than one element on RET"
|
||||
|
||||
|> Some
|
||||
|
||||
let private safeIntrinsics =
|
||||
[
|
||||
// The IL implementation is fine: https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs#L677
|
||||
@@ -417,9 +546,13 @@ module IlMachineState =
|
||||
let callIntrinsic
|
||||
(baseClassTypes : BaseClassTypes<_>)
|
||||
(methodToCall : WoofWare.PawPrint.MethodInfo<TypeDefn, WoofWare.PawPrint.GenericParameter>)
|
||||
(currentThread : ThreadId)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState option
|
||||
=
|
||||
let callerAssy =
|
||||
state.ThreadState.[currentThread].MethodState.ExecutingMethod.DeclaringType.Assembly
|
||||
|
||||
if
|
||||
methodToCall.DeclaringType.Assembly.Name = "System.Private.CoreLib"
|
||||
&& methodToCall.DeclaringType.Name = "Volatile"
|
||||
@@ -447,7 +580,32 @@ module IlMachineState =
|
||||
|
||||
let resultFieldType = resultField.Signature
|
||||
failwith "TODO"
|
||||
| "System.Private.CoreLib", "BitConverter", "SingleToInt32Bits" ->
|
||||
let arg, state = popEvalStack currentThread state
|
||||
|
||||
let result =
|
||||
match arg with
|
||||
| EvalStackValue.Float f -> BitConverter.SingleToInt32Bits (float32<float> f) |> EvalStackValue.Int32
|
||||
| _ -> failwith "TODO"
|
||||
|
||||
state
|
||||
|> pushToEvalStack' result currentThread
|
||||
|> advanceProgramCounter currentThread
|
||||
|> Some
|
||||
| "System.Private.CoreLib", "BitConverter", "DoubleToInt64Bits" ->
|
||||
let arg, state = popEvalStack currentThread state
|
||||
|
||||
let result =
|
||||
match arg with
|
||||
| EvalStackValue.Float f -> BitConverter.DoubleToInt64Bits f |> EvalStackValue.Int64
|
||||
| _ -> failwith "TODO"
|
||||
|
||||
state
|
||||
|> pushToEvalStack' result currentThread
|
||||
|> advanceProgramCounter currentThread
|
||||
|> Some
|
||||
| a, b, c -> failwith $"TODO: implement JIT intrinsic {a}.{b}.{c}"
|
||||
|> Option.map (fun s -> s.WithThreadSwitchedToAssembly callerAssy currentThread |> fst)
|
||||
|
||||
let callMethod
|
||||
(loggerFactory : ILoggerFactory)
|
||||
@@ -477,7 +635,7 @@ module IlMachineState =
|
||||
|
||||
match
|
||||
if isIntrinsic then
|
||||
callIntrinsic corelib methodToCall state
|
||||
callIntrinsic corelib methodToCall thread state
|
||||
else
|
||||
None
|
||||
with
|
||||
@@ -523,7 +681,7 @@ module IlMachineState =
|
||||
let args, afterPop =
|
||||
if methodToCall.IsStatic then
|
||||
// Static method: pop args in reverse order
|
||||
let args = ImmutableArray.CreateBuilder (methodToCall.Parameters.Length)
|
||||
let args = ImmutableArray.CreateBuilder methodToCall.Parameters.Length
|
||||
let mutable currentState = activeMethodState
|
||||
|
||||
for i = methodToCall.Parameters.Length - 1 downto 0 do
|
||||
@@ -544,7 +702,9 @@ module IlMachineState =
|
||||
// Constructor: `this` is on top of stack, by our own odd little calling convention
|
||||
// where Newobj puts the object pointer on top
|
||||
let thisArg, newState =
|
||||
popAndCoerceArg (CliType.RuntimePointer (CliRuntimePointer.Unmanaged ())) currentState
|
||||
popAndCoerceArg
|
||||
(CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null))
|
||||
currentState
|
||||
|
||||
args.Add thisArg
|
||||
currentState <- newState
|
||||
@@ -564,7 +724,9 @@ module IlMachineState =
|
||||
currentState <- newState
|
||||
|
||||
let thisArg, newState =
|
||||
popAndCoerceArg (CliType.RuntimePointer (CliRuntimePointer.Unmanaged ())) currentState
|
||||
popAndCoerceArg
|
||||
(CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null))
|
||||
currentState
|
||||
|
||||
args.Add thisArg
|
||||
currentState <- newState
|
||||
@@ -936,28 +1098,6 @@ module IlMachineState =
|
||||
|
||||
alloc, state
|
||||
|
||||
let pushToEvalStack' (o : EvalStackValue) (thread : ThreadId) (state : IlMachineState) =
|
||||
let activeThreadState = state.ThreadState.[thread]
|
||||
|
||||
let newThreadState =
|
||||
activeThreadState
|
||||
|> ThreadState.pushToEvalStack' o activeThreadState.ActiveMethodState
|
||||
|
||||
{ state with
|
||||
ThreadState = state.ThreadState |> Map.add thread newThreadState
|
||||
}
|
||||
|
||||
let pushToEvalStack (o : CliType) (thread : ThreadId) (state : IlMachineState) : IlMachineState =
|
||||
let activeThreadState = state.ThreadState.[thread]
|
||||
|
||||
let newThreadState =
|
||||
activeThreadState
|
||||
|> ThreadState.pushToEvalStack o activeThreadState.ActiveMethodState
|
||||
|
||||
{ state with
|
||||
ThreadState = state.ThreadState |> Map.add thread newThreadState
|
||||
}
|
||||
|
||||
let popFromStackToLocalVariable
|
||||
(thread : ThreadId)
|
||||
(localVariableIndex : int)
|
||||
@@ -984,19 +1124,6 @@ module IlMachineState =
|
||||
}
|
||||
}
|
||||
|
||||
let peekEvalStack (thread : ThreadId) (state : IlMachineState) : EvalStackValue option =
|
||||
ThreadState.peekEvalStack state.ThreadState.[thread]
|
||||
|
||||
let popEvalStack (thread : ThreadId) (state : IlMachineState) : EvalStackValue * IlMachineState =
|
||||
let ret, popped = ThreadState.popFromEvalStack state.ThreadState.[thread]
|
||||
|
||||
let state =
|
||||
{ state with
|
||||
ThreadState = state.ThreadState |> Map.add thread popped
|
||||
}
|
||||
|
||||
ret, state
|
||||
|
||||
let setArrayValue
|
||||
(arrayAllocation : ManagedHeapAddress)
|
||||
(v : CliType)
|
||||
@@ -1010,19 +1137,6 @@ module IlMachineState =
|
||||
ManagedHeap = heap
|
||||
}
|
||||
|
||||
let advanceProgramCounter (thread : ThreadId) (state : IlMachineState) : IlMachineState =
|
||||
{ state with
|
||||
ThreadState =
|
||||
state.ThreadState
|
||||
|> Map.change
|
||||
thread
|
||||
(fun state ->
|
||||
match state with
|
||||
| None -> failwith "expected state"
|
||||
| Some (state : ThreadState) -> state |> ThreadState.advanceProgramCounter |> Some
|
||||
)
|
||||
}
|
||||
|
||||
let jumpProgramCounter (thread : ThreadId) (bytes : int) (state : IlMachineState) : IlMachineState =
|
||||
{ state with
|
||||
ThreadState =
|
||||
@@ -1121,86 +1235,6 @@ module IlMachineState =
|
||||
|
||||
state, assy.Name, Choice1Of2 method
|
||||
|
||||
/// There might be no stack frame to return to, so you might get None.
|
||||
let returnStackFrame
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||
(currentThread : ThreadId)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState option
|
||||
=
|
||||
let threadStateAtEndOfMethod = state.ThreadState.[currentThread]
|
||||
|
||||
match threadStateAtEndOfMethod.MethodState.ReturnState with
|
||||
| None -> None
|
||||
| Some returnState ->
|
||||
|
||||
let state =
|
||||
match returnState.WasInitialisingType with
|
||||
| None -> state
|
||||
| Some finishedInitialising -> state.WithTypeEndInit currentThread finishedInitialising
|
||||
|
||||
// Return to previous stack frame
|
||||
let state =
|
||||
{ state with
|
||||
ThreadState =
|
||||
state.ThreadState
|
||||
|> Map.add
|
||||
currentThread
|
||||
{ threadStateAtEndOfMethod with
|
||||
ActiveMethodState = returnState.JumpTo
|
||||
ActiveAssembly =
|
||||
threadStateAtEndOfMethod.MethodStates.[returnState.JumpTo].ExecutingMethod.DeclaringType
|
||||
.Assembly
|
||||
}
|
||||
}
|
||||
|
||||
match returnState.WasConstructingObj with
|
||||
| Some constructing ->
|
||||
// Assumption: a constructor can't also return a value.
|
||||
// If we were constructing a reference type, we push a reference to it.
|
||||
// Otherwise, extract the now-complete object from the heap and push it to the stack directly.
|
||||
let constructed = state.ManagedHeap.NonArrayObjects.[constructing]
|
||||
|
||||
let resolvedBaseType =
|
||||
DumpedAssembly.resolveBaseType
|
||||
corelib
|
||||
state._LoadedAssemblies
|
||||
constructed.Type.Assembly
|
||||
constructed.Type.BaseType
|
||||
|
||||
match resolvedBaseType with
|
||||
| ResolvedBaseType.Delegate
|
||||
| ResolvedBaseType.Object -> state |> pushToEvalStack (CliType.OfManagedObject constructing) currentThread
|
||||
| ResolvedBaseType.ValueType ->
|
||||
state
|
||||
|> pushToEvalStack (CliType.ValueType (Seq.toList constructed.Fields.Values)) currentThread
|
||||
| ResolvedBaseType.Enum -> failwith "TODO"
|
||||
| None ->
|
||||
match threadStateAtEndOfMethod.MethodState.EvaluationStack.Values with
|
||||
| [] ->
|
||||
// no return value
|
||||
state
|
||||
| [ retVal ] ->
|
||||
let retType =
|
||||
threadStateAtEndOfMethod.MethodState.ExecutingMethod.Signature.ReturnType
|
||||
|
||||
match retType with
|
||||
| TypeDefn.Void -> state
|
||||
| retType ->
|
||||
// TODO: generics
|
||||
let state, zero =
|
||||
cliTypeZeroOf loggerFactory corelib (state.ActiveAssembly currentThread) retType None None state
|
||||
|
||||
let toPush = EvalStackValue.toCliTypeCoerced zero retVal
|
||||
|
||||
state |> pushToEvalStack toPush currentThread
|
||||
| _ ->
|
||||
failwith
|
||||
"Unexpected interpretation result has a local evaluation stack with more than one element on RET"
|
||||
|
||||
|> Some
|
||||
|
||||
let setLocalVariable
|
||||
(thread : ThreadId)
|
||||
(stackFrame : int)
|
||||
|
@@ -31,6 +31,78 @@ module private ArithmeticOperation =
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module NullaryIlOp =
|
||||
type private LdindTargetType =
|
||||
| LdindI
|
||||
| LdindI1
|
||||
| LdindI2
|
||||
| LdindI4
|
||||
| LdindI8
|
||||
| LdindU1
|
||||
| LdindU2
|
||||
| LdindU4
|
||||
| LdindU8
|
||||
| LdindR4
|
||||
| LdindR8
|
||||
|
||||
// Helper to get the target CliType for each Ldind variant
|
||||
let private getTargetLdindCliType (targetType : LdindTargetType) : CliType =
|
||||
match targetType with
|
||||
| LdindI -> CliType.Numeric (CliNumericType.NativeInt 0L)
|
||||
| LdindI1 -> CliType.Numeric (CliNumericType.Int8 0y)
|
||||
| LdindI2 -> CliType.Numeric (CliNumericType.Int16 0s)
|
||||
| LdindI4 -> CliType.Numeric (CliNumericType.Int32 0)
|
||||
| LdindI8 -> CliType.Numeric (CliNumericType.Int64 0L)
|
||||
| LdindU1 -> CliType.Numeric (CliNumericType.UInt8 0uy)
|
||||
| LdindU2 -> CliType.Numeric (CliNumericType.UInt16 0us)
|
||||
| LdindU4 ->
|
||||
// This doesn't actually exist as a CLI type
|
||||
CliType.Numeric (CliNumericType.Int32 0)
|
||||
| LdindU8 ->
|
||||
// This doesn't actually exist as a CLI type
|
||||
CliType.Numeric (CliNumericType.Int64 0L)
|
||||
| LdindR4 -> CliType.Numeric (CliNumericType.Float32 0.0f)
|
||||
| LdindR8 -> CliType.Numeric (CliNumericType.Float64 0.0)
|
||||
|
||||
/// Retrieve a value from a pointer
|
||||
let private loadFromPointerSource (state : IlMachineState) (src : ManagedPointerSource) : CliType =
|
||||
match src with
|
||||
| ManagedPointerSource.Null -> failwith "unexpected null pointer in Ldind operation"
|
||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
|
||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables.[int<uint16> whichVar]
|
||||
| ManagedPointerSource.Heap managedHeapAddress -> failwith "TODO: Heap pointer dereferencing not implemented"
|
||||
|
||||
// Unified Ldind implementation
|
||||
let private executeLdind
|
||||
(targetType : LdindTargetType)
|
||||
(currentThread : ThreadId)
|
||||
(state : IlMachineState)
|
||||
: ExecutionResult
|
||||
=
|
||||
let popped, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let loadedValue =
|
||||
match popped with
|
||||
| EvalStackValue.ManagedPointer src -> loadFromPointerSource state src
|
||||
| EvalStackValue.NativeInt nativeIntSource ->
|
||||
failwith $"TODO: Native int pointer dereferencing not implemented for {targetType}"
|
||||
| EvalStackValue.ObjectRef managedHeapAddress ->
|
||||
failwith "TODO: Object reference dereferencing not implemented"
|
||||
| other -> failwith $"Unexpected eval stack value for Ldind operation: {other}"
|
||||
|
||||
let loadedValue = loadedValue |> EvalStackValue.ofCliType
|
||||
|
||||
let targetCliType = getTargetLdindCliType targetType
|
||||
let coercedValue = EvalStackValue.toCliTypeCoerced targetCliType loadedValue
|
||||
|
||||
let state =
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack coercedValue currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||
|
||||
let private binaryArithmeticOperation
|
||||
(op : IArithmeticOperation)
|
||||
(currentThread : ThreadId)
|
||||
@@ -255,7 +327,12 @@ module NullaryIlOp =
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| LdcI4_m1 -> failwith "TODO: LdcI4_m1 unimplemented"
|
||||
| LdcI4_m1 ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 -1)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| LdNull ->
|
||||
let state =
|
||||
state
|
||||
@@ -404,7 +481,20 @@ module NullaryIlOp =
|
||||
| And -> failwith "TODO: And unimplemented"
|
||||
| Or -> failwith "TODO: Or unimplemented"
|
||||
| Xor -> failwith "TODO: Xor unimplemented"
|
||||
| Conv_I -> failwith "TODO: Conv_I unimplemented"
|
||||
| Conv_I ->
|
||||
let popped, state = IlMachineState.popEvalStack currentThread state
|
||||
let converted = EvalStackValue.toNativeInt popped
|
||||
|
||||
let state =
|
||||
match converted with
|
||||
| None -> failwith "TODO: Conv_I conversion failure unimplemented"
|
||||
| Some conv ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' (EvalStackValue.NativeInt conv) currentThread
|
||||
|
||||
let state = state |> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||
| Conv_I1 -> failwith "TODO: Conv_I1 unimplemented"
|
||||
| Conv_I2 -> failwith "TODO: Conv_I2 unimplemented"
|
||||
| Conv_I4 ->
|
||||
@@ -453,6 +543,7 @@ module NullaryIlOp =
|
||||
(conv % uint64 System.Int64.MaxValue) |> int64 |> NativeIntSource.Verbatim
|
||||
else
|
||||
int64 conv |> NativeIntSource.Verbatim
|
||||
| UnsignedNativeIntSource.FromManagedPointer ptr -> NativeIntSource.ManagedPointer ptr
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' (EvalStackValue.NativeInt conv) currentThread
|
||||
@@ -656,96 +747,17 @@ module NullaryIlOp =
|
||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||
| Stind_R4 -> failwith "TODO: Stind_R4 unimplemented"
|
||||
| Stind_R8 -> failwith "TODO: Stind_R8 unimplemented"
|
||||
| Ldind_i -> failwith "TODO: Ldind_i unimplemented"
|
||||
| Ldind_i1 -> failwith "TODO: Ldind_i1 unimplemented"
|
||||
| Ldind_i2 -> failwith "TODO: Ldind_i2 unimplemented"
|
||||
| Ldind_i4 ->
|
||||
let popped, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let value =
|
||||
let load (c : CliType) =
|
||||
match c with
|
||||
| CliType.Bool _ -> failwith "bool"
|
||||
| CliType.Numeric numeric ->
|
||||
match numeric with
|
||||
| CliNumericType.Int32 i -> i
|
||||
| _ -> failwith $"TODO: {numeric}"
|
||||
| CliType.Char _ -> failwith "tried to load a Char as a i4"
|
||||
| CliType.ObjectRef _ -> failwith "tried to load an ObjectRef as a i4"
|
||||
| CliType.RuntimePointer _ -> failwith "tried to load a RuntimePointer as a i4"
|
||||
| CliType.ValueType cliTypes -> failwith "todo"
|
||||
|
||||
match popped with
|
||||
| EvalStackValue.ManagedPointer src ->
|
||||
match src with
|
||||
| ManagedPointerSource.Null -> failwith "unexpected null pointer in Ldind_i4"
|
||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
|
||||
let methodState =
|
||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
|
||||
|
||||
load methodState
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
||||
let methodState =
|
||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables
|
||||
.[int<uint16> whichVar]
|
||||
|
||||
load methodState
|
||||
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
|
||||
| s -> failwith $"TODO(Ldind_i4): {s}"
|
||||
|
||||
let state =
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 value)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||
|
||||
| Ldind_i8 -> failwith "TODO: Ldind_i8 unimplemented"
|
||||
| Ldind_u1 ->
|
||||
let popped, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let value =
|
||||
let load (c : CliType) =
|
||||
match c with
|
||||
| CliType.Bool b -> b
|
||||
| CliType.Numeric numeric -> failwith $"tried to load a Numeric as a u8: {numeric}"
|
||||
| CliType.Char _ -> failwith "tried to load a Char as a u8"
|
||||
| CliType.ObjectRef _ -> failwith "tried to load an ObjectRef as a u8"
|
||||
| CliType.RuntimePointer _ -> failwith "tried to load a RuntimePointer as a u8"
|
||||
| CliType.ValueType cliTypes -> failwith "todo"
|
||||
|
||||
match popped with
|
||||
| EvalStackValue.NativeInt nativeIntSource -> failwith $"TODO: in Ldind_u1, {nativeIntSource}"
|
||||
| EvalStackValue.ManagedPointer src ->
|
||||
match src with
|
||||
| ManagedPointerSource.Null -> failwith "unexpected null pointer in Ldind_u1"
|
||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
|
||||
let methodState =
|
||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
|
||||
|
||||
load methodState
|
||||
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
||||
let methodState =
|
||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables
|
||||
.[int<uint16> whichVar]
|
||||
|
||||
load methodState
|
||||
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
|
||||
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
|
||||
| popped -> failwith $"unexpected Ldind_u1 input: {popped}"
|
||||
|
||||
let state =
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.UInt8 value)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||
| Ldind_u2 -> failwith "TODO: Ldind_u2 unimplemented"
|
||||
| Ldind_u4 -> failwith "TODO: Ldind_u4 unimplemented"
|
||||
| Ldind_i -> executeLdind LdindTargetType.LdindI currentThread state
|
||||
| Ldind_i1 -> executeLdind LdindTargetType.LdindI1 currentThread state
|
||||
| Ldind_i2 -> executeLdind LdindTargetType.LdindI2 currentThread state
|
||||
| Ldind_i4 -> executeLdind LdindTargetType.LdindI4 currentThread state
|
||||
| Ldind_i8 -> executeLdind LdindTargetType.LdindI8 currentThread state
|
||||
| Ldind_u1 -> executeLdind LdindTargetType.LdindU1 currentThread state
|
||||
| Ldind_u2 -> executeLdind LdindTargetType.LdindU2 currentThread state
|
||||
| Ldind_u4 -> executeLdind LdindTargetType.LdindU4 currentThread state
|
||||
| Ldind_u8 -> failwith "TODO: Ldind_u8 unimplemented"
|
||||
| Ldind_r4 -> failwith "TODO: Ldind_r4 unimplemented"
|
||||
| Ldind_r8 -> failwith "TODO: Ldind_r8 unimplemented"
|
||||
| Ldind_r4 -> executeLdind LdindTargetType.LdindR4 currentThread state
|
||||
| Ldind_r8 -> executeLdind LdindTargetType.LdindR8 currentThread state
|
||||
| Rem -> failwith "TODO: Rem unimplemented"
|
||||
| Rem_un -> failwith "TODO: Rem_un unimplemented"
|
||||
| Volatile -> failwith "TODO: Volatile unimplemented"
|
||||
|
@@ -391,7 +391,15 @@ module internal UnaryConstIlOp =
|
||||
| Bgt_un i -> failwith "TODO: Bgt_un unimplemented"
|
||||
| Ble_un i -> failwith "TODO: Ble_un unimplemented"
|
||||
| Blt_un i -> failwith "TODO: Blt_un unimplemented"
|
||||
| Ldloc_s b -> failwith "TODO: Ldloc_s unimplemented"
|
||||
| Ldloc_s b ->
|
||||
let threadState = state.ThreadState.[currentThread]
|
||||
|
||||
let state =
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack threadState.MethodState.LocalVariables.[int<uint8> b] currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
state, WhatWeDid.Executed
|
||||
| Ldloca_s b ->
|
||||
let threadState = state.ThreadState.[currentThread]
|
||||
|
||||
|
@@ -254,7 +254,12 @@ module internal UnaryMetadataIlOp =
|
||||
| EvalStackValue.Int32 v -> v
|
||||
| popped -> failwith $"unexpectedly popped value %O{popped} to serve as array len"
|
||||
|
||||
let elementType, assy =
|
||||
let typeGenerics =
|
||||
match newMethodState.ExecutingMethod.DeclaringType.Generics with
|
||||
| [] -> None
|
||||
| l -> Some (ImmutableArray.CreateRange l)
|
||||
|
||||
let state, elementType, assy =
|
||||
match metadataToken with
|
||||
| MetadataToken.TypeDefinition defn ->
|
||||
let assy = state.LoadedAssembly currentState.ActiveAssembly |> Option.get
|
||||
@@ -271,21 +276,48 @@ module internal UnaryMetadataIlOp =
|
||||
| ResolvedBaseType.Object -> SignatureTypeKind.Class
|
||||
| ResolvedBaseType.Delegate -> failwith "TODO: delegate"
|
||||
|
||||
let result =
|
||||
TypeDefn.FromDefinition (
|
||||
ComparableTypeDefinitionHandle.Make defn.TypeDefHandle,
|
||||
defn.Assembly.Name,
|
||||
signatureTypeKind
|
||||
),
|
||||
assy
|
||||
)
|
||||
|
||||
state, result, assy
|
||||
| MetadataToken.TypeSpecification spec ->
|
||||
let assy = state.LoadedAssembly currentState.ActiveAssembly |> Option.get
|
||||
assy.TypeSpecs.[spec].Signature, assy
|
||||
| x -> failwith $"TODO: Newarr element type resolution unimplemented for {x}"
|
||||
state, assy.TypeSpecs.[spec].Signature, assy
|
||||
| MetadataToken.TypeReference ref ->
|
||||
let ref = state.ActiveAssembly(thread).TypeRefs.[ref]
|
||||
|
||||
let typeGenerics =
|
||||
match newMethodState.ExecutingMethod.DeclaringType.Generics with
|
||||
| [] -> None
|
||||
| l -> Some (ImmutableArray.CreateRange l)
|
||||
let state, assy, resolved =
|
||||
IlMachineState.resolveTypeFromRef
|
||||
loggerFactory
|
||||
(state.ActiveAssembly thread)
|
||||
ref
|
||||
typeGenerics
|
||||
state
|
||||
|
||||
let baseType =
|
||||
resolved.BaseType
|
||||
|> DumpedAssembly.resolveBaseType baseClassTypes state._LoadedAssemblies assy.Name
|
||||
|
||||
let signatureTypeKind =
|
||||
match baseType with
|
||||
| ResolvedBaseType.Enum
|
||||
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
||||
| ResolvedBaseType.Object -> SignatureTypeKind.Class
|
||||
| ResolvedBaseType.Delegate -> failwith "TODO: delegate"
|
||||
|
||||
let result =
|
||||
TypeDefn.FromDefinition (
|
||||
ComparableTypeDefinitionHandle.Make resolved.TypeDefHandle,
|
||||
assy.Name.FullName,
|
||||
signatureTypeKind
|
||||
)
|
||||
|
||||
state, result, assy
|
||||
| x -> failwith $"TODO: Newarr element type resolution unimplemented for {x}"
|
||||
|
||||
let state, zeroOfType =
|
||||
IlMachineState.cliTypeZeroOf
|
||||
@@ -427,7 +459,10 @@ module internal UnaryMetadataIlOp =
|
||||
| EvalStackValue.ManagedPointer source ->
|
||||
match source with
|
||||
| ManagedPointerSource.Null -> failwith "TODO: raise NullReferenceException"
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> failwith "todo"
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
||||
let threadState = state.ThreadState.[sourceThread]
|
||||
let methodState = threadState.MethodStates.[methodFrame]
|
||||
failwith "TODO"
|
||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> failwith "todo"
|
||||
| ManagedPointerSource.Heap addr ->
|
||||
match state.ManagedHeap.NonArrayObjects.TryGetValue addr with
|
||||
|
Reference in New Issue
Block a user