mirror of
https://github.com/Smaug123/WoofWare.PawPrint
synced 2025-10-06 22:48:41 +00:00
WIP
This commit is contained in:
@@ -34,6 +34,10 @@ type FieldInfo<'typeGeneric, 'fieldGeneric when 'typeGeneric : comparison and 't
|
|||||||
/// literal, and other characteristics.
|
/// literal, and other characteristics.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Attributes : FieldAttributes
|
Attributes : FieldAttributes
|
||||||
|
|
||||||
|
/// Static fields don't have an offset at all; also, instance fields which don't have an explicit offset (but
|
||||||
|
/// which of course do have one implicitly, which is most fields) are None here.
|
||||||
|
Offset : int option
|
||||||
}
|
}
|
||||||
|
|
||||||
member this.HasFieldRVA = this.Attributes.HasFlag FieldAttributes.HasFieldRVA
|
member this.HasFieldRVA = this.Attributes.HasFlag FieldAttributes.HasFieldRVA
|
||||||
@@ -62,12 +66,18 @@ module FieldInfo =
|
|||||||
let declaringType =
|
let declaringType =
|
||||||
ConcreteType.make' assembly declaringType declaringTypeNamespace declaringTypeName typeGenerics
|
ConcreteType.make' assembly declaringType declaringTypeNamespace declaringTypeName typeGenerics
|
||||||
|
|
||||||
|
let offset =
|
||||||
|
match def.GetOffset () with
|
||||||
|
| -1 -> None
|
||||||
|
| s -> Some s
|
||||||
|
|
||||||
{
|
{
|
||||||
Name = name
|
Name = name
|
||||||
Signature = fieldSig
|
Signature = fieldSig
|
||||||
DeclaringType = declaringType
|
DeclaringType = declaringType
|
||||||
Handle = handle
|
Handle = handle
|
||||||
Attributes = def.Attributes
|
Attributes = def.Attributes
|
||||||
|
Offset = offset
|
||||||
}
|
}
|
||||||
|
|
||||||
let mapTypeGenerics<'a, 'b, 'field
|
let mapTypeGenerics<'a, 'b, 'field
|
||||||
@@ -84,5 +94,5 @@ module FieldInfo =
|
|||||||
DeclaringType = declaringType
|
DeclaringType = declaringType
|
||||||
Signature = input.Signature
|
Signature = input.Signature
|
||||||
Attributes = input.Attributes
|
Attributes = input.Attributes
|
||||||
|
Offset = input.Offset
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,16 @@ module TestPureCases =
|
|||||||
ExpectedReturnCode = 0
|
ExpectedReturnCode = 0
|
||||||
NativeImpls = MockEnv.make ()
|
NativeImpls = MockEnv.make ()
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
FileName = "OverlappingStructs.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "AdvancedStructLayout.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
{
|
{
|
||||||
FileName = "InitializeArray.cs"
|
FileName = "InitializeArray.cs"
|
||||||
ExpectedReturnCode = 0
|
ExpectedReturnCode = 0
|
||||||
|
@@ -45,6 +45,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="sourcesPure\UnsafeAs.cs" />
|
<EmbeddedResource Include="sourcesPure\UnsafeAs.cs" />
|
||||||
|
<EmbeddedResource Include="sourcesPure\OverlappingStructs.cs" />
|
||||||
|
<EmbeddedResource Include="sourcesPure\AdvancedStructLayout.cs" />
|
||||||
<EmbeddedResource Include="sourcesPure\Initobj.cs" />
|
<EmbeddedResource Include="sourcesPure\Initobj.cs" />
|
||||||
<EmbeddedResource Include="sourcesImpure\WriteLine.cs" />
|
<EmbeddedResource Include="sourcesImpure\WriteLine.cs" />
|
||||||
<EmbeddedResource Include="sourcesImpure\InstaQuit.cs" />
|
<EmbeddedResource Include="sourcesImpure\InstaQuit.cs" />
|
||||||
|
481
WoofWare.PawPrint.Test/sourcesPure/AdvancedStructLayout.cs
Normal file
481
WoofWare.PawPrint.Test/sourcesPure/AdvancedStructLayout.cs
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
// Compile with: csc /unsafe StructLayoutTestsAdvanced.cs
|
||||||
|
public class StructLayoutTestsAdvanced
|
||||||
|
{
|
||||||
|
// Test structs
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
struct PointerTestStruct
|
||||||
|
{
|
||||||
|
public int A;
|
||||||
|
public byte B;
|
||||||
|
public short C;
|
||||||
|
public int D;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
unsafe struct FixedBufferStruct
|
||||||
|
{
|
||||||
|
public int Header;
|
||||||
|
public fixed byte Buffer[64];
|
||||||
|
public int Footer;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
unsafe struct NestedFixedStruct
|
||||||
|
{
|
||||||
|
public fixed int IntArray[4];
|
||||||
|
public fixed double DoubleArray[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||||
|
struct MarshalStringStruct
|
||||||
|
{
|
||||||
|
public int Id;
|
||||||
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
||||||
|
public string Name;
|
||||||
|
public double Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
struct MarshalArrayStruct
|
||||||
|
{
|
||||||
|
public int Count;
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||||
|
public int[] Values;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
struct BlittableStruct
|
||||||
|
{
|
||||||
|
public int X;
|
||||||
|
public double Y;
|
||||||
|
public long Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref struct RefStruct
|
||||||
|
{
|
||||||
|
public int Value;
|
||||||
|
public Span<int> Span;
|
||||||
|
|
||||||
|
public RefStruct(int value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
Span = new Span<int>(new int[] { value, value * 2, value * 3 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly struct ReadOnlyStruct
|
||||||
|
{
|
||||||
|
public readonly int X;
|
||||||
|
public readonly int Y;
|
||||||
|
|
||||||
|
public ReadOnlyStruct(int x, int y)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Sum => X + Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly ref struct ReadOnlyRefStruct
|
||||||
|
{
|
||||||
|
public readonly int Value;
|
||||||
|
public readonly ReadOnlySpan<byte> Data;
|
||||||
|
|
||||||
|
public ReadOnlyRefStruct(int value, ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
Data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Generic<T> where T : struct
|
||||||
|
{
|
||||||
|
public T Value;
|
||||||
|
public int Index;
|
||||||
|
|
||||||
|
public Generic(T value, int index)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
Index = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DoubleGeneric<T, U>
|
||||||
|
{
|
||||||
|
public T First;
|
||||||
|
public U Second;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IIndexable
|
||||||
|
{
|
||||||
|
int GetIndex();
|
||||||
|
void SetIndex(int value);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StructWithInterface : IIndexable
|
||||||
|
{
|
||||||
|
public int Index;
|
||||||
|
public string Data;
|
||||||
|
|
||||||
|
public int GetIndex() => Index;
|
||||||
|
public void SetIndex(int value) => Index = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IMutable
|
||||||
|
{
|
||||||
|
void Mutate();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MutableStruct : IMutable
|
||||||
|
{
|
||||||
|
public int Counter;
|
||||||
|
|
||||||
|
public void Mutate()
|
||||||
|
{
|
||||||
|
Counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RefReturnStruct
|
||||||
|
{
|
||||||
|
public int A;
|
||||||
|
public int B;
|
||||||
|
public int C;
|
||||||
|
|
||||||
|
public ref int GetRef(int index)
|
||||||
|
{
|
||||||
|
if (index == 0) return ref A;
|
||||||
|
if (index == 1) return ref B;
|
||||||
|
return ref C;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsafe int TestUnsafePointers()
|
||||||
|
{
|
||||||
|
var s = new PointerTestStruct { A = 0x12345678, B = 0xAB, C = 0x1234, D = 0xDEADBEEF };
|
||||||
|
|
||||||
|
// Test sizeof
|
||||||
|
int size = sizeof(PointerTestStruct);
|
||||||
|
if (size == 0) return 1;
|
||||||
|
|
||||||
|
// Test pointer access
|
||||||
|
PointerTestStruct* ptr = &s;
|
||||||
|
if (ptr->A != 0x12345678) return 2;
|
||||||
|
if (ptr->B != 0xAB) return 3;
|
||||||
|
if (ptr->C != 0x1234) return 4;
|
||||||
|
if (ptr->D != unchecked((int)0xDEADBEEF)) return 5;
|
||||||
|
|
||||||
|
// Test pointer arithmetic and casting
|
||||||
|
byte* bytePtr = (byte*)ptr;
|
||||||
|
int* intPtr = (int*)bytePtr;
|
||||||
|
if (*intPtr != 0x12345678) return 6; // First int field
|
||||||
|
|
||||||
|
// Verify field offsets
|
||||||
|
int* dPtr = &(ptr->D);
|
||||||
|
int* aPtr = &(ptr->A);
|
||||||
|
long ptrDiff = (byte*)dPtr - (byte*)aPtr;
|
||||||
|
if (ptrDiff < 8) return 7; // D should be at least 8 bytes from A
|
||||||
|
|
||||||
|
// Test modification through pointer
|
||||||
|
ptr->A = 999;
|
||||||
|
if (s.A != 999) return 8;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsafe int TestFixedBuffers()
|
||||||
|
{
|
||||||
|
var f = new FixedBufferStruct();
|
||||||
|
f.Header = 0xFEED;
|
||||||
|
f.Footer = 0xBEEF;
|
||||||
|
|
||||||
|
// Test fixed buffer access
|
||||||
|
for (int i = 0; i < 64; i++)
|
||||||
|
{
|
||||||
|
f.Buffer[i] = (byte)(i % 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f.Header != 0xFEED) return 10;
|
||||||
|
if (f.Footer != 0xBEEF) return 11;
|
||||||
|
|
||||||
|
// Verify buffer contents
|
||||||
|
for (int i = 0; i < 64; i++)
|
||||||
|
{
|
||||||
|
if (f.Buffer[i] != (byte)(i % 256)) return 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test pointer to fixed buffer
|
||||||
|
byte* bufPtr = f.Buffer;
|
||||||
|
bufPtr[0] = 255;
|
||||||
|
if (f.Buffer[0] != 255) return 13;
|
||||||
|
|
||||||
|
// Test nested fixed arrays
|
||||||
|
var n = new NestedFixedStruct();
|
||||||
|
n.IntArray[0] = 100;
|
||||||
|
n.IntArray[3] = 400;
|
||||||
|
n.DoubleArray[0] = 1.5;
|
||||||
|
n.DoubleArray[1] = 2.5;
|
||||||
|
|
||||||
|
if (n.IntArray[0] != 100) return 14;
|
||||||
|
if (n.IntArray[3] != 400) return 15;
|
||||||
|
if (Math.Abs(n.DoubleArray[0] - 1.5) > 0.0001) return 16;
|
||||||
|
if (Math.Abs(n.DoubleArray[1] - 2.5) > 0.0001) return 17;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsafe int TestMarshaling()
|
||||||
|
{
|
||||||
|
// Test string marshaling
|
||||||
|
var ms = new MarshalStringStruct
|
||||||
|
{
|
||||||
|
Id = 42,
|
||||||
|
Name = "TestString",
|
||||||
|
Value = 3.14159
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ms.Id != 42) return 20;
|
||||||
|
if (ms.Name != "TestString") return 21;
|
||||||
|
if (Math.Abs(ms.Value - 3.14159) > 0.00001) return 22;
|
||||||
|
|
||||||
|
// Test Marshal.SizeOf
|
||||||
|
int marshalSize = Marshal.SizeOf(typeof(MarshalStringStruct));
|
||||||
|
if (marshalSize == 0) return 23;
|
||||||
|
|
||||||
|
// Test array marshaling
|
||||||
|
var ma = new MarshalArrayStruct
|
||||||
|
{
|
||||||
|
Count = 5,
|
||||||
|
Values = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ma.Count != 5) return 24;
|
||||||
|
if (ma.Values.Length != 8) return 25;
|
||||||
|
if (ma.Values[7] != 8) return 26;
|
||||||
|
|
||||||
|
// Test StructureToPtr and PtrToStructure
|
||||||
|
var blittable = new BlittableStruct { X = 100, Y = 200.5, Z = 300 };
|
||||||
|
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(BlittableStruct)));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Marshal.StructureToPtr(blittable, ptr, false);
|
||||||
|
var recovered = (BlittableStruct)Marshal.PtrToStructure(ptr, typeof(BlittableStruct));
|
||||||
|
|
||||||
|
if (recovered.X != 100) return 27;
|
||||||
|
if (Math.Abs(recovered.Y - 200.5) > 0.00001) return 28;
|
||||||
|
if (recovered.Z != 300) return 29;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestRefStructs()
|
||||||
|
{
|
||||||
|
// Test ref struct
|
||||||
|
var rs = new RefStruct(10);
|
||||||
|
if (rs.Value != 10) return 30;
|
||||||
|
if (rs.Span.Length != 3) return 31;
|
||||||
|
if (rs.Span[0] != 10) return 32;
|
||||||
|
if (rs.Span[1] != 20) return 33;
|
||||||
|
if (rs.Span[2] != 30) return 34;
|
||||||
|
|
||||||
|
// Modify through span
|
||||||
|
rs.Span[0] = 100;
|
||||||
|
if (rs.Span[0] != 100) return 35;
|
||||||
|
|
||||||
|
// Test readonly struct
|
||||||
|
var ros = new ReadOnlyStruct(5, 7);
|
||||||
|
if (ros.X != 5) return 36;
|
||||||
|
if (ros.Y != 7) return 37;
|
||||||
|
if (ros.Sum != 12) return 38;
|
||||||
|
|
||||||
|
// Verify immutability - create new instance
|
||||||
|
var ros2 = new ReadOnlyStruct(10, 20);
|
||||||
|
if (ros.X != 5) return 39; // Original should be unchanged
|
||||||
|
|
||||||
|
// Test readonly ref struct
|
||||||
|
byte[] data = { 1, 2, 3, 4 };
|
||||||
|
var rors = new ReadOnlyRefStruct(42, new ReadOnlySpan<byte>(data));
|
||||||
|
if (rors.Value != 42) return 40;
|
||||||
|
if (rors.Data.Length != 4) return 41;
|
||||||
|
if (rors.Data[3] != 4) return 42;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestGenerics()
|
||||||
|
{
|
||||||
|
// Test single generic parameter
|
||||||
|
var g1 = new Generic<int>(42, 1);
|
||||||
|
if (g1.Value != 42) return 50;
|
||||||
|
if (g1.Index != 1) return 51;
|
||||||
|
|
||||||
|
var g2 = new Generic<double>(3.14, 2);
|
||||||
|
if (Math.Abs(g2.Value - 3.14) > 0.00001) return 52;
|
||||||
|
if (g2.Index != 2) return 53;
|
||||||
|
|
||||||
|
// Test with custom struct
|
||||||
|
var inner = new ReadOnlyStruct(10, 20);
|
||||||
|
var g3 = new Generic<ReadOnlyStruct>(inner, 3);
|
||||||
|
if (g3.Value.X != 10) return 54;
|
||||||
|
if (g3.Value.Y != 20) return 55;
|
||||||
|
if (g3.Index != 3) return 56;
|
||||||
|
|
||||||
|
// Test double generic
|
||||||
|
var dg = new DoubleGeneric<int, string> { First = 100, Second = "test" };
|
||||||
|
if (dg.First != 100) return 57;
|
||||||
|
if (dg.Second != "test") return 58;
|
||||||
|
|
||||||
|
// Test with different type combinations
|
||||||
|
var dg2 = new DoubleGeneric<double, long> { First = 2.718, Second = long.MaxValue };
|
||||||
|
if (Math.Abs(dg2.First - 2.718) > 0.00001) return 59;
|
||||||
|
if (dg2.Second != long.MaxValue) return 60;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestByRefReturns()
|
||||||
|
{
|
||||||
|
var r = new RefReturnStruct { A = 10, B = 20, C = 30 };
|
||||||
|
|
||||||
|
// Test ref return
|
||||||
|
ref int refA = ref r.GetRef(0);
|
||||||
|
if (refA != 10) return 70;
|
||||||
|
|
||||||
|
// Modify through ref
|
||||||
|
refA = 100;
|
||||||
|
if (r.A != 100) return 71;
|
||||||
|
|
||||||
|
ref int refB = ref r.GetRef(1);
|
||||||
|
refB = 200;
|
||||||
|
if (r.B != 200) return 72;
|
||||||
|
|
||||||
|
ref int refC = ref r.GetRef(2);
|
||||||
|
refC = 300;
|
||||||
|
if (r.C != 300) return 73;
|
||||||
|
|
||||||
|
// Test ref local
|
||||||
|
ref int localRef = ref r.A;
|
||||||
|
localRef = 1000;
|
||||||
|
if (r.A != 1000) return 74;
|
||||||
|
|
||||||
|
// Test that ref points to actual field
|
||||||
|
localRef = 2000;
|
||||||
|
if (refA != 2000) return 75; // Both should see the change
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestStructInterfaces()
|
||||||
|
{
|
||||||
|
// Test struct implementing interface
|
||||||
|
var s = new StructWithInterface { Index = 42, Data = "test" };
|
||||||
|
if (s.GetIndex() != 42) return 80;
|
||||||
|
|
||||||
|
s.SetIndex(100);
|
||||||
|
if (s.Index != 100) return 81;
|
||||||
|
|
||||||
|
// Test boxing to interface
|
||||||
|
IIndexable boxed = s; // Boxing occurs here
|
||||||
|
if (boxed.GetIndex() != 100) return 82;
|
||||||
|
|
||||||
|
// Modify through interface (modifies boxed copy)
|
||||||
|
boxed.SetIndex(200);
|
||||||
|
if (boxed.GetIndex() != 200) return 83;
|
||||||
|
if (s.Index != 100) return 84; // Original should be unchanged
|
||||||
|
|
||||||
|
// Test mutable interface
|
||||||
|
var m = new MutableStruct { Counter = 0 };
|
||||||
|
m.Mutate();
|
||||||
|
if (m.Counter != 1) return 85;
|
||||||
|
|
||||||
|
// Box to interface and mutate
|
||||||
|
IMutable boxedMutable = m; // Boxing
|
||||||
|
boxedMutable.Mutate();
|
||||||
|
if (m.Counter != 1) return 86; // Original unchanged
|
||||||
|
|
||||||
|
// Cast back to see boxed mutation
|
||||||
|
var unboxed = (MutableStruct)boxedMutable;
|
||||||
|
if (unboxed.Counter != 2) return 87;
|
||||||
|
|
||||||
|
// Direct interface call on boxed struct maintains state
|
||||||
|
boxedMutable.Mutate();
|
||||||
|
boxedMutable.Mutate();
|
||||||
|
var unboxed2 = (MutableStruct)boxedMutable;
|
||||||
|
if (unboxed2.Counter != 4) return 88;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsafe int TestCombinedScenarios()
|
||||||
|
{
|
||||||
|
// Test generic with fixed buffer struct
|
||||||
|
var f = new FixedBufferStruct();
|
||||||
|
f.Header = 999;
|
||||||
|
f.Buffer[0] = 123;
|
||||||
|
f.Footer = 111;
|
||||||
|
|
||||||
|
var generic = new Generic<FixedBufferStruct>(f, 42);
|
||||||
|
if (generic.Value.Header != 999) return 90;
|
||||||
|
if (generic.Value.Buffer[0] != 123) return 91;
|
||||||
|
if (generic.Value.Footer != 111) return 92;
|
||||||
|
if (generic.Index != 42) return 93;
|
||||||
|
|
||||||
|
// Test marshaling with generic
|
||||||
|
var marshalable = new BlittableStruct { X = 10, Y = 20.0, Z = 30 };
|
||||||
|
var genericMarshal = new Generic<BlittableStruct>(marshalable, 5);
|
||||||
|
|
||||||
|
if (genericMarshal.Value.X != 10) return 94;
|
||||||
|
if (Math.Abs(genericMarshal.Value.Y - 20.0) > 0.00001) return 95;
|
||||||
|
if (genericMarshal.Value.Z != 30) return 96;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Main()
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
result = TestUnsafePointers();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestFixedBuffers();
|
||||||
|
if (result != 0) return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = TestMarshaling();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestRefStructs();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestGenerics();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestByRefReturns();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestStructInterfaces();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
result = TestCombinedScenarios();
|
||||||
|
if (result != 0) return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; // All tests passed
|
||||||
|
}
|
||||||
|
}
|
364
WoofWare.PawPrint.Test/sourcesPure/OverlappingStructs.cs
Normal file
364
WoofWare.PawPrint.Test/sourcesPure/OverlappingStructs.cs
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
public class StructLayoutTests
|
||||||
|
{
|
||||||
|
// Test structs with various layouts
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
struct SequentialStruct
|
||||||
|
{
|
||||||
|
public int A;
|
||||||
|
public byte B;
|
||||||
|
public long C;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
struct ExplicitUnion
|
||||||
|
{
|
||||||
|
[FieldOffset(0)] public int AsInt;
|
||||||
|
[FieldOffset(0)] public float AsFloat;
|
||||||
|
[FieldOffset(0)] public byte Byte0;
|
||||||
|
[FieldOffset(1)] public byte Byte1;
|
||||||
|
[FieldOffset(2)] public byte Byte2;
|
||||||
|
[FieldOffset(3)] public byte Byte3;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 16)]
|
||||||
|
struct FixedSizeStruct
|
||||||
|
{
|
||||||
|
[FieldOffset(0)] public long First;
|
||||||
|
[FieldOffset(8)] public int Second;
|
||||||
|
[FieldOffset(12)] public short Third;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
struct PackedStruct
|
||||||
|
{
|
||||||
|
public byte A;
|
||||||
|
public int B;
|
||||||
|
public byte C;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Auto)]
|
||||||
|
struct AutoLayoutStruct
|
||||||
|
{
|
||||||
|
public int X;
|
||||||
|
public string Y;
|
||||||
|
public double Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
struct NestedUnion
|
||||||
|
{
|
||||||
|
[FieldOffset(0)] public ExplicitUnion Inner;
|
||||||
|
[FieldOffset(0)] public long AsLong;
|
||||||
|
[FieldOffset(4)] public int UpperInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
struct LargeUnion
|
||||||
|
{
|
||||||
|
[FieldOffset(0)] public long Long1;
|
||||||
|
[FieldOffset(8)] public long Long2;
|
||||||
|
[FieldOffset(0)] public double Double1;
|
||||||
|
[FieldOffset(8)] public double Double2;
|
||||||
|
[FieldOffset(0)] public decimal AsDecimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static fields for testing
|
||||||
|
static SequentialStruct staticSequential;
|
||||||
|
static ExplicitUnion staticUnion;
|
||||||
|
static FixedSizeStruct staticFixed;
|
||||||
|
|
||||||
|
// Instance fields for testing
|
||||||
|
class FieldContainer
|
||||||
|
{
|
||||||
|
public SequentialStruct instanceSequential;
|
||||||
|
public ExplicitUnion instanceUnion;
|
||||||
|
public PackedStruct instancePacked;
|
||||||
|
public NestedUnion instanceNested;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestSequentialLayout()
|
||||||
|
{
|
||||||
|
var s = new SequentialStruct { A = 42, B = 255, C = long.MaxValue };
|
||||||
|
|
||||||
|
// Test field access
|
||||||
|
if (s.A != 42) return 1;
|
||||||
|
if (s.B != 255) return 2;
|
||||||
|
if (s.C != long.MaxValue) return 3;
|
||||||
|
|
||||||
|
// Test copy semantics
|
||||||
|
var s2 = s;
|
||||||
|
s2.A = 100;
|
||||||
|
if (s.A != 42) return 4; // Should be unchanged (value type)
|
||||||
|
if (s2.A != 100) return 5;
|
||||||
|
|
||||||
|
// Test static field storage
|
||||||
|
staticSequential = s;
|
||||||
|
if (staticSequential.A != 42) return 6;
|
||||||
|
if (staticSequential.C != long.MaxValue) return 7;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestExplicitUnion()
|
||||||
|
{
|
||||||
|
var u = new ExplicitUnion();
|
||||||
|
|
||||||
|
// Test overlapping int/float
|
||||||
|
u.AsInt = 0x3F800000; // IEEE 754 representation of 1.0f
|
||||||
|
if (Math.Abs(u.AsFloat - 1.0f) > 0.0001f) return 10;
|
||||||
|
|
||||||
|
// Test byte-level access
|
||||||
|
u.AsInt = 0x12345678;
|
||||||
|
bool isLittleEndian = BitConverter.IsLittleEndian;
|
||||||
|
if (isLittleEndian)
|
||||||
|
{
|
||||||
|
if (u.Byte0 != 0x78) return 11;
|
||||||
|
if (u.Byte1 != 0x56) return 12;
|
||||||
|
if (u.Byte2 != 0x34) return 13;
|
||||||
|
if (u.Byte3 != 0x12) return 14;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (u.Byte0 != 0x12) return 11;
|
||||||
|
if (u.Byte1 != 0x34) return 12;
|
||||||
|
if (u.Byte2 != 0x56) return 13;
|
||||||
|
if (u.Byte3 != 0x78) return 14;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test static field
|
||||||
|
staticUnion = u;
|
||||||
|
if (staticUnion.AsInt != 0x12345678) return 15;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestFixedSizeStruct()
|
||||||
|
{
|
||||||
|
var f = new FixedSizeStruct { First = -1, Second = 42, Third = 1000 };
|
||||||
|
|
||||||
|
if (f.First != -1) return 20;
|
||||||
|
if (f.Second != 42) return 21;
|
||||||
|
if (f.Third != 1000) return 22;
|
||||||
|
|
||||||
|
// Test size is respected
|
||||||
|
int size = Marshal.SizeOf(typeof(FixedSizeStruct));
|
||||||
|
if (size != 16) return 23;
|
||||||
|
|
||||||
|
staticFixed = f;
|
||||||
|
if (staticFixed.Second != 42) return 24;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestPackedStruct()
|
||||||
|
{
|
||||||
|
var p = new PackedStruct { A = 1, B = 0x12345678, C = 2 };
|
||||||
|
|
||||||
|
if (p.A != 1) return 30;
|
||||||
|
if (p.B != 0x12345678) return 31;
|
||||||
|
if (p.C != 2) return 32;
|
||||||
|
|
||||||
|
// Packed struct should be 6 bytes (1 + 4 + 1)
|
||||||
|
int size = Marshal.SizeOf(typeof(PackedStruct));
|
||||||
|
if (size != 6) return 33;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestInstanceFields()
|
||||||
|
{
|
||||||
|
var container = new FieldContainer();
|
||||||
|
|
||||||
|
container.instanceSequential = new SequentialStruct { A = 111, B = 222, C = 333 };
|
||||||
|
if (container.instanceSequential.A != 111) return 40;
|
||||||
|
|
||||||
|
container.instanceUnion = new ExplicitUnion { AsInt = 0xDEADBEEF };
|
||||||
|
if (container.instanceUnion.AsInt != unchecked((int)0xDEADBEEF)) return 41;
|
||||||
|
|
||||||
|
container.instancePacked = new PackedStruct { A = 10, B = 20, C = 30 };
|
||||||
|
if (container.instancePacked.B != 20) return 42;
|
||||||
|
|
||||||
|
container.instanceNested = new NestedUnion();
|
||||||
|
container.instanceNested.Inner.AsInt = 100;
|
||||||
|
if (container.instanceNested.Inner.AsInt != 100) return 43;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestStructPassing()
|
||||||
|
{
|
||||||
|
var s = new SequentialStruct { A = 500, B = 50, C = 5000 };
|
||||||
|
int result = ProcessSequential(s);
|
||||||
|
if (result != 555) return 50; // 500 + 50 + 5 (C % 1000)
|
||||||
|
|
||||||
|
var u = new ExplicitUnion { AsInt = 1000 };
|
||||||
|
u = TransformUnion(u);
|
||||||
|
if (u.AsInt != 2000) return 51;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ProcessSequential(SequentialStruct s)
|
||||||
|
{
|
||||||
|
return s.A + s.B + (int)(s.C % 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ExplicitUnion TransformUnion(ExplicitUnion u)
|
||||||
|
{
|
||||||
|
u.AsInt *= 2;
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestNestedUnion()
|
||||||
|
{
|
||||||
|
var n = new NestedUnion();
|
||||||
|
n.Inner.AsInt = 0x12345678;
|
||||||
|
|
||||||
|
// Lower 32 bits should match Inner.AsInt
|
||||||
|
if ((n.AsLong & 0xFFFFFFFF) != 0x12345678) return 60;
|
||||||
|
|
||||||
|
// Modify upper int
|
||||||
|
n.UpperInt = unchecked((int)0xABCDEF00);
|
||||||
|
|
||||||
|
// Check both parts
|
||||||
|
if (n.Inner.AsInt != 0x12345678) return 61;
|
||||||
|
if (n.UpperInt != unchecked((int)0xABCDEF00)) return 62;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestLargeUnion()
|
||||||
|
{
|
||||||
|
var l = new LargeUnion();
|
||||||
|
|
||||||
|
// Test double/long overlap
|
||||||
|
l.Double1 = 1.0;
|
||||||
|
l.Double2 = 2.0;
|
||||||
|
|
||||||
|
// IEEE 754: 1.0 = 0x3FF0000000000000
|
||||||
|
if (l.Long1 != 0x3FF0000000000000) return 70;
|
||||||
|
// IEEE 754: 2.0 = 0x4000000000000000
|
||||||
|
if (l.Long2 != 0x4000000000000000) return 71;
|
||||||
|
|
||||||
|
// Test decimal overlap (decimal is 128 bits)
|
||||||
|
l.AsDecimal = 42m;
|
||||||
|
// Just verify it doesn't crash and maintains some structure
|
||||||
|
if (l.AsDecimal != 42m) return 72;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestAutoLayout()
|
||||||
|
{
|
||||||
|
// Auto layout structs can't use FieldOffset, but we can still test basic functionality
|
||||||
|
var a = new AutoLayoutStruct { X = 100, Y = "test", Z = 3.14159 };
|
||||||
|
|
||||||
|
if (a.X != 100) return 80;
|
||||||
|
if (a.Y != "test") return 81;
|
||||||
|
if (Math.Abs(a.Z - 3.14159) > 0.00001) return 82;
|
||||||
|
|
||||||
|
// Test copy
|
||||||
|
var a2 = a;
|
||||||
|
a2.X = 200;
|
||||||
|
if (a.X != 100) return 83; // Original should be unchanged
|
||||||
|
if (a2.X != 200) return 84;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestStructArray()
|
||||||
|
{
|
||||||
|
var arr = new ExplicitUnion[3];
|
||||||
|
arr[0].AsInt = 10;
|
||||||
|
arr[1].AsInt = 20;
|
||||||
|
arr[2].AsInt = 30;
|
||||||
|
|
||||||
|
if (arr[0].AsInt != 10) return 90;
|
||||||
|
if (arr[1].AsInt != 20) return 91;
|
||||||
|
if (arr[2].AsInt != 30) return 92;
|
||||||
|
|
||||||
|
// Modify through float view
|
||||||
|
arr[1].AsFloat = 2.5f;
|
||||||
|
if (Math.Abs(arr[1].AsFloat - 2.5f) > 0.0001f) return 93;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestBoxingUnboxing()
|
||||||
|
{
|
||||||
|
ExplicitUnion u = new ExplicitUnion { AsInt = 999 };
|
||||||
|
object boxed = u; // Box
|
||||||
|
ExplicitUnion unboxed = (ExplicitUnion)boxed; // Unbox
|
||||||
|
|
||||||
|
if (unboxed.AsInt != 999) return 100;
|
||||||
|
|
||||||
|
// Modify original, boxed should remain unchanged
|
||||||
|
u.AsInt = 111;
|
||||||
|
ExplicitUnion fromBoxed = (ExplicitUnion)boxed;
|
||||||
|
if (fromBoxed.AsInt != 999) return 101; // Should still be 999
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int TestDefaultValues()
|
||||||
|
{
|
||||||
|
// Test that default struct initialization zeroes memory
|
||||||
|
var s = new SequentialStruct();
|
||||||
|
if (s.A != 0) return 110;
|
||||||
|
if (s.B != 0) return 111;
|
||||||
|
if (s.C != 0) return 112;
|
||||||
|
|
||||||
|
var u = new ExplicitUnion();
|
||||||
|
if (u.AsInt != 0) return 113;
|
||||||
|
if (u.AsFloat != 0.0f) return 114;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Main()
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
result = TestSequentialLayout();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestExplicitUnion();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestFixedSizeStruct();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestPackedStruct();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestInstanceFields();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestStructPassing();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestNestedUnion();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestLargeUnion();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestAutoLayout();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestStructArray();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestBoxingUnboxing();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
result = TestDefaultValues();
|
||||||
|
if (result != 0) return result;
|
||||||
|
|
||||||
|
return 0; // All tests passed
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,5 @@
|
|||||||
namespace WoofWare.PawPrint
|
namespace WoofWare.PawPrint
|
||||||
|
|
||||||
open System.Collections.Immutable
|
|
||||||
open Microsoft.Extensions.Logging
|
open Microsoft.Extensions.Logging
|
||||||
open Microsoft.FSharp.Core
|
open Microsoft.FSharp.Core
|
||||||
open WoofWare.PawPrint.ExternImplementations
|
open WoofWare.PawPrint.ExternImplementations
|
||||||
@@ -62,12 +61,12 @@ module AbstractMachine =
|
|||||||
let delegateToRun = state.ManagedHeap.NonArrayObjects.[delegateToRunAddr]
|
let delegateToRun = state.ManagedHeap.NonArrayObjects.[delegateToRunAddr]
|
||||||
|
|
||||||
let target =
|
let target =
|
||||||
match delegateToRun.Fields |> List.find (fun (x, _) -> x = "_target") |> snd with
|
match delegateToRun |> AllocatedNonArrayObject.DereferenceField "_target" with
|
||||||
| CliType.ObjectRef addr -> addr
|
| CliType.ObjectRef addr -> addr
|
||||||
| x -> failwith $"TODO: delegate target wasn't an object ref: %O{x}"
|
| x -> failwith $"TODO: delegate target wasn't an object ref: %O{x}"
|
||||||
|
|
||||||
let methodPtr =
|
let methodPtr =
|
||||||
match delegateToRun.Fields |> List.find (fun (x, _) -> x = "_methodPtr") |> snd with
|
match delegateToRun |> AllocatedNonArrayObject.DereferenceField "_methodPtr" with
|
||||||
| CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer mi)) -> mi
|
| CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer mi)) -> mi
|
||||||
| d -> failwith $"unexpectedly not a method pointer in delegate invocation: {d}"
|
| d -> failwith $"unexpectedly not a method pointer in delegate invocation: {d}"
|
||||||
|
|
||||||
|
@@ -160,11 +160,28 @@ type CliType =
|
|||||||
/// as a concatenated list of its fields.
|
/// as a concatenated list of its fields.
|
||||||
| ValueType of CliValueType
|
| ValueType of CliValueType
|
||||||
|
|
||||||
|
and CliField =
|
||||||
|
{
|
||||||
|
Name : string
|
||||||
|
Contents : CliType
|
||||||
|
/// "None" for "no explicit offset specified"; we expect most offsets to be None.
|
||||||
|
Offset : int option
|
||||||
|
}
|
||||||
|
|
||||||
and CliValueType =
|
and CliValueType =
|
||||||
{
|
{
|
||||||
Fields : (string * CliType) list
|
Fields : CliField list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static member OfFields (f : CliField list) =
|
||||||
|
{
|
||||||
|
Fields = f
|
||||||
|
}
|
||||||
|
|
||||||
|
static member DereferenceField (name : string) (f : CliValueType) : CliType =
|
||||||
|
// TODO: this is wrong, it doesn't account for overlapping fields
|
||||||
|
f.Fields |> List.find (fun f -> f.Name = name) |> _.Contents
|
||||||
|
|
||||||
type CliTypeResolutionResult =
|
type CliTypeResolutionResult =
|
||||||
| Resolved of CliType
|
| Resolved of CliType
|
||||||
| FirstLoad of WoofWare.PawPrint.AssemblyReference
|
| FirstLoad of WoofWare.PawPrint.AssemblyReference
|
||||||
@@ -200,10 +217,10 @@ module CliType =
|
|||||||
| CliType.ValueType vt ->
|
| CliType.ValueType vt ->
|
||||||
match vt.Fields with
|
match vt.Fields with
|
||||||
| [] -> failwith "is it even possible to instantiate a value type with no fields"
|
| [] -> failwith "is it even possible to instantiate a value type with no fields"
|
||||||
| [ _, f ] -> sizeOf f
|
| [ field ] -> sizeOf field.Contents
|
||||||
| fields ->
|
| fields ->
|
||||||
// TODO: consider struct layout (there's an `Explicit` test that will exercise that)
|
// TODO: consider struct layout (there's an `Explicit` test that will exercise that)
|
||||||
fields |> List.map (snd >> sizeOf) |> List.sum
|
fields |> List.map (_.Contents >> sizeOf) |> List.sum
|
||||||
|
|
||||||
let zeroOfPrimitive (primitiveType : PrimitiveType) : CliType =
|
let zeroOfPrimitive (primitiveType : PrimitiveType) : CliType =
|
||||||
match primitiveType with
|
match primitiveType with
|
||||||
@@ -227,19 +244,21 @@ module CliType =
|
|||||||
| PrimitiveType.TypedReference -> failwith "todo"
|
| PrimitiveType.TypedReference -> failwith "todo"
|
||||||
| PrimitiveType.IntPtr ->
|
| PrimitiveType.IntPtr ->
|
||||||
{
|
{
|
||||||
Fields =
|
Name = "_value"
|
||||||
[
|
Contents = CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
|
||||||
"_value", CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
|
Offset = Some 0
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|> List.singleton
|
||||||
|
|> CliValueType.OfFields
|
||||||
|> CliType.ValueType
|
|> CliType.ValueType
|
||||||
| PrimitiveType.UIntPtr ->
|
| PrimitiveType.UIntPtr ->
|
||||||
{
|
{
|
||||||
Fields =
|
Name = "_value"
|
||||||
[
|
Contents = CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
|
||||||
"_value", CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
|
Offset = Some 0
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|> List.singleton
|
||||||
|
|> CliValueType.OfFields
|
||||||
|> CliType.ValueType
|
|> CliType.ValueType
|
||||||
| PrimitiveType.Object -> CliType.ObjectRef None
|
| PrimitiveType.Object -> CliType.ObjectRef None
|
||||||
|
|
||||||
@@ -378,7 +397,7 @@ module CliType =
|
|||||||
// It's a value type - need to create zero values for all non-static fields
|
// It's a value type - need to create zero values for all non-static fields
|
||||||
let mutable currentConcreteTypes = concreteTypes
|
let mutable currentConcreteTypes = concreteTypes
|
||||||
|
|
||||||
let fieldZeros =
|
let vt =
|
||||||
typeDef.Fields
|
typeDef.Fields
|
||||||
|> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static))
|
|> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static))
|
||||||
|> List.map (fun field ->
|
|> List.map (fun field ->
|
||||||
@@ -394,13 +413,14 @@ module CliType =
|
|||||||
zeroOfWithVisited currentConcreteTypes assemblies corelib fieldHandle visited
|
zeroOfWithVisited currentConcreteTypes assemblies corelib fieldHandle visited
|
||||||
|
|
||||||
currentConcreteTypes <- updatedConcreteTypes2
|
currentConcreteTypes <- updatedConcreteTypes2
|
||||||
(field.Name, fieldZero)
|
|
||||||
)
|
|
||||||
|
|
||||||
let vt =
|
{
|
||||||
{
|
Name = field.Name
|
||||||
Fields = fieldZeros
|
Contents = fieldZero
|
||||||
}
|
Offset = field.Offset
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> CliValueType.OfFields
|
||||||
|
|
||||||
CliType.ValueType vt, currentConcreteTypes
|
CliType.ValueType vt, currentConcreteTypes
|
||||||
else
|
else
|
||||||
@@ -465,8 +485,14 @@ module CliType =
|
|||||||
{
|
{
|
||||||
Fields =
|
Fields =
|
||||||
cvt.Fields
|
cvt.Fields
|
||||||
|> List.replaceWhere (fun (fieldName, _existing) ->
|
|> List.replaceWhere (fun f ->
|
||||||
if fieldName = field then Some (fieldName, value) else None
|
if f.Name = field then
|
||||||
|
{ f with
|
||||||
|
Contents = value
|
||||||
|
}
|
||||||
|
|> Some
|
||||||
|
else
|
||||||
|
None
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|> CliType.ValueType
|
|> CliType.ValueType
|
||||||
@@ -478,4 +504,6 @@ module CliType =
|
|||||||
| CliType.Char (high, low) -> failwith "todo"
|
| CliType.Char (high, low) -> failwith "todo"
|
||||||
| CliType.ObjectRef managedHeapAddressOption -> failwith "todo"
|
| CliType.ObjectRef managedHeapAddressOption -> failwith "todo"
|
||||||
| CliType.RuntimePointer cliRuntimePointer -> failwith "todo"
|
| CliType.RuntimePointer cliRuntimePointer -> failwith "todo"
|
||||||
| CliType.ValueType cvt -> cvt.Fields |> List.pick (fun (n, v) -> if n = field then Some v else None)
|
| CliType.ValueType cvt ->
|
||||||
|
cvt.Fields
|
||||||
|
|> List.pick (fun f -> if f.Name = field then Some f.Contents else None)
|
||||||
|
10
WoofWare.PawPrint/Constants.fs
Normal file
10
WoofWare.PawPrint/Constants.fs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace WoofWare.PawPrint
|
||||||
|
|
||||||
|
[<AutoOpen>]
|
||||||
|
module Constants =
|
||||||
|
|
||||||
|
[<Literal>]
|
||||||
|
let SIZEOF_INT = 4
|
||||||
|
|
||||||
|
[<Literal>]
|
||||||
|
let SIZEOF_OBJ = 8
|
@@ -10,8 +10,7 @@ type EvalStackValue =
|
|||||||
| ObjectRef of ManagedHeapAddress
|
| ObjectRef of ManagedHeapAddress
|
||||||
// Fraser thinks this isn't really a thing in CoreCLR
|
// Fraser thinks this isn't really a thing in CoreCLR
|
||||||
// | TransientPointer of TransientPointerSource
|
// | TransientPointer of TransientPointerSource
|
||||||
/// Mapping of field name to value
|
| UserDefinedValueType of EvalStackValueUserType
|
||||||
| UserDefinedValueType of (string * EvalStackValue) list
|
|
||||||
|
|
||||||
override this.ToString () =
|
override this.ToString () =
|
||||||
match this with
|
match this with
|
||||||
@@ -23,12 +22,39 @@ type EvalStackValue =
|
|||||||
| EvalStackValue.ObjectRef managedHeapAddress -> $"ObjectRef(%O{managedHeapAddress})"
|
| EvalStackValue.ObjectRef managedHeapAddress -> $"ObjectRef(%O{managedHeapAddress})"
|
||||||
| EvalStackValue.UserDefinedValueType evalStackValues ->
|
| EvalStackValue.UserDefinedValueType evalStackValues ->
|
||||||
let desc =
|
let desc =
|
||||||
evalStackValues
|
evalStackValues.Fields
|
||||||
|> List.map (snd >> string<EvalStackValue>)
|
|> List.map (_.ContentsEval >> string<EvalStackValue>)
|
||||||
|> String.concat " | "
|
|> String.concat " | "
|
||||||
|
|
||||||
$"Struct(%s{desc})"
|
$"Struct(%s{desc})"
|
||||||
|
|
||||||
|
and EvalStackValueField =
|
||||||
|
{
|
||||||
|
Name : string
|
||||||
|
ContentsEval : EvalStackValue
|
||||||
|
Offset : int option
|
||||||
|
}
|
||||||
|
|
||||||
|
and EvalStackValueUserType =
|
||||||
|
{
|
||||||
|
Fields : EvalStackValueField list
|
||||||
|
}
|
||||||
|
|
||||||
|
static member DereferenceField (name : string) (this : EvalStackValueUserType) =
|
||||||
|
// TODO: this doesn't account for overlapping fields
|
||||||
|
this.Fields
|
||||||
|
|> List.pick (fun stackField ->
|
||||||
|
if stackField.Name = name then
|
||||||
|
Some stackField.ContentsEval
|
||||||
|
else
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
static member OfFields (fields : EvalStackValueField list) =
|
||||||
|
{
|
||||||
|
Fields = fields
|
||||||
|
}
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module EvalStackValue =
|
module EvalStackValue =
|
||||||
/// The conversion performed by Conv_u.
|
/// The conversion performed by Conv_u.
|
||||||
@@ -107,7 +133,11 @@ module EvalStackValue =
|
|||||||
| CliNumericType.Int32 _ ->
|
| CliNumericType.Int32 _ ->
|
||||||
match popped with
|
match popped with
|
||||||
| EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.Int32 i)
|
| EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.Int32 i)
|
||||||
| EvalStackValue.UserDefinedValueType [ popped ] -> toCliTypeCoerced target (snd popped)
|
| EvalStackValue.UserDefinedValueType popped ->
|
||||||
|
match popped.Fields with
|
||||||
|
| [] -> failwith "unexpectedly empty"
|
||||||
|
| [ popped ] -> toCliTypeCoerced target popped.ContentsEval
|
||||||
|
| _ -> failwith $"TODO: %O{target}"
|
||||||
| i -> failwith $"TODO: %O{i}"
|
| i -> failwith $"TODO: %O{i}"
|
||||||
| CliNumericType.Int64 _ ->
|
| CliNumericType.Int64 _ ->
|
||||||
match popped with
|
match popped with
|
||||||
@@ -127,7 +157,11 @@ module EvalStackValue =
|
|||||||
| EvalStackValue.ManagedPointer ptrSrc ->
|
| EvalStackValue.ManagedPointer ptrSrc ->
|
||||||
CliNumericType.NativeInt (NativeIntSource.ManagedPointer ptrSrc)
|
CliNumericType.NativeInt (NativeIntSource.ManagedPointer ptrSrc)
|
||||||
|> CliType.Numeric
|
|> CliType.Numeric
|
||||||
| EvalStackValue.UserDefinedValueType [ (_, t) ] -> toCliTypeCoerced target t
|
| EvalStackValue.UserDefinedValueType vt ->
|
||||||
|
match vt.Fields with
|
||||||
|
| [] -> failwith "unexpected"
|
||||||
|
| [ vt ] -> toCliTypeCoerced target vt.ContentsEval
|
||||||
|
| _ -> failwith $"TODO: {popped}"
|
||||||
| _ -> failwith $"TODO: {popped}"
|
| _ -> failwith $"TODO: {popped}"
|
||||||
| CliNumericType.NativeFloat f -> failwith "todo"
|
| CliNumericType.NativeFloat f -> failwith "todo"
|
||||||
| CliNumericType.Int8 _ ->
|
| CliNumericType.Int8 _ ->
|
||||||
@@ -175,9 +209,9 @@ module EvalStackValue =
|
|||||||
| ManagedPointerSource.Null -> CliType.ObjectRef None
|
| ManagedPointerSource.Null -> CliType.ObjectRef None
|
||||||
| ManagedPointerSource.Heap s -> CliType.ObjectRef (Some s)
|
| ManagedPointerSource.Heap s -> CliType.ObjectRef (Some s)
|
||||||
| _ -> failwith "TODO"
|
| _ -> failwith "TODO"
|
||||||
| EvalStackValue.UserDefinedValueType fields ->
|
| EvalStackValue.UserDefinedValueType obj ->
|
||||||
match fields with
|
match obj.Fields with
|
||||||
| [ esv ] -> toCliTypeCoerced target (snd esv)
|
| [ esv ] -> toCliTypeCoerced target esv.ContentsEval
|
||||||
| fields -> failwith $"TODO: don't know how to coerce struct of {fields} to a pointer"
|
| fields -> failwith $"TODO: don't know how to coerce struct of {fields} to a pointer"
|
||||||
| _ -> failwith $"TODO: {popped}"
|
| _ -> failwith $"TODO: {popped}"
|
||||||
| CliType.Bool _ ->
|
| CliType.Bool _ ->
|
||||||
@@ -219,28 +253,32 @@ module EvalStackValue =
|
|||||||
| CliType.ValueType vt ->
|
| CliType.ValueType vt ->
|
||||||
match popped with
|
match popped with
|
||||||
| EvalStackValue.UserDefinedValueType popped ->
|
| EvalStackValue.UserDefinedValueType popped ->
|
||||||
if vt.Fields.Length <> popped.Length then
|
if vt.Fields.Length <> popped.Fields.Length then
|
||||||
|
// TODO: overlapping fields
|
||||||
failwith
|
failwith
|
||||||
$"mismatch: popped value type {popped} (length %i{popped.Length}) into {vt} (length %i{vt.Fields.Length})"
|
$"mismatch: popped value type {popped} (length %i{popped.Fields.Length}) into {vt} (length %i{vt.Fields.Length})"
|
||||||
|
|
||||||
let fields =
|
(vt.Fields, popped.Fields)
|
||||||
List.map2
|
||> List.map2 (fun field1 popped ->
|
||||||
(fun (name1, v1) (name2, v2) ->
|
if field1.Name <> popped.Name then
|
||||||
if name1 <> name2 then
|
failwith $"TODO: name mismatch, {field1.Name} vs {popped.Name}"
|
||||||
failwith $"TODO: name mismatch, {name1} vs {name2}"
|
|
||||||
|
|
||||||
name1, toCliTypeCoerced v1 v2
|
if field1.Offset <> popped.Offset then
|
||||||
)
|
failwith $"TODO: offset mismatch for {field1.Name}, {field1.Offset} vs {popped.Offset}"
|
||||||
vt.Fields
|
|
||||||
popped
|
|
||||||
|
|
||||||
{
|
let contents = toCliTypeCoerced field1.Contents popped.ContentsEval
|
||||||
Fields = fields
|
|
||||||
}
|
{
|
||||||
|
CliField.Name = field1.Name
|
||||||
|
Contents = contents
|
||||||
|
Offset = field1.Offset
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> CliValueType.OfFields
|
||||||
|> CliType.ValueType
|
|> CliType.ValueType
|
||||||
| popped ->
|
| popped ->
|
||||||
match vt.Fields with
|
match vt.Fields with
|
||||||
| [ _, target ] -> toCliTypeCoerced target popped
|
| [ field ] -> toCliTypeCoerced field.Contents popped
|
||||||
| _ -> failwith $"TODO: {popped} into value type {target}"
|
| _ -> failwith $"TODO: {popped} into value type {target}"
|
||||||
|
|
||||||
let rec ofCliType (v : CliType) : EvalStackValue =
|
let rec ofCliType (v : CliType) : EvalStackValue =
|
||||||
@@ -285,8 +323,18 @@ module EvalStackValue =
|
|||||||
ManagedPointerSource.Field (failwith "TODO", fieldName)
|
ManagedPointerSource.Field (failwith "TODO", fieldName)
|
||||||
|> EvalStackValue.ManagedPointer
|
|> EvalStackValue.ManagedPointer
|
||||||
| CliType.ValueType fields ->
|
| CliType.ValueType fields ->
|
||||||
|
// TODO: this is a bit dubious; we're being a bit sloppy with possibly-overlapping fields here
|
||||||
fields.Fields
|
fields.Fields
|
||||||
|> List.map (fun (name, f) -> name, ofCliType f)
|
|> List.map (fun field ->
|
||||||
|
let contents = ofCliType field.Contents
|
||||||
|
|
||||||
|
{
|
||||||
|
Name = field.Name
|
||||||
|
Offset = field.Offset
|
||||||
|
ContentsEval = contents
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> EvalStackValueUserType.OfFields
|
||||||
|> EvalStackValue.UserDefinedValueType
|
|> EvalStackValue.UserDefinedValueType
|
||||||
|
|
||||||
type EvalStack =
|
type EvalStack =
|
||||||
|
@@ -123,11 +123,24 @@ module EvalStackValueComparisons =
|
|||||||
let rec ceq (var1 : EvalStackValue) (var2 : EvalStackValue) : bool =
|
let rec ceq (var1 : EvalStackValue) (var2 : EvalStackValue) : bool =
|
||||||
// Table III.4
|
// Table III.4
|
||||||
match var1, var2 with
|
match var1, var2 with
|
||||||
| EvalStackValue.UserDefinedValueType [ _, u ], v -> ceq u v
|
| EvalStackValue.UserDefinedValueType {
|
||||||
| u, EvalStackValue.UserDefinedValueType [ _, v ] -> ceq u v
|
Fields = [ f ]
|
||||||
| EvalStackValue.UserDefinedValueType [], EvalStackValue.UserDefinedValueType [] -> true
|
},
|
||||||
|
v -> ceq f.ContentsEval v
|
||||||
|
| u,
|
||||||
|
EvalStackValue.UserDefinedValueType {
|
||||||
|
Fields = [ f ]
|
||||||
|
} -> ceq u f.ContentsEval
|
||||||
|
| EvalStackValue.UserDefinedValueType {
|
||||||
|
Fields = []
|
||||||
|
},
|
||||||
|
EvalStackValue.UserDefinedValueType {
|
||||||
|
Fields = []
|
||||||
|
} ->
|
||||||
|
// hmm, surely this can't happen, but :shrug:
|
||||||
|
true
|
||||||
| EvalStackValue.UserDefinedValueType _, _
|
| EvalStackValue.UserDefinedValueType _, _
|
||||||
| _, EvalStackValue.UserDefinedValueType _ -> failwith $"bad ceq: {var1} vs {var2}"
|
| _, EvalStackValue.UserDefinedValueType _ -> failwith $"TODO: ceq {var1} vs {var2}"
|
||||||
| EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> var1 = var2
|
| EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> var1 = var2
|
||||||
| EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 -> failwith "TODO: int32 CEQ nativeint"
|
| EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 -> failwith "TODO: int32 CEQ nativeint"
|
||||||
| EvalStackValue.Int32 _, _ -> failwith $"bad ceq: Int32 vs {var2}"
|
| EvalStackValue.Int32 _, _ -> failwith $"bad ceq: Int32 vs {var2}"
|
||||||
|
@@ -34,7 +34,7 @@ module FieldHandleRegistry =
|
|||||||
let getOrAllocate
|
let getOrAllocate
|
||||||
(baseClassTypes : BaseClassTypes<'corelib>)
|
(baseClassTypes : BaseClassTypes<'corelib>)
|
||||||
(allocState : 'allocState)
|
(allocState : 'allocState)
|
||||||
(allocate : (string * CliType) list -> 'allocState -> ManagedHeapAddress * 'allocState)
|
(allocate : CliField list -> 'allocState -> ManagedHeapAddress * 'allocState)
|
||||||
(declaringAssy : AssemblyName)
|
(declaringAssy : AssemblyName)
|
||||||
(declaringType : ConcreteTypeHandle)
|
(declaringType : ConcreteTypeHandle)
|
||||||
(handle : FieldDefinitionHandle)
|
(handle : FieldDefinitionHandle)
|
||||||
@@ -54,8 +54,12 @@ module FieldHandleRegistry =
|
|||||||
failwith $"unexpected field name %s{field.Name} for BCL type RuntimeFieldHandle"
|
failwith $"unexpected field name %s{field.Name} for BCL type RuntimeFieldHandle"
|
||||||
|
|
||||||
{
|
{
|
||||||
Fields = [ "m_ptr", CliType.ofManagedObject runtimeFieldInfoStub ]
|
Name = "m_ptr"
|
||||||
|
Contents = CliType.ofManagedObject runtimeFieldInfoStub
|
||||||
|
Offset = None
|
||||||
}
|
}
|
||||||
|
|> List.singleton
|
||||||
|
|> CliValueType.OfFields
|
||||||
|> CliType.ValueType
|
|> CliType.ValueType
|
||||||
|
|
||||||
let handle =
|
let handle =
|
||||||
@@ -81,21 +85,52 @@ module FieldHandleRegistry =
|
|||||||
| TypeDefn.PrimitiveType PrimitiveType.IntPtr -> ()
|
| TypeDefn.PrimitiveType PrimitiveType.IntPtr -> ()
|
||||||
| s -> failwith $"bad sig: {s}"
|
| s -> failwith $"bad sig: {s}"
|
||||||
|
|
||||||
|
// https://github.com/dotnet/runtime/blob/2b21c73fa2c32fa0195e4a411a435dda185efd08/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1380
|
||||||
{
|
{
|
||||||
Fields = [ "m_handle", CliType.RuntimePointer (CliRuntimePointer.Unmanaged newHandle) ]
|
Name = "m_handle"
|
||||||
|
Contents = CliType.RuntimePointer (CliRuntimePointer.Unmanaged newHandle)
|
||||||
|
Offset = None // no struct layout was specified
|
||||||
}
|
}
|
||||||
|
|> List.singleton
|
||||||
|
|> CliValueType.OfFields
|
||||||
|> CliType.ValueType
|
|> CliType.ValueType
|
||||||
|
|
||||||
|
// https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1074
|
||||||
let runtimeFieldInfoStub =
|
let runtimeFieldInfoStub =
|
||||||
|
// LayoutKind.Sequential
|
||||||
[
|
[
|
||||||
// If we ever implement a GC, something should change here
|
// If we ever implement a GC, something should change here
|
||||||
"m_keepalive", CliType.ObjectRef None
|
{
|
||||||
"m_c", CliType.ObjectRef None
|
Name = "m_keepalive"
|
||||||
"m_d", CliType.ObjectRef None
|
Contents = CliType.ObjectRef None
|
||||||
"m_b", CliType.Numeric (CliNumericType.Int32 0)
|
Offset = Some 0
|
||||||
"m_e", CliType.ObjectRef None
|
}
|
||||||
|
{
|
||||||
|
Name = "m_c"
|
||||||
|
Contents = CliType.ObjectRef None
|
||||||
|
Offset = Some SIZEOF_OBJ
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Name = "m_d"
|
||||||
|
Contents = CliType.ObjectRef None
|
||||||
|
Offset = Some (SIZEOF_OBJ * 2)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Name = "m_b"
|
||||||
|
Contents = CliType.Numeric (CliNumericType.Int32 0)
|
||||||
|
Offset = Some (SIZEOF_OBJ * 3)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Name = "m_e"
|
||||||
|
Contents = CliType.ObjectRef None
|
||||||
|
Offset = Some (SIZEOF_OBJ * 3 + SIZEOF_INT)
|
||||||
|
}
|
||||||
// RuntimeFieldHandleInternal: https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1048
|
// RuntimeFieldHandleInternal: https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1048
|
||||||
"m_fieldHandle", runtimeFieldHandleInternal
|
{
|
||||||
|
Name = "m_fieldHandle"
|
||||||
|
Contents = runtimeFieldHandleInternal
|
||||||
|
Offset = Some (SIZEOF_OBJ * 4 + SIZEOF_INT)
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
let alloc, state = allocate runtimeFieldInfoStub allocState
|
let alloc, state = allocate runtimeFieldInfoStub allocState
|
||||||
|
@@ -785,7 +785,7 @@ module IlMachineState =
|
|||||||
| ResolvedBaseType.ValueType ->
|
| ResolvedBaseType.ValueType ->
|
||||||
let vt =
|
let vt =
|
||||||
{
|
{
|
||||||
Fields = constructed.Fields
|
CliValueType.Fields = constructed.Fields
|
||||||
}
|
}
|
||||||
|
|
||||||
state
|
state
|
||||||
@@ -1165,7 +1165,7 @@ module IlMachineState =
|
|||||||
|
|
||||||
let allocateManagedObject<'generic, 'field>
|
let allocateManagedObject<'generic, 'field>
|
||||||
(typeInfo : WoofWare.PawPrint.TypeInfo<'generic, 'field>)
|
(typeInfo : WoofWare.PawPrint.TypeInfo<'generic, 'field>)
|
||||||
(fields : (string * CliType) list)
|
(fields : CliField list)
|
||||||
(state : IlMachineState)
|
(state : IlMachineState)
|
||||||
: ManagedHeapAddress * IlMachineState
|
: ManagedHeapAddress * IlMachineState
|
||||||
=
|
=
|
||||||
@@ -1484,9 +1484,18 @@ module IlMachineState =
|
|||||||
// Standard delegate fields in .NET are _target and _methodPtr
|
// Standard delegate fields in .NET are _target and _methodPtr
|
||||||
// Update the fields with the target object and method pointer
|
// Update the fields with the target object and method pointer
|
||||||
let updatedFields =
|
let updatedFields =
|
||||||
// TODO: field ordering here is probably wrong
|
// Let's not consider field ordering for reference types like delegates.
|
||||||
("_target", CliType.ObjectRef targetObj)
|
// Nobody's going to be marshalling a reference type anyway, I hope.
|
||||||
:: ("_methodPtr", methodPtr)
|
{
|
||||||
|
Name = "_target"
|
||||||
|
Contents = CliType.ObjectRef targetObj
|
||||||
|
Offset = None
|
||||||
|
}
|
||||||
|
:: {
|
||||||
|
Name = "_methodPtr"
|
||||||
|
Contents = methodPtr
|
||||||
|
Offset = None
|
||||||
|
}
|
||||||
:: heapObj.Fields
|
:: heapObj.Fields
|
||||||
|
|
||||||
let updatedObj =
|
let updatedObj =
|
||||||
@@ -1602,5 +1611,5 @@ module IlMachineState =
|
|||||||
let obj = dereferencePointer state addr
|
let obj = dereferencePointer state addr
|
||||||
|
|
||||||
match obj with
|
match obj with
|
||||||
| CliType.ValueType vt -> vt.Fields |> Map.ofList |> Map.find name
|
| CliType.ValueType vt -> vt |> CliValueType.DereferenceField name
|
||||||
| v -> failwith $"could not find field {name} on object {v}"
|
| v -> failwith $"could not find field {name} on object {v}"
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
namespace WoofWare.PawPrint
|
namespace WoofWare.PawPrint
|
||||||
|
|
||||||
open System
|
open System
|
||||||
open System.Collections.Immutable
|
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Intrinsics =
|
module Intrinsics =
|
||||||
@@ -47,13 +46,15 @@ module Intrinsics =
|
|||||||
// https://github.com/dotnet/runtime/blob/108fa7856efcfd39bc991c2d849eabbf7ba5989c/src/coreclr/tools/Common/TypeSystem/IL/Stubs/UnsafeIntrinsics.cs#L192
|
// https://github.com/dotnet/runtime/blob/108fa7856efcfd39bc991c2d849eabbf7ba5989c/src/coreclr/tools/Common/TypeSystem/IL/Stubs/UnsafeIntrinsics.cs#L192
|
||||||
match methodToCall.DeclaringType.Assembly.Name, methodToCall.DeclaringType.Name, methodToCall.Name with
|
match methodToCall.DeclaringType.Assembly.Name, methodToCall.DeclaringType.Name, methodToCall.Name with
|
||||||
| "System.Private.CoreLib", "Type", "get_TypeHandle" ->
|
| "System.Private.CoreLib", "Type", "get_TypeHandle" ->
|
||||||
|
// https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/Type.cs#L470
|
||||||
|
|
||||||
// TODO: check return type is RuntimeTypeHandle
|
// TODO: check return type is RuntimeTypeHandle
|
||||||
match methodToCall.Signature.ParameterTypes with
|
match methodToCall.Signature.ParameterTypes with
|
||||||
| _ :: _ -> failwith "bad signature Type.get_TypeHandle"
|
| _ :: _ -> failwith "bad signature Type.get_TypeHandle"
|
||||||
| _ -> ()
|
| _ -> ()
|
||||||
|
|
||||||
// https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/Type.cs#L470
|
|
||||||
// no args, returns RuntimeTypeHandle, a struct with a single field (a RuntimeType class)
|
// no args, returns RuntimeTypeHandle, a struct with a single field (a RuntimeType class)
|
||||||
|
// https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L18
|
||||||
|
|
||||||
// The thing on top of the stack will be a RuntimeType.
|
// The thing on top of the stack will be a RuntimeType.
|
||||||
let arg, state = IlMachineState.popEvalStack currentThread state
|
let arg, state = IlMachineState.popEvalStack currentThread state
|
||||||
@@ -61,7 +62,10 @@ module Intrinsics =
|
|||||||
let arg =
|
let arg =
|
||||||
let rec go (arg : EvalStackValue) =
|
let rec go (arg : EvalStackValue) =
|
||||||
match arg with
|
match arg with
|
||||||
| EvalStackValue.UserDefinedValueType [ _, s ] -> go s
|
| EvalStackValue.UserDefinedValueType vt ->
|
||||||
|
match vt.Fields with
|
||||||
|
| [ field ] -> go field.ContentsEval
|
||||||
|
| _ -> failwith $"TODO: %O{vt}"
|
||||||
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE"
|
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE"
|
||||||
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> Some addr
|
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> Some addr
|
||||||
| s -> failwith $"TODO: called with unrecognised arg %O{s}"
|
| s -> failwith $"TODO: called with unrecognised arg %O{s}"
|
||||||
@@ -70,9 +74,14 @@ module Intrinsics =
|
|||||||
|
|
||||||
let state =
|
let state =
|
||||||
let vt =
|
let vt =
|
||||||
|
// https://github.com/dotnet/runtime/blob/2b21c73fa2c32fa0195e4a411a435dda185efd08/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L92
|
||||||
{
|
{
|
||||||
Fields = [ "m_type", CliType.ObjectRef arg ]
|
Name = "m_type"
|
||||||
|
Contents = CliType.ObjectRef arg
|
||||||
|
Offset = Some 0
|
||||||
}
|
}
|
||||||
|
|> List.singleton
|
||||||
|
|> CliValueType.OfFields
|
||||||
|
|
||||||
IlMachineState.pushToEvalStack (CliType.ValueType vt) currentThread state
|
IlMachineState.pushToEvalStack (CliType.ValueType vt) currentThread state
|
||||||
|> IlMachineState.advanceProgramCounter currentThread
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
@@ -217,9 +226,16 @@ module Intrinsics =
|
|||||||
| EvalStackValue.ManagedPointer src -> IlMachineState.dereferencePointer state src
|
| EvalStackValue.ManagedPointer src -> IlMachineState.dereferencePointer state src
|
||||||
| EvalStackValue.NativeInt src -> failwith "TODO"
|
| EvalStackValue.NativeInt src -> failwith "TODO"
|
||||||
| EvalStackValue.ObjectRef ptr -> failwith "TODO"
|
| EvalStackValue.ObjectRef ptr -> failwith "TODO"
|
||||||
| EvalStackValue.UserDefinedValueType [ _, field ] -> go field
|
| EvalStackValue.UserDefinedValueType {
|
||||||
| EvalStackValue.UserDefinedValueType []
|
Fields = [ f ]
|
||||||
| EvalStackValue.UserDefinedValueType (_ :: _ :: _)
|
} -> go f.ContentsEval
|
||||||
|
| EvalStackValue.UserDefinedValueType {
|
||||||
|
Fields = []
|
||||||
|
} -> failwith "unexpected no-fields object"
|
||||||
|
| EvalStackValue.UserDefinedValueType {
|
||||||
|
Fields = _ :: _ :: _
|
||||||
|
} ->
|
||||||
|
failwith "TODO: check overlapping fields to see if this is a pointer"
|
||||||
| EvalStackValue.Int32 _
|
| EvalStackValue.Int32 _
|
||||||
| EvalStackValue.Int64 _
|
| EvalStackValue.Int64 _
|
||||||
| EvalStackValue.Float _ -> failwith $"this isn't a pointer! {ptr}"
|
| EvalStackValue.Float _ -> failwith $"this isn't a pointer! {ptr}"
|
||||||
|
@@ -8,11 +8,15 @@ type SyncBlock =
|
|||||||
|
|
||||||
type AllocatedNonArrayObject =
|
type AllocatedNonArrayObject =
|
||||||
{
|
{
|
||||||
Fields : (string * CliType) list
|
Fields : CliField list
|
||||||
Type : WoofWare.PawPrint.TypeInfoCrate
|
Type : WoofWare.PawPrint.TypeInfoCrate
|
||||||
SyncBlock : SyncBlock
|
SyncBlock : SyncBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static member DereferenceField (name : string) (f : AllocatedNonArrayObject) : CliType =
|
||||||
|
// TODO: this is wrong, it doesn't account for overlapping fields
|
||||||
|
f.Fields |> List.find (fun f -> f.Name = name) |> _.Contents
|
||||||
|
|
||||||
type AllocatedArray =
|
type AllocatedArray =
|
||||||
{
|
{
|
||||||
Length : int
|
Length : int
|
||||||
|
@@ -18,7 +18,7 @@ module TypeHandleRegistry =
|
|||||||
/// Returns an allocated System.RuntimeType as well.
|
/// Returns an allocated System.RuntimeType as well.
|
||||||
let getOrAllocate
|
let getOrAllocate
|
||||||
(allocState : 'allocState)
|
(allocState : 'allocState)
|
||||||
(allocate : (string * CliType) list -> 'allocState -> ManagedHeapAddress * 'allocState)
|
(allocate : CliField list -> 'allocState -> ManagedHeapAddress * 'allocState)
|
||||||
(def : ConcreteTypeHandle)
|
(def : ConcreteTypeHandle)
|
||||||
(reg : TypeHandleRegistry)
|
(reg : TypeHandleRegistry)
|
||||||
: ManagedHeapAddress * TypeHandleRegistry * 'allocState
|
: ManagedHeapAddress * TypeHandleRegistry * 'allocState
|
||||||
@@ -29,16 +29,34 @@ module TypeHandleRegistry =
|
|||||||
|
|
||||||
// Here follows the class System.RuntimeType, which is an internal class type with a constructor
|
// Here follows the class System.RuntimeType, which is an internal class type with a constructor
|
||||||
// whose only purpose is to throw.
|
// whose only purpose is to throw.
|
||||||
|
// https://github.com/dotnet/runtime/blob/2b21c73fa2c32fa0195e4a411a435dda185efd08/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs#L14
|
||||||
|
// and https://github.com/dotnet/runtime/blob/f0168ee80ba9aca18a7e7140b2bb436defda623c/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs#L44
|
||||||
let fields =
|
let fields =
|
||||||
[
|
[
|
||||||
// for the GC, I think?
|
// for the GC, I think?
|
||||||
"m_keepalive", CliType.ObjectRef None
|
{
|
||||||
|
Name = "m_keepalive"
|
||||||
|
Contents = CliType.ObjectRef None
|
||||||
|
Offset = None
|
||||||
|
}
|
||||||
// TODO: this is actually a System.IntPtr https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/coreclr/nativeaot/Runtime.Base/src/System/Primitives.cs#L339
|
// TODO: this is actually a System.IntPtr https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/coreclr/nativeaot/Runtime.Base/src/System/Primitives.cs#L339
|
||||||
"m_cache", CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.Verbatim 0L))
|
{
|
||||||
"m_handle", CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.TypeHandlePtr def))
|
Name = "m_cache"
|
||||||
|
Contents = CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.Verbatim 0L))
|
||||||
|
Offset = None
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Name = "m_handle"
|
||||||
|
Contents = CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.TypeHandlePtr def))
|
||||||
|
Offset = None
|
||||||
|
}
|
||||||
// This is the const -1, apparently?!
|
// This is the const -1, apparently?!
|
||||||
// https://github.com/dotnet/runtime/blob/f0168ee80ba9aca18a7e7140b2bb436defda623c/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs#L2496
|
// https://github.com/dotnet/runtime/blob/f0168ee80ba9aca18a7e7140b2bb436defda623c/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs#L2496
|
||||||
"GenericParameterCountAny", CliType.Numeric (CliNumericType.Int32 -1)
|
{
|
||||||
|
Name = "GenericParameterCountAny"
|
||||||
|
Contents = CliType.Numeric (CliNumericType.Int32 -1)
|
||||||
|
Offset = None
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
let alloc, state = allocate fields allocState
|
let alloc, state = allocate fields allocState
|
||||||
|
@@ -394,7 +394,14 @@ module internal UnaryMetadataIlOp =
|
|||||||
ImmutableArray.Empty
|
ImmutableArray.Empty
|
||||||
state
|
state
|
||||||
|
|
||||||
state, (field.Name, zero) :: zeros
|
let field =
|
||||||
|
{
|
||||||
|
Name = field.Name
|
||||||
|
Contents = zero
|
||||||
|
Offset = field.Offset
|
||||||
|
}
|
||||||
|
|
||||||
|
state, field :: zeros
|
||||||
)
|
)
|
||||||
|
|
||||||
let fields = List.rev fieldZeros
|
let fields = List.rev fieldZeros
|
||||||
@@ -639,8 +646,14 @@ module internal UnaryMetadataIlOp =
|
|||||||
{ v with
|
{ v with
|
||||||
Fields =
|
Fields =
|
||||||
v.Fields
|
v.Fields
|
||||||
|> List.replaceWhere (fun (x, _) ->
|
|> List.replaceWhere (fun f ->
|
||||||
if x = field.Name then Some (x, valueToStore) else None
|
if f.Name = field.Name then
|
||||||
|
{ f with
|
||||||
|
Contents = valueToStore
|
||||||
|
}
|
||||||
|
|> Some
|
||||||
|
else
|
||||||
|
None
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -836,7 +849,7 @@ module internal UnaryMetadataIlOp =
|
|||||||
| false, _ -> failwith $"todo: array {managedHeapAddress}"
|
| false, _ -> failwith $"todo: array {managedHeapAddress}"
|
||||||
| true, v ->
|
| true, v ->
|
||||||
IlMachineState.pushToEvalStack
|
IlMachineState.pushToEvalStack
|
||||||
(v.Fields |> List.find (fun (x, _) -> field.Name = x) |> snd)
|
(v.Fields |> List.find (fun f -> field.Name = f.Name) |> _.Contents)
|
||||||
thread
|
thread
|
||||||
state
|
state
|
||||||
| EvalStackValue.ManagedPointer (ManagedPointerSource.ArrayIndex (arr, index)) ->
|
| EvalStackValue.ManagedPointer (ManagedPointerSource.ArrayIndex (arr, index)) ->
|
||||||
@@ -848,9 +861,8 @@ module internal UnaryMetadataIlOp =
|
|||||||
failwith "TODO: raise NullReferenceException"
|
failwith "TODO: raise NullReferenceException"
|
||||||
| EvalStackValue.ManagedPointer (ManagedPointerSource.Field _) ->
|
| EvalStackValue.ManagedPointer (ManagedPointerSource.Field _) ->
|
||||||
failwith "TODO: get a field on a field ptr"
|
failwith "TODO: get a field on a field ptr"
|
||||||
| EvalStackValue.UserDefinedValueType fields ->
|
| EvalStackValue.UserDefinedValueType vt ->
|
||||||
let result =
|
let result = vt |> EvalStackValueUserType.DereferenceField field.Name
|
||||||
fields |> List.pick (fun (k, v) -> if k = field.Name then Some v else None)
|
|
||||||
|
|
||||||
IlMachineState.pushToEvalStack' result thread state
|
IlMachineState.pushToEvalStack' result thread state
|
||||||
|
|
||||||
@@ -1182,16 +1194,19 @@ module internal UnaryMetadataIlOp =
|
|||||||
let zero, state =
|
let zero, state =
|
||||||
IlMachineState.cliTypeZeroOfHandle state baseClassTypes fieldHandle
|
IlMachineState.cliTypeZeroOfHandle state baseClassTypes fieldHandle
|
||||||
|
|
||||||
state, (field.Name, zero) :: acc
|
let field =
|
||||||
|
{
|
||||||
|
Name = field.Name
|
||||||
|
Contents = zero
|
||||||
|
Offset = failwith "offset"
|
||||||
|
}
|
||||||
|
|
||||||
|
state, field :: acc
|
||||||
)
|
)
|
||||||
|> fun (state, fields) -> state, List.rev fields
|
|> fun (state, fields) -> state, List.rev fields
|
||||||
|
|
||||||
// Create the value type with zero-initialized fields
|
// Create the value type with zero-initialized fields
|
||||||
let newValue =
|
let newValue = zeroFields |> CliValueType.OfFields |> CliType.ValueType
|
||||||
CliType.ValueType
|
|
||||||
{
|
|
||||||
Fields = zeroFields
|
|
||||||
}
|
|
||||||
|
|
||||||
state |> IlMachineState.setLocalVariable thread frame var newValue
|
state |> IlMachineState.setLocalVariable thread frame var newValue
|
||||||
| ManagedPointerSource.Argument (thread, frame, arg) -> failwith "TODO: Argument"
|
| ManagedPointerSource.Argument (thread, frame, arg) -> failwith "TODO: Argument"
|
||||||
@@ -1319,9 +1334,14 @@ module internal UnaryMetadataIlOp =
|
|||||||
let alloc, state = IlMachineState.getOrAllocateType baseClassTypes handle state
|
let alloc, state = IlMachineState.getOrAllocateType baseClassTypes handle state
|
||||||
|
|
||||||
let vt =
|
let vt =
|
||||||
|
// https://github.com/dotnet/runtime/blob/2b21c73fa2c32fa0195e4a411a435dda185efd08/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L92
|
||||||
{
|
{
|
||||||
Fields = [ "m_type", CliType.ObjectRef (Some alloc) ]
|
Name = "m_type"
|
||||||
|
Contents = CliType.ObjectRef (Some alloc)
|
||||||
|
Offset = None
|
||||||
}
|
}
|
||||||
|
|> List.singleton
|
||||||
|
|> CliValueType.OfFields
|
||||||
|
|
||||||
IlMachineState.pushToEvalStack (CliType.ValueType vt) thread state
|
IlMachineState.pushToEvalStack (CliType.ValueType vt) thread state
|
||||||
|
|
||||||
|
@@ -25,6 +25,8 @@ module internal UnaryStringTokenIlOp =
|
|||||||
|
|
||||||
let state = state |> IlMachineState.setStringData dataAddr stringToAllocate
|
let state = state |> IlMachineState.setStringData dataAddr stringToAllocate
|
||||||
|
|
||||||
|
// String type is:
|
||||||
|
// https://github.com/dotnet/runtime/blob/f0168ee80ba9aca18a7e7140b2bb436defda623c/src/libraries/System.Private.CoreLib/src/System/String.cs#L26
|
||||||
let stringInstanceFields =
|
let stringInstanceFields =
|
||||||
baseClassTypes.String.Fields
|
baseClassTypes.String.Fields
|
||||||
|> List.choose (fun field ->
|
|> List.choose (fun field ->
|
||||||
@@ -47,8 +49,16 @@ module internal UnaryStringTokenIlOp =
|
|||||||
|
|
||||||
let fields =
|
let fields =
|
||||||
[
|
[
|
||||||
"_firstChar", CliType.ofChar state.ManagedHeap.StringArrayData.[dataAddr]
|
{
|
||||||
"_stringLength", CliType.Numeric (CliNumericType.Int32 stringToAllocate.Length)
|
Name = "_firstChar"
|
||||||
|
Contents = CliType.ofChar state.ManagedHeap.StringArrayData.[dataAddr]
|
||||||
|
Offset = None
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Name = "_stringLength"
|
||||||
|
Contents = CliType.Numeric (CliNumericType.Int32 stringToAllocate.Length)
|
||||||
|
Offset = None
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
let addr, state =
|
let addr, state =
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
<Compile Include="Tuple.fs" />
|
<Compile Include="Tuple.fs" />
|
||||||
<Compile Include="List.fs" />
|
<Compile Include="List.fs" />
|
||||||
<Compile Include="Result.fs" />
|
<Compile Include="Result.fs" />
|
||||||
|
<Compile Include="Constants.fs" />
|
||||||
<Compile Include="Corelib.fs" />
|
<Compile Include="Corelib.fs" />
|
||||||
<Compile Include="AbstractMachineDomain.fs" />
|
<Compile Include="AbstractMachineDomain.fs" />
|
||||||
<Compile Include="BasicCliType.fs" />
|
<Compile Include="BasicCliType.fs" />
|
||||||
|
Reference in New Issue
Block a user