mirror of
https://github.com/Smaug123/WoofWare.PawPrint
synced 2025-10-09 15:58:39 +00:00
WIP
This commit is contained in:
@@ -57,6 +57,11 @@ module TestPureCases =
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
}
|
||||
{
|
||||
FileName = "UnsafeAs.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
}
|
||||
{
|
||||
FileName = "GenericEdgeCases.cs"
|
||||
ExpectedReturnCode = 0
|
||||
|
@@ -44,6 +44,7 @@
|
||||
<EmbeddedResource Include="sourcesPure\Sizeof.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="sourcesPure\UnsafeAs.cs" />
|
||||
<EmbeddedResource Include="sourcesImpure\WriteLine.cs" />
|
||||
<EmbeddedResource Include="sourcesImpure\InstaQuit.cs" />
|
||||
</ItemGroup>
|
||||
|
215
WoofWare.PawPrint.Test/sourcesPure/UnsafeAs.cs
Normal file
215
WoofWare.PawPrint.Test/sourcesPure/UnsafeAs.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
public class TestUnsafeAs
|
||||
{
|
||||
private struct Int32Wrapper
|
||||
{
|
||||
public int Value;
|
||||
}
|
||||
|
||||
private struct UInt32Wrapper
|
||||
{
|
||||
public uint Value;
|
||||
}
|
||||
|
||||
private struct TwoInt16s
|
||||
{
|
||||
public short First;
|
||||
public short Second;
|
||||
}
|
||||
|
||||
private struct FourBytes
|
||||
{
|
||||
public byte B0;
|
||||
public byte B1;
|
||||
public byte B2;
|
||||
public byte B3;
|
||||
}
|
||||
|
||||
private enum TestEnum : int
|
||||
{
|
||||
Value1 = 0x12345678,
|
||||
Value2 = -1
|
||||
}
|
||||
|
||||
public static int Main(string[] argv)
|
||||
{
|
||||
// Test 1: Int32 -> UInt32 reinterpretation
|
||||
{
|
||||
int original = -1;
|
||||
ref uint reinterpreted = ref Unsafe.As<int, uint>(ref original);
|
||||
|
||||
if (reinterpreted != 0xFFFFFFFF)
|
||||
return 1;
|
||||
|
||||
reinterpreted = 0x12345678;
|
||||
if (original != 0x12345678)
|
||||
return 2;
|
||||
|
||||
original = int.MinValue;
|
||||
if (reinterpreted != 0x80000000)
|
||||
return 3;
|
||||
}
|
||||
|
||||
// Test 2: Struct -> Struct reinterpretation
|
||||
{
|
||||
Int32Wrapper wrapper = new Int32Wrapper { Value = 0x01020304 };
|
||||
ref FourBytes bytes = ref Unsafe.As<Int32Wrapper, FourBytes>(ref wrapper);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
if (bytes.B0 != 0x04) return 10;
|
||||
if (bytes.B1 != 0x03) return 11;
|
||||
if (bytes.B2 != 0x02) return 12;
|
||||
if (bytes.B3 != 0x01) return 13;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bytes.B0 != 0x01) return 14;
|
||||
if (bytes.B1 != 0x02) return 15;
|
||||
if (bytes.B2 != 0x03) return 16;
|
||||
if (bytes.B3 != 0x04) return 17;
|
||||
}
|
||||
|
||||
bytes.B0 = 0xFF;
|
||||
int expectedValue = BitConverter.IsLittleEndian ? 0x010203FF : unchecked((int)0xFF020304);
|
||||
if (wrapper.Value != expectedValue)
|
||||
return 18;
|
||||
}
|
||||
|
||||
// Test 3: Int32 -> Two Int16s
|
||||
{
|
||||
int value = 0x12345678;
|
||||
ref TwoInt16s halves = ref Unsafe.As<int, TwoInt16s>(ref value);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
if (halves.First != unchecked((short)0x5678)) return 20;
|
||||
if (halves.Second != 0x1234) return 21;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (halves.First != 0x1234) return 22;
|
||||
if (halves.Second != unchecked((short)0x5678)) return 23;
|
||||
}
|
||||
|
||||
halves.First = -1;
|
||||
int expectedValue = BitConverter.IsLittleEndian ? 0x1234FFFF : unchecked((int)0xFFFF5678);
|
||||
if (value != expectedValue)
|
||||
return 24;
|
||||
}
|
||||
|
||||
// Test 4: Array element reinterpretation
|
||||
{
|
||||
int[] intArray = new int[] { 0x01020304, 0x05060708 };
|
||||
ref uint uintRef = ref Unsafe.As<int, uint>(ref intArray[0]);
|
||||
|
||||
if (uintRef != 0x01020304u)
|
||||
return 30;
|
||||
|
||||
uintRef = 0xAABBCCDD;
|
||||
if (intArray[0] != unchecked((int)0xAABBCCDD))
|
||||
return 31;
|
||||
if (intArray[1] != 0x05060708)
|
||||
return 32;
|
||||
}
|
||||
|
||||
// Test 5: Bool -> Byte
|
||||
{
|
||||
bool trueValue = true;
|
||||
bool falseValue = false;
|
||||
|
||||
ref byte trueByte = ref Unsafe.As<bool, byte>(ref trueValue);
|
||||
ref byte falseByte = ref Unsafe.As<bool, byte>(ref falseValue);
|
||||
|
||||
if (trueByte != 1)
|
||||
return 40;
|
||||
if (falseByte != 0)
|
||||
return 41;
|
||||
|
||||
// Modify through byte reference
|
||||
trueByte = 0;
|
||||
if (trueValue != false)
|
||||
return 42;
|
||||
|
||||
falseByte = 1;
|
||||
if (falseValue != true)
|
||||
return 43;
|
||||
}
|
||||
|
||||
// Test 6: Char -> UInt16
|
||||
{
|
||||
char ch = 'A';
|
||||
ref ushort asUInt16 = ref Unsafe.As<char, ushort>(ref ch);
|
||||
|
||||
if (asUInt16 != 65)
|
||||
return 50;
|
||||
|
||||
asUInt16 = 0x03B1; // Greek lowercase alpha
|
||||
if (ch != 'α')
|
||||
return 51;
|
||||
}
|
||||
|
||||
// Test 7: Float -> Int32
|
||||
{
|
||||
float floatValue = 1.0f;
|
||||
ref int intBits = ref Unsafe.As<float, int>(ref floatValue);
|
||||
|
||||
// IEEE 754: 1.0f = 0x3F800000
|
||||
if (intBits != 0x3F800000)
|
||||
return 60;
|
||||
|
||||
intBits = 0x40000000; // 2.0f in IEEE 754
|
||||
if (floatValue != 2.0f)
|
||||
return 61;
|
||||
|
||||
floatValue = -0.0f;
|
||||
if (intBits != unchecked((int)0x80000000))
|
||||
return 62;
|
||||
}
|
||||
|
||||
// Test 8: Double -> Int64
|
||||
{
|
||||
double doubleValue = 1.0;
|
||||
ref long longBits = ref Unsafe.As<double, long>(ref doubleValue);
|
||||
|
||||
// IEEE 754: 1.0 = 0x3FF0000000000000
|
||||
if (longBits != 0x3FF0000000000000L)
|
||||
return 70;
|
||||
|
||||
longBits = 0x4000000000000000L; // 2.0 in IEEE 754
|
||||
if (doubleValue != 2.0)
|
||||
return 71;
|
||||
}
|
||||
|
||||
// Test 9: Enum -> Underlying type
|
||||
{
|
||||
TestEnum enumValue = TestEnum.Value1;
|
||||
ref int underlying = ref Unsafe.As<TestEnum, int>(ref enumValue);
|
||||
|
||||
if (underlying != 0x12345678)
|
||||
return 80;
|
||||
|
||||
underlying = -1;
|
||||
if (enumValue != TestEnum.Value2)
|
||||
return 81;
|
||||
}
|
||||
|
||||
// Test 10: Local variable reinterpretation
|
||||
{
|
||||
int local = unchecked((int)0xDEADBEEF);
|
||||
ref uint localAsUint = ref Unsafe.As<int, uint>(ref local);
|
||||
|
||||
if (localAsUint != 0xDEADBEEF)
|
||||
return 90;
|
||||
|
||||
localAsUint = 0xCAFEBABE;
|
||||
if (local != unchecked((int)0xCAFEBABE))
|
||||
return 91;
|
||||
}
|
||||
|
||||
// All tests passed
|
||||
return 0;
|
||||
}
|
||||
}
|
@@ -201,7 +201,9 @@ module CliType =
|
||||
match vt.Fields with
|
||||
| [] -> failwith "is it even possible to instantiate a value type with no fields"
|
||||
| [ _, f ] -> sizeOf f
|
||||
| _ -> failwith $"TODO: %O{vt.Fields} (need to consider struct layout)"
|
||||
| fields ->
|
||||
// TODO: consider struct layout (there's an `Explicit` test that will exercise that)
|
||||
fields |> List.map (snd >> sizeOf) |> List.sum
|
||||
|
||||
let zeroOfPrimitive (primitiveType : PrimitiveType) : CliType =
|
||||
match primitiveType with
|
||||
|
@@ -267,19 +267,21 @@ module Intrinsics =
|
||||
let result =
|
||||
// Some types appear circular, because they're hardcoded in the runtime. We have to special-case them.
|
||||
match arg with
|
||||
| ConcreteChar state.ConcreteTypes ->
|
||||
false
|
||||
| ConcreteChar state.ConcreteTypes -> false
|
||||
| _ ->
|
||||
|
||||
let generic =
|
||||
AllConcreteTypes.lookup arg state.ConcreteTypes
|
||||
let generic = AllConcreteTypes.lookup arg state.ConcreteTypes
|
||||
|
||||
let generic =
|
||||
match generic with
|
||||
| None -> failwith "somehow have not already concretised type in IsReferenceOrContainsReferences"
|
||||
| Some generic -> generic
|
||||
|
||||
let td = state.LoadedAssembly(generic.Assembly) |> Option.get |> fun a -> a.TypeDefs.[generic.Definition.Get]
|
||||
let td =
|
||||
state.LoadedAssembly (generic.Assembly)
|
||||
|> Option.get
|
||||
|> fun a -> a.TypeDefs.[generic.Definition.Get]
|
||||
|
||||
let baseType =
|
||||
td.BaseType
|
||||
|> DumpedAssembly.resolveBaseType baseClassTypes state._LoadedAssemblies generic.Assembly
|
||||
@@ -289,12 +291,7 @@ module Intrinsics =
|
||||
| ResolvedBaseType.ValueType ->
|
||||
let nonStaticFields =
|
||||
td.Fields
|
||||
|> List.choose (fun field ->
|
||||
if field.IsStatic then
|
||||
None
|
||||
else
|
||||
Some field.Signature
|
||||
)
|
||||
|> List.choose (fun field -> if field.IsStatic then None else Some field.Signature)
|
||||
|
||||
failwith $"TODO: search the fields on {td.Namespace}.{td.Name}: {nonStaticFields}"
|
||||
| ResolvedBaseType.Object
|
||||
@@ -316,15 +313,14 @@ module Intrinsics =
|
||||
// https://github.com/dotnet/runtime/blob/721fdf6dcb032da1f883d30884e222e35e3d3c99/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs#L64
|
||||
let inputType, retType =
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [input], ret -> input, ret
|
||||
| [ input ], ret -> input, ret
|
||||
| _ -> failwith "bad signature Unsafe.As"
|
||||
|
||||
let genericArg = Seq.exactlyOne methodToCall.Generics
|
||||
|
||||
state
|
||||
|> IlMachineState.loadArgument currentThread 0
|
||||
let state =
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.ofBool result) currentThread
|
||||
|> IlMachineState.loadArgument currentThread 0
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
Some state
|
||||
|
Reference in New Issue
Block a user