This commit is contained in:
Smaug123
2025-08-15 14:11:48 +01:00
parent fca9a6dc47
commit 2e8245d341
18 changed files with 1176 additions and 98 deletions

View File

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

View File

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

View File

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

View 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
}
}

View 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
}
}

View File

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

View File

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

View File

@@ -0,0 +1,10 @@
namespace WoofWare.PawPrint
[<AutoOpen>]
module Constants =
[<Literal>]
let SIZEOF_INT = 4
[<Literal>]
let SIZEOF_OBJ = 8

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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