mirror of
https://github.com/Smaug123/WoofWare.PawPrint
synced 2025-10-06 06:28:39 +00:00
Compare commits
36 Commits
expand-exc
...
2e9fdbed48
Author | SHA1 | Date | |
---|---|---|---|
|
2e9fdbed48 | ||
|
fb5c4a6313 | ||
|
95987e592c | ||
|
0e31d74586 | ||
|
8112b122fb | ||
|
5173805562 | ||
|
cb5d76f059 | ||
|
5e7bd969ba | ||
|
07fabfff65 | ||
|
655ba4400a | ||
|
c58c8ce678 | ||
|
239ae0f0cd | ||
|
4de0dbd816 | ||
|
91aff34d1e | ||
|
f9e186ba8f | ||
|
622d0782ae | ||
|
3e4b0a7b7e | ||
|
5f35c7a7cd | ||
|
9afc7efea1 | ||
|
2190f148e1 | ||
|
e2e3d5c3bf | ||
|
92f22cff42 | ||
|
3bdfeaf8a1 | ||
|
5c14baec9f | ||
|
174e415c70 | ||
|
a531531aef | ||
|
4c64dd7eb5 | ||
|
cfd6716616 | ||
|
d711d6fff5 | ||
|
6dbe6614d5 | ||
|
d8b7e84f6c | ||
|
3af1f2cc97 | ||
|
56d1cf63d6 | ||
|
de1eefb436 | ||
|
30b8ec990f | ||
|
2efe9803da |
61
CLAUDE.md
61
CLAUDE.md
@@ -42,8 +42,13 @@ dotnet fantomas .
|
||||
```
|
||||
|
||||
### Running the Application
|
||||
A playground C# file is in CSharpExample/Class1.cs.
|
||||
This environment is convenient for running WoofWare.PawPrint against a standalone DLL.
|
||||
Interpolate the appropriate strings like `{Platform}` as necessary depending on the current environment and the output of the `dotnet publish`.
|
||||
|
||||
```bash
|
||||
dotnet publish --self-contained --runtime-id osx-arm64 CSharpExample/ && dotnet run --project WoofWare.PawPrint.App/WoofWare.PawPrint.App.fsproj -- CSharpExample/bin/Debug/net9.0/osx-arm64/publish/CSharpExample.dll
|
||||
dotnet publish --self-contained --runtime {Platform} CSharpExample/
|
||||
dotnet run --project WoofWare.PawPrint.App/WoofWare.PawPrint.App.fsproj -- CSharpExample/bin/{Configuration}/{Framework}/{Platform}/publish/CSharpExample.dll
|
||||
```
|
||||
|
||||
## Architecture
|
||||
@@ -63,9 +68,11 @@ dotnet publish --self-contained --runtime-id osx-arm64 CSharpExample/ && dotnet
|
||||
|
||||
**WoofWare.PawPrint.Test**
|
||||
- Uses NUnit as the test framework
|
||||
- Test cases are defined in `TestCases.fs`
|
||||
- C# source files in `sources/` are compiled and executed by the runtime as test cases
|
||||
- Test cases are defined in `TestPureCases.fs` and `TestImpureCases.fs`
|
||||
- C# source files in `sources{Pure,Impure}/` are compiled and executed by the runtime as test cases; files in `sourcesPure` are automatically turned into test cases with no further action (see TestPureCases.fs for the mechanism)
|
||||
- `TestHarness.fs` provides infrastructure for running test assemblies through the interpreter
|
||||
- Run all tests with `dotnet run --project WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj -- --no-spinner` (note the additional `--`)
|
||||
- Run a specific test with `dotnet run --project WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj -- --filter-test-case StringWithinTestName --no-spinner`
|
||||
|
||||
**WoofWare.PawPrint.App**
|
||||
- Entry point application for running the interpreter
|
||||
@@ -90,8 +97,8 @@ dotnet publish --self-contained --runtime-id osx-arm64 CSharpExample/ && dotnet
|
||||
When adding new IL instruction support:
|
||||
1. Add the instruction to `IlOp.fs`
|
||||
2. Implement execution logic in `AbstractMachine.fs`
|
||||
3. Add a test case in `sources/` (C# file) that exercises the instruction
|
||||
4. Add the test case to `TestCases.fs`
|
||||
3. Add a test case in `sourcesPure/` or `sourcesImpure/` (C# file) that exercises the instruction, remembering also to add the file as an EmbeddedResource in WoofWare.PawPrint.Test.fsproj
|
||||
4. Add the test case to `TestPureCases.fs` or `TestImpureCases.fs`
|
||||
5. Run tests to verify implementation
|
||||
|
||||
The project uses deterministic builds and treats warnings as errors to maintain code quality.
|
||||
@@ -174,3 +181,47 @@ When encountering errors:
|
||||
4. Add logging to see generic contexts: `failwithf "Failed to concretize: %A" typeDefn`
|
||||
5. Check if you're in a generic method calling another generic method
|
||||
6. Verify TypeRefs are being resolved in the correct assembly
|
||||
|
||||
## Common Type System Patterns
|
||||
|
||||
### Creating TypeDefn from Type Metadata
|
||||
|
||||
When you need to create a `TypeDefn` from type metadata (e.g., from a `TypeInfo`), there's a common pattern that involves:
|
||||
1. Resolving the base type to determine `SignatureTypeKind`
|
||||
2. Creating the base `TypeDefn.FromDefinition`
|
||||
3. For generic types, creating a `GenericInstantiation` with type parameters
|
||||
|
||||
This pattern is implemented in `UnaryMetadataIlOp.lookupTypeDefn`. Example usage:
|
||||
```fsharp
|
||||
let state, typeDefn =
|
||||
UnaryMetadataIlOp.lookupTypeDefn
|
||||
baseClassTypes
|
||||
state
|
||||
activeAssembly
|
||||
typeDefHandle
|
||||
```
|
||||
|
||||
### Field Signature Comparison in Generic Contexts
|
||||
|
||||
When comparing field signatures in generic contexts (e.g., when resolving member references), signatures must be concretized before comparison. This ensures generic type parameters are properly substituted:
|
||||
|
||||
```fsharp
|
||||
// Concretize both signatures before comparing
|
||||
let state, concreteFieldSig = concretizeType ... fieldSig
|
||||
let state, fieldSigConcrete = concretizeType ... fi.Signature
|
||||
if fieldSigConcrete = concreteFieldSig then ...
|
||||
```
|
||||
|
||||
### Static vs Instance Fields
|
||||
|
||||
When constructing objects with `Newobj`:
|
||||
- Only instance fields should be included in the object
|
||||
- Static fields belong to the type, not instances
|
||||
- Filter using: `field.Attributes.HasFlag FieldAttributes.Static`
|
||||
|
||||
Example:
|
||||
```fsharp
|
||||
let instanceFields =
|
||||
ctorType.Fields
|
||||
|> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static))
|
||||
```
|
||||
|
@@ -6,6 +6,11 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<WarningsAsErrors>false</WarningsAsErrors>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<EnableDefaultItems>false</EnableDefaultItems>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Class1.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -45,10 +45,7 @@ type DumpedAssembly =
|
||||
/// Dictionary of all type definitions in this assembly, keyed by their handle.
|
||||
/// </summary>
|
||||
TypeDefs :
|
||||
IReadOnlyDictionary<
|
||||
TypeDefinitionHandle,
|
||||
WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
>
|
||||
IReadOnlyDictionary<TypeDefinitionHandle, WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn>>
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of all type references in this assembly, keyed by their handle.
|
||||
@@ -67,7 +64,7 @@ type DumpedAssembly =
|
||||
Methods :
|
||||
IReadOnlyDictionary<
|
||||
MethodDefinitionHandle,
|
||||
WoofWare.PawPrint.MethodInfo<FakeUnit, WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
WoofWare.PawPrint.MethodInfo<GenericParamFromMetadata, GenericParamFromMetadata, TypeDefn>
|
||||
>
|
||||
|
||||
/// <summary>
|
||||
@@ -78,7 +75,8 @@ type DumpedAssembly =
|
||||
/// <summary>
|
||||
/// Dictionary of all field definitions in this assembly, keyed by their handle.
|
||||
/// </summary>
|
||||
Fields : IReadOnlyDictionary<FieldDefinitionHandle, WoofWare.PawPrint.FieldInfo<FakeUnit, TypeDefn>>
|
||||
Fields :
|
||||
IReadOnlyDictionary<FieldDefinitionHandle, WoofWare.PawPrint.FieldInfo<GenericParamFromMetadata, TypeDefn>>
|
||||
|
||||
/// <summary>
|
||||
/// The entry point method of the assembly, if one exists.
|
||||
@@ -146,10 +144,7 @@ type DumpedAssembly =
|
||||
/// Internal lookup for type definitions by namespace and name.
|
||||
/// </summary>
|
||||
_TypeDefsLookup :
|
||||
ImmutableDictionary<
|
||||
string * string,
|
||||
WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
>
|
||||
ImmutableDictionary<string * string, WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn>>
|
||||
}
|
||||
|
||||
static member internal BuildExportedTypesLookup
|
||||
@@ -205,7 +200,7 @@ type DumpedAssembly =
|
||||
static member internal BuildTypeDefsLookup
|
||||
(logger : ILogger)
|
||||
(name : AssemblyName)
|
||||
(typeDefs : WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> seq)
|
||||
(typeDefs : WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn> seq)
|
||||
=
|
||||
let result = ImmutableDictionary.CreateBuilder ()
|
||||
let keys = HashSet ()
|
||||
@@ -236,7 +231,7 @@ type DumpedAssembly =
|
||||
member this.TypeDef
|
||||
(``namespace`` : string)
|
||||
(name : string)
|
||||
: WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> option
|
||||
: WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn> option
|
||||
=
|
||||
match this._TypeDefsLookup.TryGetValue ((``namespace``, name)) with
|
||||
| false, _ -> None
|
||||
@@ -428,8 +423,8 @@ module Assembly =
|
||||
let rec resolveTypeRef
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(referencedInAssembly : DumpedAssembly)
|
||||
(target : TypeRef)
|
||||
(genericArgs : ImmutableArray<TypeDefn>)
|
||||
(target : TypeRef)
|
||||
: TypeResolutionResult
|
||||
=
|
||||
match target.ResolutionScope with
|
||||
@@ -467,7 +462,8 @@ module Assembly =
|
||||
|
||||
match targetType with
|
||||
| [ t ] ->
|
||||
let t = t |> TypeInfo.mapGeneric (fun _ param -> genericArgs.[param.SequenceNumber])
|
||||
let t =
|
||||
t |> TypeInfo.mapGeneric (fun (param, md) -> genericArgs.[param.SequenceNumber])
|
||||
|
||||
TypeResolutionResult.Resolved (assy, t)
|
||||
| _ :: _ :: _ -> failwith $"Multiple matching type definitions! {nsPath} {target.Name}"
|
||||
@@ -493,13 +489,13 @@ module Assembly =
|
||||
| Some typeDef ->
|
||||
let typeDef =
|
||||
typeDef
|
||||
|> TypeInfo.mapGeneric (fun _ param -> genericArgs.[param.SequenceNumber])
|
||||
|> TypeInfo.mapGeneric (fun (param, md) -> genericArgs.[param.SequenceNumber])
|
||||
|
||||
TypeResolutionResult.Resolved (assy, typeDef)
|
||||
| None ->
|
||||
|
||||
match assy.TypeRef ns name with
|
||||
| Some typeRef -> resolveTypeRef assemblies assy typeRef genericArgs
|
||||
| Some typeRef -> resolveTypeRef assemblies assy genericArgs typeRef
|
||||
| None ->
|
||||
|
||||
match assy.ExportedType (Some ns) name with
|
||||
@@ -536,7 +532,7 @@ module DumpedAssembly =
|
||||
| Some (BaseTypeInfo.TypeRef r) ->
|
||||
let assy = loadedAssemblies.[source.FullName]
|
||||
// TODO: generics
|
||||
match Assembly.resolveTypeRef loadedAssemblies assy assy.TypeRefs.[r] ImmutableArray.Empty with
|
||||
match Assembly.resolveTypeRef loadedAssemblies assy ImmutableArray.Empty assy.TypeRefs.[r] with
|
||||
| TypeResolutionResult.FirstLoadAssy _ ->
|
||||
failwith
|
||||
"seems pretty unlikely that we could have constructed this object without loading its base type"
|
||||
@@ -564,3 +560,36 @@ module DumpedAssembly =
|
||||
| None -> ResolvedBaseType.Object
|
||||
|
||||
go source baseTypeInfo
|
||||
|
||||
let typeInfoToTypeDefn
|
||||
(bct : BaseClassTypes<DumpedAssembly>)
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(ti : TypeInfo<TypeDefn, TypeDefn>)
|
||||
: TypeDefn
|
||||
=
|
||||
ti
|
||||
|> TypeInfo.toTypeDefn
|
||||
bct
|
||||
(fun n -> assemblies.[n.FullName])
|
||||
_.Name
|
||||
(fun x y -> x.TypeDefs.[y])
|
||||
(fun x y ->
|
||||
let r = x.TypeRefs.[y] |> Assembly.resolveTypeRef assemblies x ImmutableArray.Empty
|
||||
|
||||
match r with
|
||||
| TypeResolutionResult.FirstLoadAssy assemblyReference -> failwith "todo"
|
||||
| TypeResolutionResult.Resolved (dumpedAssembly, typeInfo) ->
|
||||
let result =
|
||||
typeInfo |> TypeInfo.mapGeneric (fun typeDef -> failwith "TODO: generics")
|
||||
|
||||
dumpedAssembly, result
|
||||
)
|
||||
|
||||
let typeInfoToTypeDefn'
|
||||
(bct : BaseClassTypes<DumpedAssembly>)
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(ti : TypeInfo<GenericParamFromMetadata, TypeDefn>)
|
||||
=
|
||||
ti
|
||||
|> TypeInfo.mapGeneric (fun (par, _) -> TypeDefn.GenericTypeParameter par.SequenceNumber)
|
||||
|> typeInfoToTypeDefn bct assemblies
|
||||
|
37
WoofWare.PawPrint.Domain/ComparableFieldDefinitionHandle.fs
Normal file
37
WoofWare.PawPrint.Domain/ComparableFieldDefinitionHandle.fs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System
|
||||
open System.Reflection.Metadata
|
||||
|
||||
[<CustomEquality>]
|
||||
[<CustomComparison>]
|
||||
type ComparableFieldDefinitionHandle =
|
||||
private
|
||||
{
|
||||
_Inner : FieldDefinitionHandle
|
||||
}
|
||||
|
||||
override this.Equals other =
|
||||
match other with
|
||||
| :? ComparableFieldDefinitionHandle as other -> this._Inner.GetHashCode () = other._Inner.GetHashCode ()
|
||||
| _ -> false
|
||||
|
||||
override this.GetHashCode () : int = this._Inner.GetHashCode ()
|
||||
|
||||
interface IComparable<ComparableFieldDefinitionHandle> with
|
||||
member this.CompareTo (other : ComparableFieldDefinitionHandle) : int =
|
||||
this._Inner.GetHashCode().CompareTo (other._Inner.GetHashCode ())
|
||||
|
||||
interface IComparable with
|
||||
member this.CompareTo (other : obj) : int =
|
||||
match other with
|
||||
| :? ComparableFieldDefinitionHandle as other ->
|
||||
(this :> IComparable<ComparableFieldDefinitionHandle>).CompareTo other
|
||||
| _ -> failwith "invalid comparison"
|
||||
|
||||
static member Make (h : FieldDefinitionHandle) =
|
||||
{
|
||||
_Inner = h
|
||||
}
|
||||
|
||||
member this.Get = this._Inner
|
@@ -11,7 +11,7 @@ type ComparableTypeDefinitionHandle =
|
||||
_Inner : TypeDefinitionHandle
|
||||
}
|
||||
|
||||
override this.Equals (other) =
|
||||
override this.Equals other =
|
||||
match other with
|
||||
| :? ComparableTypeDefinitionHandle as other -> this._Inner.GetHashCode () = other._Inner.GetHashCode ()
|
||||
| _ -> false
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection
|
||||
open System.Reflection.Metadata
|
||||
|
||||
@@ -16,8 +16,8 @@ module FakeUnit =
|
||||
|
||||
/// A type which has been concretised, runtime-representable, etc.
|
||||
[<CustomEquality>]
|
||||
[<CustomComparison>]
|
||||
type ConcreteType<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :> IComparable<'typeGeneric>> =
|
||||
[<NoComparison>]
|
||||
type ConcreteType<'typeGeneric> =
|
||||
private
|
||||
{
|
||||
/// Do not use this, because it's intended to be private; use the accessor `.Assembly : AssemblyName`
|
||||
@@ -30,12 +30,26 @@ type ConcreteType<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :
|
||||
/// Do not use this, because it's intended to be private; use the accessor `.Namespace` instead.
|
||||
_Namespace : string
|
||||
/// Do not use this, because it's intended to be private; use the accessor `.Generics` instead.
|
||||
_Generics : 'typeGeneric list
|
||||
_Generics : ImmutableArray<'typeGeneric>
|
||||
}
|
||||
|
||||
override this.ToString () : string =
|
||||
let basic = $"%s{this.Assembly.Name}.%s{this.Namespace}.%s{this.Name}"
|
||||
|
||||
let generics =
|
||||
if this.Generics.IsEmpty then
|
||||
""
|
||||
else
|
||||
this.Generics
|
||||
|> Seq.map string
|
||||
|> String.concat ", "
|
||||
|> fun x -> "<" + x + ">"
|
||||
|
||||
basic + generics
|
||||
|
||||
member this.Assembly : AssemblyName = this._AssemblyName
|
||||
member this.Definition : ComparableTypeDefinitionHandle = this._Definition
|
||||
member this.Generics : 'typeGeneric list = this._Generics
|
||||
member this.Generics : ImmutableArray<'typeGeneric> = this._Generics
|
||||
member this.Name = this._Name
|
||||
member this.Namespace = this._Namespace
|
||||
|
||||
@@ -50,72 +64,26 @@ type ConcreteType<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :
|
||||
override this.GetHashCode () : int =
|
||||
hash (this._AssemblyName.FullName, this._Definition, this._Generics)
|
||||
|
||||
interface IComparable<ConcreteType<'typeGeneric>> with
|
||||
member this.CompareTo (other : ConcreteType<'typeGeneric>) : int =
|
||||
let comp = this._AssemblyName.FullName.CompareTo other._AssemblyName.FullName
|
||||
|
||||
if comp <> 0 then
|
||||
comp
|
||||
else
|
||||
|
||||
let comp =
|
||||
(this._Definition :> IComparable<ComparableTypeDefinitionHandle>).CompareTo other._Definition
|
||||
|
||||
if comp <> 0 then
|
||||
comp
|
||||
else
|
||||
|
||||
let thisGen = (this._Generics : 'typeGeneric list) :> IComparable<'typeGeneric list>
|
||||
thisGen.CompareTo other._Generics
|
||||
|
||||
interface IComparable with
|
||||
member this.CompareTo other =
|
||||
match other with
|
||||
| :? ConcreteType<'typeGeneric> as other ->
|
||||
(this :> IComparable<ConcreteType<'typeGeneric>>).CompareTo other
|
||||
| _ -> failwith "bad comparison"
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module ConcreteType =
|
||||
let make
|
||||
(assemblyName : AssemblyName)
|
||||
(defn : TypeDefinitionHandle)
|
||||
(ns : string)
|
||||
(name : string)
|
||||
(defn : TypeDefinitionHandle)
|
||||
(generics : TypeDefn list)
|
||||
: ConcreteType<TypeDefn>
|
||||
(genericParam : ImmutableArray<GenericParamFromMetadata>)
|
||||
: ConcreteType<GenericParamFromMetadata>
|
||||
=
|
||||
{
|
||||
_AssemblyName = assemblyName
|
||||
_Definition = ComparableTypeDefinitionHandle.Make defn
|
||||
_Name = name
|
||||
_Namespace = ns
|
||||
_Generics = generics
|
||||
_Generics = genericParam
|
||||
}
|
||||
|
||||
let make'
|
||||
(assemblyName : AssemblyName)
|
||||
(defn : TypeDefinitionHandle)
|
||||
(ns : string)
|
||||
(name : string)
|
||||
(genericParamCount : int)
|
||||
: ConcreteType<FakeUnit>
|
||||
=
|
||||
{
|
||||
_AssemblyName = assemblyName
|
||||
_Definition = ComparableTypeDefinitionHandle.Make defn
|
||||
_Name = name
|
||||
_Namespace = ns
|
||||
_Generics = List.replicate genericParamCount FakeUnit.FakeUnit
|
||||
}
|
||||
|
||||
let mapGeneric<'a, 'b
|
||||
when 'a : comparison and 'a :> IComparable<'a> and 'b : equality and 'b : comparison and 'b :> IComparable<'b>>
|
||||
(f : int -> 'a -> 'b)
|
||||
(x : ConcreteType<'a>)
|
||||
: ConcreteType<'b>
|
||||
=
|
||||
let generics = x._Generics |> List.mapi f
|
||||
let mapGeneric<'a, 'b> (f : int -> 'a -> 'b) (x : ConcreteType<'a>) : ConcreteType<'b> =
|
||||
let generics = x._Generics |> Seq.mapi f |> ImmutableArray.CreateRange
|
||||
|
||||
{
|
||||
_AssemblyName = x._AssemblyName
|
||||
|
16
WoofWare.PawPrint.Domain/Constants.fs
Normal file
16
WoofWare.PawPrint.Domain/Constants.fs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
[<AutoOpen>]
|
||||
module Constants =
|
||||
|
||||
[<Literal>]
|
||||
let SIZEOF_INT = 4
|
||||
|
||||
[<Literal>]
|
||||
let SIZEOF_OBJ = 8
|
||||
|
||||
[<Literal>]
|
||||
let DEFAULT_STRUCT_ALIGNMENT = 8
|
||||
|
||||
[<Literal>]
|
||||
let NATIVE_INT_SIZE = 8
|
@@ -1,6 +1,5 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System
|
||||
open System.Reflection
|
||||
open System.Reflection.Metadata
|
||||
|
||||
@@ -8,8 +7,7 @@ open System.Reflection.Metadata
|
||||
/// Represents detailed information about a field in a .NET assembly.
|
||||
/// This is a strongly-typed representation of FieldDefinition from System.Reflection.Metadata.
|
||||
/// </summary>
|
||||
type FieldInfo<'typeGeneric, 'fieldGeneric when 'typeGeneric : comparison and 'typeGeneric :> IComparable<'typeGeneric>>
|
||||
=
|
||||
type FieldInfo<'typeGeneric, 'fieldGeneric> =
|
||||
{
|
||||
/// <summary>
|
||||
/// The metadata token handle that uniquely identifies this field in the assembly.
|
||||
@@ -34,8 +32,15 @@ type FieldInfo<'typeGeneric, 'fieldGeneric when 'typeGeneric : comparison and 't
|
||||
/// literal, and other characteristics.
|
||||
/// </summary>
|
||||
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.IsStatic = this.Attributes.HasFlag FieldAttributes.Static
|
||||
|
||||
override this.ToString () : string =
|
||||
$"%s{this.DeclaringType.Assembly.Name}.{this.DeclaringType.Name}.%s{this.Name}"
|
||||
|
||||
@@ -46,18 +51,26 @@ module FieldInfo =
|
||||
(assembly : AssemblyName)
|
||||
(handle : FieldDefinitionHandle)
|
||||
(def : FieldDefinition)
|
||||
: FieldInfo<FakeUnit, TypeDefn>
|
||||
: FieldInfo<GenericParamFromMetadata, TypeDefn>
|
||||
=
|
||||
let name = mr.GetString def.Name
|
||||
let fieldSig = def.DecodeSignature (TypeDefn.typeProvider assembly, ())
|
||||
let declaringType = def.GetDeclaringType ()
|
||||
let typeGenerics = mr.GetTypeDefinition(declaringType).GetGenericParameters().Count
|
||||
let decType = mr.GetTypeDefinition (declaringType)
|
||||
|
||||
let decType = mr.GetTypeDefinition declaringType
|
||||
|
||||
let typeGenerics = decType.GetGenericParameters () |> GenericParameter.readAll mr
|
||||
|
||||
let declaringTypeNamespace = mr.GetString decType.Namespace
|
||||
let declaringTypeName = mr.GetString decType.Name
|
||||
|
||||
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
|
||||
@@ -65,14 +78,10 @@ module FieldInfo =
|
||||
DeclaringType = declaringType
|
||||
Handle = handle
|
||||
Attributes = def.Attributes
|
||||
Offset = offset
|
||||
}
|
||||
|
||||
let mapTypeGenerics<'a, 'b, 'field
|
||||
when 'a :> IComparable<'a> and 'a : comparison and 'b :> IComparable<'b> and 'b : comparison>
|
||||
(f : int -> 'a -> 'b)
|
||||
(input : FieldInfo<'a, 'field>)
|
||||
: FieldInfo<'b, 'field>
|
||||
=
|
||||
let mapTypeGenerics<'a, 'b, 'field> (f : int -> 'a -> 'b) (input : FieldInfo<'a, 'field>) : FieldInfo<'b, 'field> =
|
||||
let declaringType = input.DeclaringType |> ConcreteType.mapGeneric f
|
||||
|
||||
{
|
||||
@@ -81,5 +90,5 @@ module FieldInfo =
|
||||
DeclaringType = declaringType
|
||||
Signature = input.Signature
|
||||
Attributes = input.Attributes
|
||||
|
||||
Offset = input.Offset
|
||||
}
|
||||
|
85
WoofWare.PawPrint.Domain/GenericParameter.fs
Normal file
85
WoofWare.PawPrint.Domain/GenericParameter.fs
Normal file
@@ -0,0 +1,85 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection
|
||||
open System.Reflection.Metadata
|
||||
|
||||
type GenericVariance =
|
||||
| Covariant
|
||||
| Contravariant
|
||||
|
||||
type GenericConstraint =
|
||||
| Reference
|
||||
| NonNullableValue
|
||||
|
||||
type GenericParamMetadata =
|
||||
{
|
||||
Variance : GenericVariance option
|
||||
Constraint : GenericConstraint option
|
||||
RequiresParameterlessConstructor : bool
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a generic type or method parameter definition.
|
||||
/// Corresponds to GenericParameter in System.Reflection.Metadata.
|
||||
/// </summary>
|
||||
type GenericParameter =
|
||||
{
|
||||
/// <summary>The name of the generic parameter (e.g., 'T', 'TKey', etc.).</summary>
|
||||
Name : string
|
||||
|
||||
/// <summary>
|
||||
/// The zero-based index of the generic parameter in the generic parameter list.
|
||||
/// For example, in Dictionary<TKey, TValue&rt;, TKey has index 0 and TValue has index 1.
|
||||
/// </summary>
|
||||
SequenceNumber : int
|
||||
}
|
||||
|
||||
type GenericParamFromMetadata = GenericParameter * GenericParamMetadata
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module GenericParameter =
|
||||
let readAll
|
||||
(metadata : MetadataReader)
|
||||
(param : GenericParameterHandleCollection)
|
||||
: GenericParamFromMetadata ImmutableArray
|
||||
=
|
||||
param
|
||||
|> Seq.map (fun param ->
|
||||
let param = metadata.GetGenericParameter param
|
||||
|
||||
let requiresParamlessCons =
|
||||
param.Attributes.HasFlag GenericParameterAttributes.DefaultConstructorConstraint
|
||||
|
||||
let constr =
|
||||
if param.Attributes.HasFlag GenericParameterAttributes.NotNullableValueTypeConstraint then
|
||||
Some GenericConstraint.NonNullableValue
|
||||
elif param.Attributes.HasFlag GenericParameterAttributes.ReferenceTypeConstraint then
|
||||
Some GenericConstraint.Reference
|
||||
else
|
||||
None
|
||||
|
||||
let variance =
|
||||
if param.Attributes.HasFlag GenericParameterAttributes.Contravariant then
|
||||
Some GenericVariance.Contravariant
|
||||
elif param.Attributes.HasFlag GenericParameterAttributes.Covariant then
|
||||
Some GenericVariance.Covariant
|
||||
else
|
||||
None
|
||||
|
||||
let md =
|
||||
{
|
||||
Variance = variance
|
||||
Constraint = constr
|
||||
RequiresParameterlessConstructor = requiresParamlessCons
|
||||
}
|
||||
|
||||
let p =
|
||||
{
|
||||
Name = metadata.GetString param.Name
|
||||
SequenceNumber = param.Index
|
||||
}
|
||||
|
||||
p, md
|
||||
)
|
||||
|> ImmutableArray.CreateRange
|
22
WoofWare.PawPrint.Domain/ImmutableArray.fs
Normal file
22
WoofWare.PawPrint.Domain/ImmutableArray.fs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System.Collections.Immutable
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal ImmutableArray =
|
||||
|
||||
let inline map ([<InlineIfLambda>] f : 'a -> 'b) (arr : ImmutableArray<'a>) : ImmutableArray<'b> =
|
||||
let b = ImmutableArray.CreateBuilder ()
|
||||
|
||||
for i in arr do
|
||||
b.Add (f i)
|
||||
|
||||
b.ToImmutable ()
|
||||
|
||||
let inline mapi ([<InlineIfLambda>] f : int -> 'a -> 'b) (arr : ImmutableArray<'a>) : ImmutableArray<'b> =
|
||||
let b = ImmutableArray.CreateBuilder ()
|
||||
|
||||
for i = 0 to arr.Length - 1 do
|
||||
b.Add (f i arr.[i])
|
||||
|
||||
b.ToImmutable ()
|
@@ -52,40 +52,6 @@ module Parameter =
|
||||
|
||||
result.ToImmutable ()
|
||||
|
||||
/// <summary>
|
||||
/// Represents a generic type or method parameter definition.
|
||||
/// Corresponds to GenericParameter in System.Reflection.Metadata.
|
||||
/// </summary>
|
||||
type GenericParameter =
|
||||
{
|
||||
/// <summary>The name of the generic parameter (e.g., 'T', 'TKey', etc.).</summary>
|
||||
Name : string
|
||||
|
||||
/// <summary>
|
||||
/// The zero-based index of the generic parameter in the generic parameter list.
|
||||
/// For example, in Dictionary<TKey, TValue&rt;, TKey has index 0 and TValue has index 1.
|
||||
/// </summary>
|
||||
SequenceNumber : int
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module GenericParameter =
|
||||
let readAll
|
||||
(metadata : MetadataReader)
|
||||
(param : GenericParameterHandleCollection)
|
||||
: GenericParameter ImmutableArray
|
||||
=
|
||||
param
|
||||
|> Seq.map (fun param ->
|
||||
let param = metadata.GetGenericParameter param
|
||||
|
||||
{
|
||||
Name = metadata.GetString param.Name
|
||||
SequenceNumber = param.Index
|
||||
}
|
||||
)
|
||||
|> ImmutableArray.CreateRange
|
||||
|
||||
type ExceptionOffset =
|
||||
{
|
||||
TryLength : int
|
||||
@@ -168,8 +134,7 @@ module MethodInstructions =
|
||||
/// Represents detailed information about a method in a .NET assembly.
|
||||
/// This is a strongly-typed representation of MethodDefinition from System.Reflection.Metadata.
|
||||
/// </summary>
|
||||
type MethodInfo<'typeGenerics, 'methodGenerics, 'methodVars
|
||||
when 'typeGenerics :> IComparable<'typeGenerics> and 'typeGenerics : comparison> =
|
||||
type MethodInfo<'typeGenerics, 'methodGenerics, 'methodVars> =
|
||||
{
|
||||
/// <summary>
|
||||
/// The type that declares this method, along with its assembly information.
|
||||
@@ -267,14 +232,13 @@ module MethodInfo =
|
||||
| con -> failwith $"TODO: {con}"
|
||||
)
|
||||
|
||||
let mapTypeGenerics<'a, 'b, 'methodGen, 'vars
|
||||
when 'a :> IComparable<'a> and 'a : comparison and 'b : comparison and 'b :> IComparable<'b>>
|
||||
(f : int -> 'a -> 'b)
|
||||
let mapTypeGenerics<'a, 'b, 'methodGen, 'vars>
|
||||
(f : 'a -> 'b)
|
||||
(m : MethodInfo<'a, 'methodGen, 'vars>)
|
||||
: MethodInfo<'b, 'methodGen, 'vars>
|
||||
=
|
||||
{
|
||||
DeclaringType = m.DeclaringType |> ConcreteType.mapGeneric f
|
||||
DeclaringType = m.DeclaringType |> ConcreteType.mapGeneric (fun _ -> f)
|
||||
Handle = m.Handle
|
||||
Name = m.Name
|
||||
Instructions = m.Instructions
|
||||
@@ -288,18 +252,20 @@ module MethodInfo =
|
||||
IsStatic = m.IsStatic
|
||||
}
|
||||
|
||||
let mapMethodGenerics<'a, 'b, 'vars, 'typeGen when 'typeGen :> IComparable<'typeGen> and 'typeGen : comparison>
|
||||
let mapMethodGenerics<'a, 'b, 'vars, 'typeGen>
|
||||
(f : int -> 'a -> 'b)
|
||||
(m : MethodInfo<'typeGen, 'a, 'vars>)
|
||||
: MethodInfo<'typeGen, 'b, 'vars>
|
||||
=
|
||||
let generics = m.Generics |> Seq.mapi f |> ImmutableArray.CreateRange
|
||||
|
||||
{
|
||||
DeclaringType = m.DeclaringType
|
||||
Handle = m.Handle
|
||||
Name = m.Name
|
||||
Instructions = m.Instructions
|
||||
Parameters = m.Parameters
|
||||
Generics = m.Generics |> Seq.mapi f |> ImmutableArray.CreateRange
|
||||
Generics = generics
|
||||
Signature = m.Signature
|
||||
RawSignature = m.RawSignature
|
||||
CustomAttributes = m.CustomAttributes
|
||||
@@ -676,7 +642,7 @@ module MethodInfo =
|
||||
(peReader : PEReader)
|
||||
(metadataReader : MetadataReader)
|
||||
(methodHandle : MethodDefinitionHandle)
|
||||
: MethodInfo<FakeUnit, GenericParameter, TypeDefn> option
|
||||
: MethodInfo<GenericParamFromMetadata, GenericParamFromMetadata, TypeDefn> option
|
||||
=
|
||||
let logger = loggerFactory.CreateLogger "MethodInfo"
|
||||
let assemblyName = metadataReader.GetAssemblyDefinition().GetAssemblyName ()
|
||||
@@ -717,7 +683,8 @@ module MethodInfo =
|
||||
let declaringTypeName = metadataReader.GetString declaringDefn.Name
|
||||
|
||||
let declaringTypeGenericParams =
|
||||
metadataReader.GetTypeDefinition(declaringType).GetGenericParameters().Count
|
||||
metadataReader.GetTypeDefinition(declaringType).GetGenericParameters ()
|
||||
|> GenericParameter.readAll metadataReader
|
||||
|
||||
let attrs =
|
||||
let result = ImmutableArray.CreateBuilder ()
|
||||
@@ -738,7 +705,7 @@ module MethodInfo =
|
||||
GenericParameter.readAll metadataReader (methodDef.GetGenericParameters ())
|
||||
|
||||
let declaringType =
|
||||
ConcreteType.make'
|
||||
ConcreteType.make
|
||||
assemblyName
|
||||
declaringType
|
||||
declaringTypeNamespace
|
||||
|
@@ -1,5 +1,6 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection
|
||||
open System.Reflection.Metadata
|
||||
@@ -9,6 +10,12 @@ type ConcreteTypeHandle =
|
||||
| Byref of ConcreteTypeHandle
|
||||
| Pointer of ConcreteTypeHandle
|
||||
|
||||
override this.ToString () =
|
||||
match this with
|
||||
| ConcreteTypeHandle.Byref b -> "&" + b.ToString ()
|
||||
| ConcreteTypeHandle.Concrete i -> i.ToString ()
|
||||
| ConcreteTypeHandle.Pointer i -> "*" + i.ToString ()
|
||||
|
||||
type AllConcreteTypes =
|
||||
{
|
||||
Mapping : Map<int, ConcreteType<ConcreteTypeHandle>>
|
||||
@@ -46,7 +53,7 @@ module AllConcreteTypes =
|
||||
|
||||
let findExistingConcreteType
|
||||
(concreteTypes : AllConcreteTypes)
|
||||
(asm : AssemblyName, ns : string, name : string, generics : ConcreteTypeHandle list as key)
|
||||
(asm : AssemblyName, ns : string, name : string, generics : ConcreteTypeHandle ImmutableArray)
|
||||
: ConcreteTypeHandle option
|
||||
=
|
||||
concreteTypes.Mapping
|
||||
@@ -75,10 +82,318 @@ module AllConcreteTypes =
|
||||
|
||||
toRet, newState
|
||||
|
||||
// Active patterns for matching concrete types
|
||||
|
||||
[<AutoOpen>]
|
||||
module ConcreteActivePatterns =
|
||||
/// Active pattern to match primitive types from concrete type handles
|
||||
let (|ConcretePrimitive|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct when ct.Namespace = "System" && ct.Generics.IsEmpty ->
|
||||
match ct.Name with
|
||||
| "Int32" -> Some PrimitiveType.Int32
|
||||
| "Int64" -> Some PrimitiveType.Int64
|
||||
| "Int16" -> Some PrimitiveType.Int16
|
||||
| "UInt32" -> Some PrimitiveType.UInt32
|
||||
| "UInt64" -> Some PrimitiveType.UInt64
|
||||
| "UInt16" -> Some PrimitiveType.UInt16
|
||||
| "Byte" -> Some PrimitiveType.Byte
|
||||
| "SByte" -> Some PrimitiveType.SByte
|
||||
| "Single" -> Some PrimitiveType.Single
|
||||
| "Double" -> Some PrimitiveType.Double
|
||||
| "String" -> Some PrimitiveType.String
|
||||
| "Boolean" -> Some PrimitiveType.Boolean
|
||||
| "Char" -> Some PrimitiveType.Char
|
||||
| "Object" -> Some PrimitiveType.Object
|
||||
| "IntPtr" -> Some PrimitiveType.IntPtr
|
||||
| "UIntPtr" -> Some PrimitiveType.UIntPtr
|
||||
| "TypedReference" -> Some PrimitiveType.TypedReference
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
/// Active pattern to match void type
|
||||
let (|ConcreteVoid|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct when
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "Void"
|
||||
&& ct.Generics.IsEmpty
|
||||
->
|
||||
Some ()
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
/// Active pattern to match any concrete type by assembly/namespace/name and generics
|
||||
let (|ConcreteType|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct -> Some (ct.Assembly.Name, ct.Namespace, ct.Name, ct.Generics)
|
||||
| None -> None
|
||||
| _ -> None
|
||||
|
||||
let (|ConcreteChar|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct ->
|
||||
if
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "Char"
|
||||
&& ct.Generics.IsEmpty
|
||||
then
|
||||
Some ()
|
||||
else
|
||||
None
|
||||
| None -> None
|
||||
| _ -> None
|
||||
|
||||
let (|ConcreteRuntimeFieldHandle|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct when
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "RuntimeFieldHandle"
|
||||
&& ct.Generics.IsEmpty
|
||||
->
|
||||
Some ()
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|ConcreteNonGenericArray|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct when
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "Array"
|
||||
&& ct.Generics.IsEmpty
|
||||
->
|
||||
Some ()
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|ConcreteGenericArray|_|)
|
||||
(concreteTypes : AllConcreteTypes)
|
||||
(eltType : ConcreteTypeHandle)
|
||||
(handle : ConcreteTypeHandle)
|
||||
=
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct when
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "Array"
|
||||
&& Seq.tryExactlyOne ct.Generics = Some eltType
|
||||
->
|
||||
Some ()
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|ConcreteObj|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct ->
|
||||
if
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "Object"
|
||||
&& ct.Generics.IsEmpty
|
||||
then
|
||||
Some ()
|
||||
else
|
||||
None
|
||||
| None -> None
|
||||
| _ -> None
|
||||
|
||||
let (|ConcreteValueType|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct ->
|
||||
if
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "ValueType"
|
||||
&& ct.Generics.IsEmpty
|
||||
then
|
||||
Some ()
|
||||
else
|
||||
None
|
||||
| None -> None
|
||||
| _ -> None
|
||||
|
||||
let (|ConcreteBool|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct ->
|
||||
if
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "Boolean"
|
||||
&& ct.Generics.IsEmpty
|
||||
then
|
||||
Some ()
|
||||
else
|
||||
None
|
||||
| None -> None
|
||||
| _ -> None
|
||||
|
||||
let (|ConcreteString|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct ->
|
||||
if
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "String"
|
||||
&& ct.Generics.IsEmpty
|
||||
then
|
||||
Some ()
|
||||
else
|
||||
None
|
||||
| None -> None
|
||||
| _ -> None
|
||||
|
||||
let (|ConcreteDouble|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct ->
|
||||
if
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "Double"
|
||||
&& ct.Generics.IsEmpty
|
||||
then
|
||||
Some ()
|
||||
else
|
||||
None
|
||||
| None -> None
|
||||
| _ -> None
|
||||
|
||||
let (|ConcreteInt64|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct ->
|
||||
if
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "Int64"
|
||||
&& ct.Generics.IsEmpty
|
||||
then
|
||||
Some ()
|
||||
else
|
||||
None
|
||||
| None -> None
|
||||
| _ -> None
|
||||
|
||||
let (|ConcreteInt32|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct ->
|
||||
if
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "Int32"
|
||||
&& ct.Generics.IsEmpty
|
||||
then
|
||||
Some ()
|
||||
else
|
||||
None
|
||||
| None -> None
|
||||
| _ -> None
|
||||
|
||||
let (|ConcreteUInt32|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct ->
|
||||
if
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "UInt32"
|
||||
&& ct.Generics.IsEmpty
|
||||
then
|
||||
Some ()
|
||||
else
|
||||
None
|
||||
| None -> None
|
||||
| _ -> None
|
||||
|
||||
let (|ConcreteUInt64|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct ->
|
||||
if
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "UInt64"
|
||||
&& ct.Generics.IsEmpty
|
||||
then
|
||||
Some ()
|
||||
else
|
||||
None
|
||||
| None -> None
|
||||
| _ -> None
|
||||
|
||||
let (|ConcreteSingle|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Concrete id ->
|
||||
match concreteTypes.Mapping |> Map.tryFind id with
|
||||
| Some ct ->
|
||||
if
|
||||
ct.Assembly.Name = "System.Private.CoreLib"
|
||||
&& ct.Namespace = "System"
|
||||
&& ct.Name = "Single"
|
||||
&& ct.Generics.IsEmpty
|
||||
then
|
||||
Some ()
|
||||
else
|
||||
None
|
||||
| None -> None
|
||||
| _ -> None
|
||||
|
||||
/// Active pattern to match byref types
|
||||
let (|ConcreteByref|_|) (handle : ConcreteTypeHandle) =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Byref inner -> Some inner
|
||||
| _ -> None
|
||||
|
||||
/// Active pattern to match pointer types
|
||||
let (|ConcretePointer|_|) (handle : ConcreteTypeHandle) =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Pointer inner -> Some inner
|
||||
| _ -> None
|
||||
|
||||
type IAssemblyLoad =
|
||||
abstract LoadAssembly :
|
||||
loadedAssemblies : ImmutableDictionary<string, DumpedAssembly> ->
|
||||
referencedIn : AssemblyName ->
|
||||
handle : AssemblyReferenceHandle ->
|
||||
ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module TypeConcretization =
|
||||
|
||||
type ConcretizationContext =
|
||||
type ConcretizationContext<'corelib> =
|
||||
{
|
||||
/// Types currently being processed (to detect cycles)
|
||||
InProgress : ImmutableDictionary<AssemblyName * TypeDefn, ConcreteTypeHandle>
|
||||
@@ -86,7 +401,7 @@ module TypeConcretization =
|
||||
ConcreteTypes : AllConcreteTypes
|
||||
/// For resolving type references
|
||||
LoadedAssemblies : ImmutableDictionary<string, DumpedAssembly>
|
||||
BaseTypes : BaseClassTypes<DumpedAssembly>
|
||||
BaseTypes : BaseClassTypes<'corelib>
|
||||
}
|
||||
|
||||
// Helper function to find existing types by assembly, namespace, name, and generics
|
||||
@@ -95,7 +410,7 @@ module TypeConcretization =
|
||||
(assembly : AssemblyName)
|
||||
(ns : string)
|
||||
(name : string)
|
||||
(generics : ConcreteTypeHandle list)
|
||||
(generics : ConcreteTypeHandle ImmutableArray)
|
||||
: ConcreteTypeHandle option
|
||||
=
|
||||
concreteTypes.Mapping
|
||||
@@ -117,18 +432,18 @@ module TypeConcretization =
|
||||
(key : AssemblyName * string * string)
|
||||
: ConcreteTypeHandle option
|
||||
=
|
||||
let (asm, ns, name) = key
|
||||
findExistingType concreteTypes asm ns name []
|
||||
let asm, ns, name = key
|
||||
findExistingType concreteTypes asm ns name ImmutableArray.Empty
|
||||
|
||||
// Helper function to create and add a ConcreteType to the context
|
||||
let private createAndAddConcreteType
|
||||
(ctx : ConcretizationContext)
|
||||
(ctx : ConcretizationContext<'corelib>)
|
||||
(assembly : AssemblyName)
|
||||
(definition : ComparableTypeDefinitionHandle)
|
||||
(ns : string)
|
||||
(name : string)
|
||||
(generics : ConcreteTypeHandle list)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
(generics : ConcreteTypeHandle ImmutableArray)
|
||||
: ConcreteTypeHandle * ConcretizationContext<'corelib>
|
||||
=
|
||||
let concreteType =
|
||||
{
|
||||
@@ -150,12 +465,11 @@ module TypeConcretization =
|
||||
|
||||
// Helper function for assembly loading with retry pattern
|
||||
let private loadAssemblyAndResolveTypeRef
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly)
|
||||
(ctx : ConcretizationContext)
|
||||
(loadAssembly : IAssemblyLoad)
|
||||
(ctx : ConcretizationContext<'corelib>)
|
||||
(currentAssembly : AssemblyName)
|
||||
(typeRef : TypeRef)
|
||||
: (DumpedAssembly * WoofWare.PawPrint.TypeInfo<_, _>) * ConcretizationContext
|
||||
: (DumpedAssembly * WoofWare.PawPrint.TypeInfo<_, _>) * ConcretizationContext<'corelib>
|
||||
=
|
||||
let currentAssy =
|
||||
match ctx.LoadedAssemblies.TryGetValue currentAssembly.FullName with
|
||||
@@ -164,7 +478,7 @@ module TypeConcretization =
|
||||
|
||||
// First try to resolve without loading new assemblies
|
||||
let resolutionResult =
|
||||
Assembly.resolveTypeRef ctx.LoadedAssemblies currentAssy typeRef ImmutableArray.Empty
|
||||
Assembly.resolveTypeRef ctx.LoadedAssemblies currentAssy ImmutableArray.Empty typeRef
|
||||
|
||||
match resolutionResult with
|
||||
| TypeResolutionResult.Resolved (targetAssy, typeInfo) -> (targetAssy, typeInfo), ctx
|
||||
@@ -172,7 +486,8 @@ module TypeConcretization =
|
||||
// Need to load the assembly
|
||||
match typeRef.ResolutionScope with
|
||||
| TypeRefResolutionScope.Assembly assyRef ->
|
||||
let newAssemblies, loadedAssy = loadAssembly currentAssembly assyRef
|
||||
let newAssemblies, _ =
|
||||
loadAssembly.LoadAssembly ctx.LoadedAssemblies currentAssembly assyRef
|
||||
|
||||
let newCtx =
|
||||
{ ctx with
|
||||
@@ -181,7 +496,7 @@ module TypeConcretization =
|
||||
|
||||
// Now try to resolve again with the loaded assembly
|
||||
let resolutionResult2 =
|
||||
Assembly.resolveTypeRef newCtx.LoadedAssemblies currentAssy typeRef ImmutableArray.Empty
|
||||
Assembly.resolveTypeRef newCtx.LoadedAssemblies currentAssy ImmutableArray.Empty typeRef
|
||||
|
||||
match resolutionResult2 with
|
||||
| TypeResolutionResult.Resolved (targetAssy, typeInfo) -> (targetAssy, typeInfo), newCtx
|
||||
@@ -190,9 +505,9 @@ module TypeConcretization =
|
||||
| _ -> failwith "Unexpected resolution scope"
|
||||
|
||||
let private concretizePrimitive
|
||||
(ctx : ConcretizationContext)
|
||||
(ctx : ConcretizationContext<'corelib>)
|
||||
(prim : PrimitiveType)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
: ConcreteTypeHandle * ConcretizationContext<'corelib>
|
||||
=
|
||||
|
||||
// Get the TypeInfo for this primitive from BaseClassTypes
|
||||
@@ -229,13 +544,13 @@ module TypeConcretization =
|
||||
(ComparableTypeDefinitionHandle.Make typeInfo.TypeDefHandle)
|
||||
typeInfo.Namespace
|
||||
typeInfo.Name
|
||||
[] // Primitives have no generic parameters
|
||||
ImmutableArray.Empty // Primitives have no generic parameters
|
||||
|
||||
let private concretizeArray
|
||||
(ctx : ConcretizationContext)
|
||||
(ctx : ConcretizationContext<'corelib>)
|
||||
(elementHandle : ConcreteTypeHandle)
|
||||
(shape : 'a)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
: ConcreteTypeHandle * ConcretizationContext<'corelib>
|
||||
=
|
||||
|
||||
// Arrays are System.Array<T> where T is the element type
|
||||
@@ -248,7 +563,7 @@ module TypeConcretization =
|
||||
arrayTypeInfo.Assembly
|
||||
arrayTypeInfo.Namespace
|
||||
arrayTypeInfo.Name
|
||||
[ elementHandle ]
|
||||
(ImmutableArray.Create elementHandle)
|
||||
with
|
||||
| Some handle -> handle, ctx
|
||||
| None ->
|
||||
@@ -259,12 +574,12 @@ module TypeConcretization =
|
||||
(ComparableTypeDefinitionHandle.Make arrayTypeInfo.TypeDefHandle)
|
||||
arrayTypeInfo.Namespace
|
||||
arrayTypeInfo.Name
|
||||
[ elementHandle ] // Array<T> has one generic parameter
|
||||
(ImmutableArray.Create elementHandle) // Array<T> has one generic parameter
|
||||
|
||||
let private concretizeOneDimArray
|
||||
(ctx : ConcretizationContext)
|
||||
(ctx : ConcretizationContext<'corelib>)
|
||||
(elementHandle : ConcreteTypeHandle)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
: ConcreteTypeHandle * ConcretizationContext<'corelib>
|
||||
=
|
||||
|
||||
// One-dimensional arrays with lower bound 0 are also System.Array<T>
|
||||
@@ -278,7 +593,7 @@ module TypeConcretization =
|
||||
arrayTypeInfo.Assembly
|
||||
arrayTypeInfo.Namespace
|
||||
arrayTypeInfo.Name
|
||||
[ elementHandle ]
|
||||
(ImmutableArray.Create elementHandle)
|
||||
with
|
||||
| Some handle -> handle, ctx
|
||||
| None ->
|
||||
@@ -289,13 +604,13 @@ module TypeConcretization =
|
||||
(ComparableTypeDefinitionHandle.Make arrayTypeInfo.TypeDefHandle)
|
||||
arrayTypeInfo.Namespace
|
||||
arrayTypeInfo.Name
|
||||
[ elementHandle ] // Array<T> has one generic parameter
|
||||
(ImmutableArray.Create elementHandle) // Array<T> has one generic parameter
|
||||
|
||||
let concretizeTypeDefinition
|
||||
(ctx : ConcretizationContext)
|
||||
(ctx : ConcretizationContext<'corelib>)
|
||||
(assemblyName : AssemblyName)
|
||||
(typeDefHandle : ComparableTypeDefinitionHandle)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
: ConcreteTypeHandle * ConcretizationContext<'corelib>
|
||||
=
|
||||
|
||||
// Look up the type definition in the assembly
|
||||
@@ -315,19 +630,24 @@ module TypeConcretization =
|
||||
typeInfo.Generics.Length
|
||||
|
||||
// Check if we've already concretized this type
|
||||
match findExistingType ctx.ConcreteTypes assemblyName typeInfo.Namespace typeInfo.Name [] with
|
||||
match findExistingType ctx.ConcreteTypes assemblyName typeInfo.Namespace typeInfo.Name ImmutableArray.Empty with
|
||||
| Some handle -> handle, ctx
|
||||
| None ->
|
||||
// Create and add the concrete type (no generic arguments since it's not generic)
|
||||
createAndAddConcreteType ctx assemblyName typeDefHandle typeInfo.Namespace typeInfo.Name [] // No generic parameters
|
||||
createAndAddConcreteType
|
||||
ctx
|
||||
assemblyName
|
||||
typeDefHandle
|
||||
typeInfo.Namespace
|
||||
typeInfo.Name
|
||||
ImmutableArray.Empty // No generic parameters
|
||||
|
||||
let private concretizeTypeReference
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly)
|
||||
(ctx : ConcretizationContext)
|
||||
(loadAssembly : IAssemblyLoad)
|
||||
(ctx : ConcretizationContext<'corelib>)
|
||||
(currentAssembly : AssemblyName)
|
||||
(typeRef : TypeRef)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
: ConcreteTypeHandle * ConcretizationContext<'corelib>
|
||||
=
|
||||
// Use the helper to load assembly and resolve the type reference
|
||||
let (targetAssy, typeInfo), ctx =
|
||||
@@ -346,14 +666,13 @@ module TypeConcretization =
|
||||
|
||||
/// Concretize a type in a specific generic context
|
||||
let rec concretizeType
|
||||
(ctx : ConcretizationContext)
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
|
||||
(ctx : ConcretizationContext<DumpedAssembly>)
|
||||
(loadAssembly : IAssemblyLoad)
|
||||
(assembly : AssemblyName)
|
||||
(typeGenerics : ConcreteTypeHandle ImmutableArray)
|
||||
(methodGenerics : ConcreteTypeHandle ImmutableArray)
|
||||
(typeGenerics : ImmutableArray<ConcreteTypeHandle>)
|
||||
(methodGenerics : ImmutableArray<ConcreteTypeHandle>)
|
||||
(typeDefn : TypeDefn)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
: ConcreteTypeHandle * ConcretizationContext<DumpedAssembly>
|
||||
=
|
||||
|
||||
let key = (assembly, typeDefn)
|
||||
@@ -382,13 +701,13 @@ module TypeConcretization =
|
||||
if index < typeGenerics.Length then
|
||||
typeGenerics.[index], ctx
|
||||
else
|
||||
failwithf "Generic type parameter %d out of range" index
|
||||
raise (IndexOutOfRangeException $"Generic type parameter %i{index}")
|
||||
|
||||
| TypeDefn.GenericMethodParameter index ->
|
||||
if index < methodGenerics.Length then
|
||||
methodGenerics.[index], ctx
|
||||
else
|
||||
failwithf "Generic method parameter %d out of range" index
|
||||
raise (IndexOutOfRangeException $"Generic method parameter %i{index}")
|
||||
|
||||
| TypeDefn.GenericInstantiation (genericDef, args) ->
|
||||
concretizeGenericInstantiation ctx loadAssembly assembly typeGenerics methodGenerics genericDef args
|
||||
@@ -422,7 +741,12 @@ module TypeConcretization =
|
||||
let voidTypeInfo = ctx.BaseTypes.Void
|
||||
|
||||
match
|
||||
findExistingType ctx.ConcreteTypes voidTypeInfo.Assembly voidTypeInfo.Namespace voidTypeInfo.Name []
|
||||
findExistingType
|
||||
ctx.ConcreteTypes
|
||||
voidTypeInfo.Assembly
|
||||
voidTypeInfo.Namespace
|
||||
voidTypeInfo.Name
|
||||
ImmutableArray.Empty
|
||||
with
|
||||
| Some handle -> handle, ctx
|
||||
| None ->
|
||||
@@ -433,20 +757,19 @@ module TypeConcretization =
|
||||
(ComparableTypeDefinitionHandle.Make voidTypeInfo.TypeDefHandle)
|
||||
voidTypeInfo.Namespace
|
||||
voidTypeInfo.Name
|
||||
[] // Void has no generic parameters
|
||||
ImmutableArray.Empty // Void has no generic parameters
|
||||
|
||||
| _ -> failwithf "TODO: Concretization of %A not implemented" typeDefn
|
||||
|
||||
and private concretizeGenericInstantiation
|
||||
(ctx : ConcretizationContext)
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
|
||||
(ctx : ConcretizationContext<DumpedAssembly>)
|
||||
(loadAssembly : IAssemblyLoad)
|
||||
(assembly : AssemblyName)
|
||||
(typeGenerics : ConcreteTypeHandle ImmutableArray)
|
||||
(methodGenerics : ConcreteTypeHandle ImmutableArray)
|
||||
(typeGenerics : ImmutableArray<ConcreteTypeHandle>)
|
||||
(methodGenerics : ImmutableArray<ConcreteTypeHandle>)
|
||||
(genericDef : TypeDefn)
|
||||
(args : ImmutableArray<TypeDefn>)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
: ConcreteTypeHandle * ConcretizationContext<DumpedAssembly>
|
||||
=
|
||||
// First, concretize all type arguments
|
||||
let argHandles, ctxAfterArgs =
|
||||
@@ -460,7 +783,7 @@ module TypeConcretization =
|
||||
)
|
||||
([], ctx)
|
||||
|
||||
let argHandles = argHandles |> List.rev
|
||||
let argHandles = argHandles |> Seq.rev |> ImmutableArray.CreateRange
|
||||
|
||||
// Get the base type definition
|
||||
let baseAssembly, baseTypeDefHandle, baseNamespace, baseName, ctxAfterArgs =
|
||||
@@ -519,7 +842,8 @@ module TypeConcretization =
|
||||
|
||||
| false, _ ->
|
||||
// Need to load the assembly
|
||||
let newAssemblies, loadedAssy = loadAssembly assembly assyRef
|
||||
let newAssemblies, loadedAssy =
|
||||
loadAssembly.LoadAssembly ctx.LoadedAssemblies assembly assyRef
|
||||
|
||||
let ctxWithNewAssy =
|
||||
{ ctxAfterArgs with
|
||||
@@ -597,17 +921,16 @@ module Concretization =
|
||||
|
||||
/// Helper to concretize an array of types
|
||||
let private concretizeTypeArray
|
||||
(ctx : TypeConcretization.ConcretizationContext)
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
|
||||
(ctx : TypeConcretization.ConcretizationContext<DumpedAssembly>)
|
||||
(loadAssembly : IAssemblyLoad)
|
||||
(assembly : AssemblyName)
|
||||
(typeArgs : ConcreteTypeHandle ImmutableArray)
|
||||
(methodArgs : ConcreteTypeHandle ImmutableArray)
|
||||
(typeArgs : ImmutableArray<ConcreteTypeHandle>)
|
||||
(methodArgs : ImmutableArray<ConcreteTypeHandle>)
|
||||
(types : ImmutableArray<TypeDefn>)
|
||||
: ImmutableArray<ConcreteTypeHandle> * TypeConcretization.ConcretizationContext
|
||||
: ImmutableArray<ConcreteTypeHandle> * TypeConcretization.ConcretizationContext<DumpedAssembly>
|
||||
=
|
||||
|
||||
let handles = ImmutableArray.CreateBuilder (types.Length)
|
||||
let handles = ImmutableArray.CreateBuilder types.Length
|
||||
let mutable ctx = ctx
|
||||
|
||||
for i = 0 to types.Length - 1 do
|
||||
@@ -621,14 +944,13 @@ module Concretization =
|
||||
|
||||
/// Helper to concretize a method signature
|
||||
let private concretizeMethodSignature
|
||||
(ctx : TypeConcretization.ConcretizationContext)
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
|
||||
(ctx : TypeConcretization.ConcretizationContext<DumpedAssembly>)
|
||||
(loadAssembly : IAssemblyLoad)
|
||||
(assembly : AssemblyName)
|
||||
(typeArgs : ConcreteTypeHandle ImmutableArray)
|
||||
(methodArgs : ConcreteTypeHandle ImmutableArray)
|
||||
(typeArgs : ImmutableArray<ConcreteTypeHandle>)
|
||||
(methodArgs : ImmutableArray<ConcreteTypeHandle>)
|
||||
(signature : TypeMethodSignature<TypeDefn>)
|
||||
: TypeMethodSignature<ConcreteTypeHandle> * TypeConcretization.ConcretizationContext
|
||||
: TypeMethodSignature<ConcreteTypeHandle> * TypeConcretization.ConcretizationContext<DumpedAssembly>
|
||||
=
|
||||
|
||||
// Concretize return type
|
||||
@@ -643,7 +965,7 @@ module Concretization =
|
||||
let handle, newCtx =
|
||||
TypeConcretization.concretizeType ctx loadAssembly assembly typeArgs methodArgs paramType
|
||||
|
||||
paramHandles.Add (handle)
|
||||
paramHandles.Add handle
|
||||
ctx <- newCtx
|
||||
|
||||
let newSignature =
|
||||
@@ -659,8 +981,7 @@ module Concretization =
|
||||
|
||||
/// Helper to ensure base type assembly is loaded
|
||||
let rec private ensureBaseTypeAssembliesLoaded
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
|
||||
(loadAssembly : IAssemblyLoad)
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(assyName : AssemblyName)
|
||||
(baseTypeInfo : BaseTypeInfo option)
|
||||
@@ -680,7 +1001,7 @@ module Concretization =
|
||||
| true, _ -> assemblies
|
||||
| false, _ ->
|
||||
// Need to load the assembly - pass the assembly that contains the reference
|
||||
let newAssemblies, _ = loadAssembly assy.Name assyRef
|
||||
let newAssemblies, _ = loadAssembly.LoadAssembly assemblies assy.Name assyRef
|
||||
newAssemblies
|
||||
| _ -> assemblies
|
||||
| Some (BaseTypeInfo.TypeDef _)
|
||||
@@ -690,13 +1011,12 @@ module Concretization =
|
||||
/// Concretize a method's signature and body
|
||||
let concretizeMethod
|
||||
(ctx : AllConcreteTypes)
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
|
||||
(loadAssembly : IAssemblyLoad)
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(baseTypes : BaseClassTypes<DumpedAssembly>)
|
||||
(method : WoofWare.PawPrint.MethodInfo<TypeDefn, WoofWare.PawPrint.GenericParameter, TypeDefn>)
|
||||
(typeArgs : ConcreteTypeHandle ImmutableArray)
|
||||
(methodArgs : ConcreteTypeHandle ImmutableArray)
|
||||
(method : WoofWare.PawPrint.MethodInfo<'ty, GenericParamFromMetadata, TypeDefn>)
|
||||
(typeArgs : ImmutableArray<ConcreteTypeHandle>)
|
||||
(methodArgs : ImmutableArray<ConcreteTypeHandle>)
|
||||
: WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle> *
|
||||
AllConcreteTypes *
|
||||
ImmutableDictionary<string, DumpedAssembly>
|
||||
@@ -826,10 +1146,9 @@ module Concretization =
|
||||
// Map generics to handles
|
||||
let genericHandles =
|
||||
method.Generics
|
||||
|> Seq.mapi (fun i _ -> methodArgs.[i])
|
||||
|> ImmutableArray.CreateRange
|
||||
|> ImmutableArray.map (fun (gp, md) -> methodArgs.[gp.SequenceNumber])
|
||||
|
||||
let concretizedMethod : MethodInfo<_, _, ConcreteTypeHandle> =
|
||||
let concretizedMethod : MethodInfo<_, _, _> =
|
||||
{
|
||||
DeclaringType = concretizedDeclaringType
|
||||
Handle = method.Handle
|
||||
@@ -891,8 +1210,7 @@ module Concretization =
|
||||
// Recursively convert generic arguments
|
||||
let genericArgs =
|
||||
concreteType.Generics
|
||||
|> List.map (fun h -> concreteHandleToTypeDefn baseClassTypes h concreteTypes assemblies)
|
||||
|> ImmutableArray.CreateRange
|
||||
|> ImmutableArray.map (fun h -> concreteHandleToTypeDefn baseClassTypes h concreteTypes assemblies)
|
||||
|
||||
let baseDef =
|
||||
TypeDefn.FromDefinition (concreteType.Definition, concreteType.Assembly.FullName, signatureTypeKind)
|
||||
|
@@ -6,6 +6,8 @@ open System.Reflection.Metadata
|
||||
open System.Reflection.Metadata.Ecma335
|
||||
open Microsoft.FSharp.Core
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
[<NoComparison>]
|
||||
type ResolvedBaseType =
|
||||
| Enum
|
||||
| ValueType
|
||||
@@ -149,6 +151,28 @@ type PrimitiveType =
|
||||
| PrimitiveType.UIntPtr -> "uintptr"
|
||||
| PrimitiveType.Object -> "obj"
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module PrimitiveType =
|
||||
let sizeOf (pt : PrimitiveType) : int =
|
||||
match pt with
|
||||
| PrimitiveType.Boolean -> 1
|
||||
| PrimitiveType.Char -> 2
|
||||
| PrimitiveType.SByte -> 1
|
||||
| PrimitiveType.Byte -> 1
|
||||
| PrimitiveType.Int16 -> 2
|
||||
| PrimitiveType.UInt16 -> 2
|
||||
| PrimitiveType.Int32 -> 4
|
||||
| PrimitiveType.UInt32 -> 4
|
||||
| PrimitiveType.Int64 -> 8
|
||||
| PrimitiveType.UInt64 -> 8
|
||||
| PrimitiveType.Single -> 4
|
||||
| PrimitiveType.Double -> 8
|
||||
| PrimitiveType.String -> 8
|
||||
| PrimitiveType.TypedReference -> failwith "todo"
|
||||
| PrimitiveType.IntPtr -> NATIVE_INT_SIZE
|
||||
| PrimitiveType.UIntPtr -> NATIVE_INT_SIZE
|
||||
| PrimitiveType.Object -> 8
|
||||
|
||||
type TypeDefn =
|
||||
| PrimitiveType of PrimitiveType
|
||||
// TODO: array shapes
|
||||
|
@@ -1,5 +1,6 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection
|
||||
@@ -19,6 +20,19 @@ type MethodImplParsed =
|
||||
| MethodImplementation of MethodImplementationHandle
|
||||
| MethodDefinition of MethodDefinitionHandle
|
||||
|
||||
type InterfaceImplementation =
|
||||
{
|
||||
/// TypeDefinition, TypeReference, or TypeSpecification
|
||||
InterfaceHandle : MetadataToken
|
||||
|
||||
/// The assembly which InterfaceHandle is relative to
|
||||
RelativeToAssembly : AssemblyName
|
||||
}
|
||||
|
||||
type Layout =
|
||||
| Default
|
||||
| Custom of size : int * packingSize : int
|
||||
|
||||
/// <summary>
|
||||
/// Represents detailed information about a type definition in a .NET assembly.
|
||||
/// This is a strongly-typed representation of TypeDefinition from System.Reflection.Metadata.
|
||||
@@ -34,7 +48,7 @@ type TypeInfo<'generic, 'fieldGeneric> =
|
||||
/// <summary>
|
||||
/// All methods defined within this type.
|
||||
/// </summary>
|
||||
Methods : WoofWare.PawPrint.MethodInfo<FakeUnit, WoofWare.PawPrint.GenericParameter, TypeDefn> list
|
||||
Methods : WoofWare.PawPrint.MethodInfo<GenericParamFromMetadata, GenericParamFromMetadata, TypeDefn> list
|
||||
|
||||
/// <summary>
|
||||
/// Method implementation mappings for this type, often used for interface implementations
|
||||
@@ -45,7 +59,7 @@ type TypeInfo<'generic, 'fieldGeneric> =
|
||||
/// <summary>
|
||||
/// Fields defined in this type.
|
||||
/// </summary>
|
||||
Fields : WoofWare.PawPrint.FieldInfo<FakeUnit, 'fieldGeneric> list
|
||||
Fields : WoofWare.PawPrint.FieldInfo<GenericParamFromMetadata, 'fieldGeneric> list
|
||||
|
||||
/// <summary>
|
||||
/// The base type that this type inherits from, or None for types that don't have a base type
|
||||
@@ -71,6 +85,8 @@ type TypeInfo<'generic, 'fieldGeneric> =
|
||||
/// </summary>
|
||||
TypeDefHandle : TypeDefinitionHandle
|
||||
|
||||
DeclaringType : TypeDefinitionHandle
|
||||
|
||||
/// <summary>
|
||||
/// The assembly in which this type is defined.
|
||||
/// </summary>
|
||||
@@ -79,8 +95,25 @@ type TypeInfo<'generic, 'fieldGeneric> =
|
||||
Generics : 'generic ImmutableArray
|
||||
|
||||
Events : EventDefn ImmutableArray
|
||||
|
||||
ImplementedInterfaces : InterfaceImplementation ImmutableArray
|
||||
|
||||
Layout : Layout
|
||||
}
|
||||
|
||||
member this.IsInterface = this.TypeAttributes.HasFlag TypeAttributes.Interface
|
||||
|
||||
member this.IsNested =
|
||||
[
|
||||
TypeAttributes.NestedPublic
|
||||
TypeAttributes.NestedPrivate
|
||||
TypeAttributes.NestedFamily
|
||||
TypeAttributes.NestedAssembly
|
||||
TypeAttributes.NestedFamANDAssem
|
||||
TypeAttributes.NestedFamORAssem
|
||||
]
|
||||
|> List.exists this.TypeAttributes.HasFlag
|
||||
|
||||
override this.ToString () =
|
||||
$"%s{this.Assembly.Name}.%s{this.Namespace}.%s{this.Name}"
|
||||
|
||||
@@ -129,36 +162,47 @@ module TypeInfoCrate =
|
||||
type BaseClassTypes<'corelib> =
|
||||
{
|
||||
Corelib : 'corelib
|
||||
String : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Boolean : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Char : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
SByte : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Byte : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Int16 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
UInt16 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Int32 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
UInt32 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Int64 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
UInt64 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Single : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Double : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Array : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Enum : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
ValueType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
DelegateType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Object : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
RuntimeMethodHandle : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
RuntimeFieldHandle : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
RuntimeTypeHandle : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
RuntimeType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Void : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
TypedReference : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
IntPtr : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
UIntPtr : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
String : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
Boolean : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
Char : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
SByte : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
Byte : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
Int16 : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
UInt16 : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
Int32 : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
UInt32 : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
Int64 : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
UInt64 : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
Single : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
Double : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
Array : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
Enum : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
ValueType : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
DelegateType : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
Object : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
RuntimeMethodHandle : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
RuntimeFieldHandle : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
RuntimeTypeHandle : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
RuntimeFieldInfoStub : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
RuntimeFieldHandleInternal : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
RuntimeType : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
Void : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
TypedReference : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
IntPtr : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
UIntPtr : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module TypeInfo =
|
||||
let rec fullName (get : TypeDefinitionHandle -> TypeInfo<_, _>) (ty : TypeInfo<'a, 'b>) =
|
||||
if ty.IsNested then
|
||||
let parent = get ty.DeclaringType |> fullName get
|
||||
$"%s{parent}.{ty.Name}"
|
||||
else if not (String.IsNullOrEmpty ty.Namespace) then
|
||||
$"{ty.Namespace}.{ty.Name}"
|
||||
else
|
||||
ty.Name
|
||||
|
||||
let withGenerics<'a, 'b, 'field> (gen : 'b ImmutableArray) (t : TypeInfo<'a, 'field>) : TypeInfo<'b, 'field> =
|
||||
{
|
||||
Namespace = t.Namespace
|
||||
@@ -170,13 +214,16 @@ module TypeInfo =
|
||||
TypeAttributes = t.TypeAttributes
|
||||
Attributes = t.Attributes
|
||||
TypeDefHandle = t.TypeDefHandle
|
||||
DeclaringType = t.DeclaringType
|
||||
Assembly = t.Assembly
|
||||
Generics = gen
|
||||
Events = t.Events
|
||||
ImplementedInterfaces = t.ImplementedInterfaces
|
||||
Layout = t.Layout
|
||||
}
|
||||
|
||||
let mapGeneric<'a, 'b, 'field> (f : int -> 'a -> 'b) (t : TypeInfo<'a, 'field>) : TypeInfo<'b, 'field> =
|
||||
withGenerics (t.Generics |> Seq.mapi f |> ImmutableArray.CreateRange) t
|
||||
let mapGeneric<'a, 'b, 'field> (f : 'a -> 'b) (t : TypeInfo<'a, 'field>) : TypeInfo<'b, 'field> =
|
||||
withGenerics (t.Generics |> ImmutableArray.map f) t
|
||||
|
||||
let internal read
|
||||
(loggerFactory : ILoggerFactory)
|
||||
@@ -184,9 +231,10 @@ module TypeInfo =
|
||||
(thisAssembly : AssemblyName)
|
||||
(metadataReader : MetadataReader)
|
||||
(typeHandle : TypeDefinitionHandle)
|
||||
: TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
: TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
=
|
||||
let typeDef = metadataReader.GetTypeDefinition typeHandle
|
||||
let declaringType = typeDef.GetDeclaringType ()
|
||||
let methods = typeDef.GetMethods ()
|
||||
|
||||
let methodImpls =
|
||||
@@ -253,6 +301,28 @@ module TypeInfo =
|
||||
|
||||
result.ToImmutable ()
|
||||
|
||||
let interfaces =
|
||||
let result = ImmutableArray.CreateBuilder ()
|
||||
|
||||
for i in typeDef.GetInterfaceImplementations () do
|
||||
let impl = metadataReader.GetInterfaceImplementation i
|
||||
|
||||
{
|
||||
InterfaceHandle = MetadataToken.ofEntityHandle impl.Interface
|
||||
RelativeToAssembly = thisAssembly
|
||||
}
|
||||
|> result.Add
|
||||
|
||||
result.ToImmutable ()
|
||||
|
||||
let layout =
|
||||
let l = typeDef.GetLayout ()
|
||||
|
||||
if l.IsDefault then
|
||||
Layout.Default
|
||||
else
|
||||
Layout.Custom (size = l.Size, packingSize = l.PackingSize)
|
||||
|
||||
{
|
||||
Namespace = ns
|
||||
Name = name
|
||||
@@ -266,6 +336,9 @@ module TypeInfo =
|
||||
Assembly = thisAssembly
|
||||
Generics = genericParams
|
||||
Events = events
|
||||
ImplementedInterfaces = interfaces
|
||||
DeclaringType = declaringType
|
||||
Layout = layout
|
||||
}
|
||||
|
||||
let isBaseType<'corelib>
|
||||
@@ -291,10 +364,10 @@ module TypeInfo =
|
||||
|
||||
let rec resolveBaseType<'corelib, 'generic, 'field>
|
||||
(baseClassTypes : BaseClassTypes<'corelib>)
|
||||
(sourceAssy : 'corelib)
|
||||
(getName : 'corelib -> AssemblyName)
|
||||
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic, 'field>)
|
||||
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic, 'field>)
|
||||
(sourceAssembly : AssemblyName)
|
||||
(getTypeRef : 'corelib -> TypeReferenceHandle -> 'corelib * TypeInfo<'generic, 'field>)
|
||||
(value : BaseTypeInfo option)
|
||||
: ResolvedBaseType
|
||||
=
|
||||
@@ -304,40 +377,48 @@ module TypeInfo =
|
||||
|
||||
match value with
|
||||
| BaseTypeInfo.TypeDef typeDefinitionHandle ->
|
||||
match isBaseType baseClassTypes getName sourceAssembly typeDefinitionHandle with
|
||||
match isBaseType baseClassTypes getName (getName sourceAssy) typeDefinitionHandle with
|
||||
| Some x -> x
|
||||
| None ->
|
||||
let baseType = getTypeDef baseClassTypes.Corelib typeDefinitionHandle
|
||||
resolveBaseType baseClassTypes getName getTypeDef getTypeRef sourceAssembly baseType.BaseType
|
||||
resolveBaseType baseClassTypes sourceAssy getName getTypeDef getTypeRef baseType.BaseType
|
||||
| BaseTypeInfo.TypeRef typeReferenceHandle ->
|
||||
let typeRef = getTypeRef baseClassTypes.Corelib typeReferenceHandle
|
||||
failwith $"{typeRef}"
|
||||
let targetAssy, typeRef = getTypeRef sourceAssy typeReferenceHandle
|
||||
|
||||
match isBaseType baseClassTypes getName (getName targetAssy) typeRef.TypeDefHandle with
|
||||
| Some x -> x
|
||||
| None ->
|
||||
let baseType = getTypeDef baseClassTypes.Corelib typeRef.TypeDefHandle
|
||||
resolveBaseType baseClassTypes sourceAssy getName getTypeDef getTypeRef baseType.BaseType
|
||||
| BaseTypeInfo.TypeSpec typeSpecificationHandle -> failwith "todo"
|
||||
| BaseTypeInfo.ForeignAssemblyType (assemblyName, typeDefinitionHandle) ->
|
||||
resolveBaseType
|
||||
baseClassTypes
|
||||
sourceAssy
|
||||
getName
|
||||
getTypeDef
|
||||
getTypeRef
|
||||
assemblyName
|
||||
(Some (BaseTypeInfo.TypeDef typeDefinitionHandle))
|
||||
|
||||
let toTypeDefn
|
||||
(corelib : BaseClassTypes<'corelib>)
|
||||
(baseClassTypes : BaseClassTypes<'corelib>)
|
||||
(assemblies : AssemblyName -> 'corelib)
|
||||
(getName : 'corelib -> AssemblyName)
|
||||
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic, 'field>)
|
||||
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic, 'field>)
|
||||
(getTypeRef : 'corelib -> TypeReferenceHandle -> 'corelib * TypeInfo<'generic, 'field>)
|
||||
(ty : TypeInfo<TypeDefn, TypeDefn>)
|
||||
: TypeDefn
|
||||
=
|
||||
let stk =
|
||||
match resolveBaseType corelib getName getTypeDef getTypeRef ty.Assembly ty.BaseType with
|
||||
match resolveBaseType baseClassTypes (assemblies ty.Assembly) getName getTypeDef getTypeRef ty.BaseType with
|
||||
| ResolvedBaseType.Enum
|
||||
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
||||
| ResolvedBaseType.Object
|
||||
| ResolvedBaseType.Delegate -> SignatureTypeKind.Class
|
||||
|
||||
let defn =
|
||||
// The only allowed construction of FromDefinition!
|
||||
// All other constructions should use DumpedAssembly.typeInfoToTypeDefn.
|
||||
TypeDefn.FromDefinition (ComparableTypeDefinitionHandle.Make ty.TypeDefHandle, ty.Assembly.FullName, stk)
|
||||
|
||||
if ty.Generics.IsEmpty then
|
||||
|
@@ -7,13 +7,17 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="StringToken.fs" />
|
||||
<Compile Include="Constants.fs" />
|
||||
<Compile Include="ImmutableArray.fs" />
|
||||
<Compile Include="Tokens.fs" />
|
||||
<Compile Include="TypeRef.fs" />
|
||||
<Compile Include="IlOp.fs" />
|
||||
<Compile Include="CustomAttribute.fs" />
|
||||
<Compile Include="GenericParameter.fs" />
|
||||
<Compile Include="AssemblyReference.fs" />
|
||||
<Compile Include="EventDefn.fs" />
|
||||
<Compile Include="ComparableTypeDefinitionHandle.fs" />
|
||||
<Compile Include="ComparableFieldDefinitionHandle.fs" />
|
||||
<Compile Include="ComparableSignatureHeader.fs" />
|
||||
<Compile Include="TypeDefn.fs" />
|
||||
<Compile Include="ConcreteType.fs" />
|
||||
|
@@ -25,10 +25,9 @@ module MockEnv =
|
||||
System_Threading_Monitor = System_Threading_MonitorMock.Empty
|
||||
}
|
||||
|
||||
type TestCase =
|
||||
type EndToEndTestCase =
|
||||
{
|
||||
FileName : string
|
||||
ExpectedReturnCode : int
|
||||
NativeImpls : NativeImpls
|
||||
LocalVariablesOfMain : CliType list option
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
namespace WoofWare.Pawprint.Test
|
||||
|
||||
open System
|
||||
open System.Collections.Immutable
|
||||
open System.IO
|
||||
open FsUnitTyped
|
||||
@@ -20,11 +21,10 @@ module TestImpureCases =
|
||||
FileName = "WriteLine.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = NativeImpls.PassThru ()
|
||||
LocalVariablesOfMain = [] |> Some
|
||||
}
|
||||
]
|
||||
|
||||
let cases : TestCase list =
|
||||
let cases : EndToEndTestCase list =
|
||||
[
|
||||
{
|
||||
FileName = "InstaQuit.cs"
|
||||
@@ -47,12 +47,10 @@ module TestImpureCases =
|
||||
ExecutionResult.Terminated (state, thread)
|
||||
}
|
||||
}
|
||||
LocalVariablesOfMain = [] |> Some
|
||||
}
|
||||
]
|
||||
|
||||
[<TestCaseSource(nameof cases)>]
|
||||
let ``Can evaluate C# files`` (case : TestCase) : unit =
|
||||
let runTest (case : EndToEndTestCase) : unit =
|
||||
let source = Assembly.getEmbeddedResourceAsString case.FileName assy
|
||||
let image = Roslyn.compile [ source ]
|
||||
let messages, loggerFactory = LoggerFactory.makeTest ()
|
||||
@@ -75,15 +73,6 @@ module TestImpureCases =
|
||||
| ret -> failwith $"expected program to return an int, but it returned %O{ret}"
|
||||
|
||||
exitCode |> shouldEqual case.ExpectedReturnCode
|
||||
|
||||
let finalVariables =
|
||||
terminalState.ThreadState.[terminatingThread].MethodState.LocalVariables
|
||||
|> Seq.toList
|
||||
|
||||
match case.LocalVariablesOfMain with
|
||||
| None -> ()
|
||||
| Some expected -> finalVariables |> shouldEqual expected
|
||||
|
||||
with _ ->
|
||||
for message in messages () do
|
||||
System.Console.Error.WriteLine $"{message}"
|
||||
@@ -91,33 +80,8 @@ module TestImpureCases =
|
||||
reraise ()
|
||||
|
||||
[<TestCaseSource(nameof unimplemented)>]
|
||||
[<Explicit "not yet implemented">]
|
||||
let ``Can evaluate C# files, unimplemented`` (case : TestCase) : unit =
|
||||
let source = Assembly.getEmbeddedResourceAsString case.FileName assy
|
||||
let image = Roslyn.compile [ source ]
|
||||
let messages, loggerFactory = LoggerFactory.makeTest ()
|
||||
[<Explicit>]
|
||||
let ``Can evaluate C# files, unimplemented`` (case : EndToEndTestCase) = runTest case
|
||||
|
||||
let dotnetRuntimes =
|
||||
DotnetRuntime.SelectForDll assy.Location |> ImmutableArray.CreateRange
|
||||
|
||||
use peImage = new MemoryStream (image)
|
||||
|
||||
try
|
||||
let terminalState, terminatingThread =
|
||||
Program.run loggerFactory (Some case.FileName) peImage dotnetRuntimes case.NativeImpls []
|
||||
|
||||
let exitCode =
|
||||
match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with
|
||||
| [] -> failwith "expected program to return a value, but it returned void"
|
||||
| head :: _ ->
|
||||
match head with
|
||||
| EvalStackValue.Int32 i -> i
|
||||
| ret -> failwith $"expected program to return an int, but it returned %O{ret}"
|
||||
|
||||
exitCode |> shouldEqual case.ExpectedReturnCode
|
||||
|
||||
with _ ->
|
||||
for message in messages () do
|
||||
System.Console.Error.WriteLine $"{message}"
|
||||
|
||||
reraise ()
|
||||
[<TestCaseSource(nameof cases)>]
|
||||
let ``Can evaluate C# files`` (case : EndToEndTestCase) = runTest case
|
||||
|
@@ -1,5 +1,6 @@
|
||||
namespace WoofWare.Pawprint.Test
|
||||
|
||||
open System
|
||||
open System.Collections.Immutable
|
||||
open System.IO
|
||||
open FsUnitTyped
|
||||
@@ -16,224 +17,67 @@ module TestPureCases =
|
||||
|
||||
let unimplemented =
|
||||
[
|
||||
{
|
||||
FileName = "CrossAssemblyTypes.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "GenericEdgeCases.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "TestShl.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "TestShr.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "Threads.cs"
|
||||
ExpectedReturnCode = 3
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = [] |> Some
|
||||
}
|
||||
{
|
||||
FileName = "ComplexTryCatch.cs"
|
||||
ExpectedReturnCode = 14
|
||||
NativeImpls = NativeImpls.PassThru ()
|
||||
LocalVariablesOfMain =
|
||||
[
|
||||
4
|
||||
20
|
||||
115
|
||||
12
|
||||
1
|
||||
10
|
||||
2
|
||||
112
|
||||
12
|
||||
1111
|
||||
42
|
||||
99
|
||||
25
|
||||
50
|
||||
123
|
||||
20
|
||||
35
|
||||
5
|
||||
11111
|
||||
100001
|
||||
]
|
||||
|> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i))
|
||||
|> Some
|
||||
}
|
||||
{
|
||||
FileName = "ResizeArray.cs"
|
||||
ExpectedReturnCode = 109
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ] |> Some
|
||||
}
|
||||
"CrossAssemblyTypes.cs"
|
||||
"OverlappingStructs.cs"
|
||||
"AdvancedStructLayout.cs"
|
||||
"InitializeArray.cs"
|
||||
"Threads.cs"
|
||||
"ComplexTryCatch.cs"
|
||||
"ResizeArray.cs"
|
||||
"LdtokenField.cs"
|
||||
"GenericEdgeCases.cs"
|
||||
"UnsafeAs.cs"
|
||||
]
|
||||
|> Set.ofList
|
||||
|
||||
let requiresMocks =
|
||||
let empty = MockEnv.make ()
|
||||
|
||||
let cases : TestCase list =
|
||||
[
|
||||
{
|
||||
FileName = "NoOp.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 1) ] |> Some
|
||||
}
|
||||
{
|
||||
FileName = "StaticVariables.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "Ldind.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain =
|
||||
[
|
||||
// `failures`
|
||||
CliType.Numeric (CliNumericType.Int32 0)
|
||||
// Return value
|
||||
CliType.Numeric (CliNumericType.Int32 0)
|
||||
]
|
||||
|> Some
|
||||
}
|
||||
{
|
||||
FileName = "CustomDelegate.cs"
|
||||
ExpectedReturnCode = 8
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain =
|
||||
[
|
||||
// filter
|
||||
CliType.ObjectRef (Some (ManagedHeapAddress 2))
|
||||
// result
|
||||
CliType.OfBool true
|
||||
// result, cloned for "if(result)" check
|
||||
CliType.OfBool true
|
||||
// ret
|
||||
CliType.Numeric (CliNumericType.Int32 8)
|
||||
]
|
||||
|> Some
|
||||
}
|
||||
{
|
||||
FileName = "ArgumentOrdering.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "BasicLock.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls =
|
||||
let mock = MockEnv.make ()
|
||||
|
||||
{ mock with
|
||||
System_Threading_Monitor = System_Threading_Monitor.passThru
|
||||
}
|
||||
LocalVariablesOfMain =
|
||||
[
|
||||
// Four variables:
|
||||
// locker
|
||||
CliType.ObjectRef (Some (ManagedHeapAddress 2))
|
||||
// a copy of locker, taken so that the contents of the implicit `finally` have a stable copy
|
||||
CliType.ObjectRef (Some (ManagedHeapAddress 2))
|
||||
// out param of `ReliableEnter`
|
||||
CliType.OfBool true
|
||||
// return value
|
||||
CliType.Numeric (CliNumericType.Int32 1)
|
||||
]
|
||||
|> Some
|
||||
}
|
||||
{
|
||||
FileName = "TriangleNumber.cs"
|
||||
ExpectedReturnCode = 10
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain =
|
||||
[
|
||||
// answer
|
||||
CliType.Numeric (CliNumericType.Int32 10)
|
||||
// i
|
||||
CliType.Numeric (CliNumericType.Int32 5)
|
||||
// End-loop condition
|
||||
CliType.OfBool false
|
||||
// Ret
|
||||
CliType.Numeric (CliNumericType.Int32 10)
|
||||
]
|
||||
|> Some
|
||||
}
|
||||
{
|
||||
FileName = "ExceptionWithNoOpFinally.cs"
|
||||
ExpectedReturnCode = 3
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain =
|
||||
[
|
||||
// Variable 1 is `x`, variable 2 is the implicit return value
|
||||
4
|
||||
3
|
||||
]
|
||||
|> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i))
|
||||
|> Some
|
||||
}
|
||||
{
|
||||
FileName = "ExceptionWithNoOpCatch.cs"
|
||||
ExpectedReturnCode = 10
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ] |> Some
|
||||
}
|
||||
{
|
||||
FileName = "Floats.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "TryCatchWithThrowInBody.cs"
|
||||
ExpectedReturnCode = 4
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain =
|
||||
[
|
||||
// one variable is x, one variable is the return value which also happens to have the same value
|
||||
4
|
||||
4
|
||||
]
|
||||
|> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i))
|
||||
|> Some
|
||||
}
|
||||
{
|
||||
FileName = "Ldelema.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "TypeConcretization.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "TestOr.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
"BasicLock.cs",
|
||||
(1,
|
||||
{ empty with
|
||||
System_Threading_Monitor = System_Threading_Monitor.passThru
|
||||
})
|
||||
]
|
||||
|> Map.ofList
|
||||
|
||||
[<TestCaseSource(nameof cases)>]
|
||||
let ``Can evaluate C# files`` (case : TestCase) : unit =
|
||||
let customExitCodes =
|
||||
[
|
||||
"NoOp.cs", 1
|
||||
"CustomDelegate.cs", 8
|
||||
"ExceptionWithNoOpFinally.cs", 3
|
||||
"ExceptionWithNoOpCatch.cs", 10
|
||||
"TryCatchWithThrowInBody.cs", 4
|
||||
"ResizeArray.cs", 114
|
||||
"Threads.cs", 3
|
||||
"TriangleNumber.cs", 10
|
||||
]
|
||||
|> Map.ofList
|
||||
|
||||
let allPure =
|
||||
assy.GetManifestResourceNames ()
|
||||
|> Seq.choose (fun res ->
|
||||
let s = "WoofWare.PawPrint.Test.sourcesPure."
|
||||
|
||||
if res.StartsWith (s, StringComparison.OrdinalIgnoreCase) then
|
||||
res.Substring s.Length |> Some
|
||||
else
|
||||
None
|
||||
)
|
||||
|> Set.ofSeq
|
||||
|
||||
let simpleCases : string list =
|
||||
allPure
|
||||
|> Seq.filter (fun s ->
|
||||
(customExitCodes.ContainsKey s
|
||||
|| requiresMocks.ContainsKey s
|
||||
|| unimplemented.Contains s)
|
||||
|> not
|
||||
)
|
||||
|> Seq.toList
|
||||
|
||||
let runTest (case : EndToEndTestCase) : unit =
|
||||
let source = Assembly.getEmbeddedResourceAsString case.FileName assy
|
||||
let image = Roslyn.compile [ source ]
|
||||
let messages, loggerFactory = LoggerFactory.makeTest ()
|
||||
@@ -244,11 +88,12 @@ module TestPureCases =
|
||||
use peImage = new MemoryStream (image)
|
||||
|
||||
try
|
||||
let realResult = RealRuntime.executeWithRealRuntime [||] image
|
||||
realResult.ExitCode |> shouldEqual case.ExpectedReturnCode
|
||||
|
||||
let terminalState, terminatingThread =
|
||||
Program.run loggerFactory (Some case.FileName) peImage dotnetRuntimes case.NativeImpls []
|
||||
|
||||
let realResult = RealRuntime.executeWithRealRuntime [||] image
|
||||
|
||||
let exitCode =
|
||||
match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with
|
||||
| [] -> failwith "expected program to return a value, but it returned void"
|
||||
@@ -259,49 +104,49 @@ module TestPureCases =
|
||||
|
||||
exitCode |> shouldEqual realResult.ExitCode
|
||||
|
||||
exitCode |> shouldEqual case.ExpectedReturnCode
|
||||
|
||||
let finalVariables =
|
||||
terminalState.ThreadState.[terminatingThread].MethodState.LocalVariables
|
||||
|> Seq.toList
|
||||
|
||||
match case.LocalVariablesOfMain with
|
||||
| None -> ()
|
||||
| Some expected -> finalVariables |> shouldEqual expected
|
||||
with _ ->
|
||||
for message in messages () do
|
||||
System.Console.Error.WriteLine $"{message}"
|
||||
|
||||
reraise ()
|
||||
|
||||
[<TestCaseSource(nameof simpleCases)>]
|
||||
let ``Standard tests`` (fileName : string) =
|
||||
{
|
||||
FileName = fileName
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
}
|
||||
|> runTest
|
||||
|
||||
[<TestCaseSource(nameof customExitCodes)>]
|
||||
let ``Custom exit code tests`` (KeyValue (fileName : string, exitCode : int)) =
|
||||
if unimplemented.Contains fileName then
|
||||
Assert.Inconclusive ()
|
||||
|
||||
{
|
||||
FileName = fileName
|
||||
ExpectedReturnCode = exitCode
|
||||
NativeImpls = MockEnv.make ()
|
||||
}
|
||||
|> runTest
|
||||
|
||||
[<TestCaseSource(nameof requiresMocks)>]
|
||||
let ``Tests which require mocks`` (KeyValue (fileName : string, (exitCode : int, mock : NativeImpls))) =
|
||||
{
|
||||
FileName = fileName
|
||||
ExpectedReturnCode = exitCode
|
||||
NativeImpls = mock
|
||||
}
|
||||
|> runTest
|
||||
|
||||
|
||||
[<TestCaseSource(nameof unimplemented)>]
|
||||
[<Explicit "not yet implemented">]
|
||||
let ``Can evaluate C# files, unimplemented`` (case : TestCase) : unit =
|
||||
let source = Assembly.getEmbeddedResourceAsString case.FileName assy
|
||||
let image = Roslyn.compile [ source ]
|
||||
let messages, loggerFactory = LoggerFactory.makeTest ()
|
||||
|
||||
let dotnetRuntimes =
|
||||
DotnetRuntime.SelectForDll assy.Location |> ImmutableArray.CreateRange
|
||||
|
||||
use peImage = new MemoryStream (image)
|
||||
|
||||
try
|
||||
let terminalState, terminatingThread =
|
||||
Program.run loggerFactory (Some case.FileName) peImage dotnetRuntimes case.NativeImpls []
|
||||
|
||||
let exitCode =
|
||||
match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with
|
||||
| [] -> failwith "expected program to return a value, but it returned void"
|
||||
| head :: _ ->
|
||||
match head with
|
||||
| EvalStackValue.Int32 i -> i
|
||||
| ret -> failwith $"expected program to return an int, but it returned %O{ret}"
|
||||
|
||||
exitCode |> shouldEqual case.ExpectedReturnCode
|
||||
|
||||
with _ ->
|
||||
for message in messages () do
|
||||
System.Console.Error.WriteLine $"{message}"
|
||||
|
||||
reraise ()
|
||||
[<Explicit>]
|
||||
let ``Can evaluate C# files, unimplemented`` (fileName : string) =
|
||||
{
|
||||
FileName = fileName
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
}
|
||||
|> runTest
|
||||
|
@@ -4,9 +4,6 @@
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<OutputType>Exe</OutputType>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
|
||||
<EnableNUnitRunner>true</EnableNUnitRunner>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -19,31 +16,8 @@
|
||||
<Compile Include="TestImpureCases.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="sourcesPure\BasicLock.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\Floats.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\NoOp.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\StaticVariables.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\Ldelema.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\ExceptionWithNoOpCatch.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\ExceptionWithNoOpFinally.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\TryCatchWithThrowInBody.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\ComplexTryCatch.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\TriangleNumber.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\Threads.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\ResizeArray.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\ArgumentOrdering.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\TestShl.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\TestShr.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\TestOr.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CustomDelegate.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\Ldind.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\TypeConcretization.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CrossAssemblyTypes.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\GenericEdgeCases.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="sourcesImpure\WriteLine.cs" />
|
||||
<EmbeddedResource Include="sourcesImpure\InstaQuit.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\*.cs" />
|
||||
<EmbeddedResource Include="sourcesImpure\*.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -51,10 +25,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NUnit" Version="4.4.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0"/>
|
||||
<PackageReference Include="FsUnit" Version="7.1.1"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
|
||||
<PackageReference Include="NUnit" Version="4.3.2"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.6" />
|
||||
<PackageReference Include="WoofWare.DotnetRuntimeLocator" Version="0.3.2"/>
|
||||
|
480
WoofWare.PawPrint.Test/sourcesPure/AdvancedStructLayout.cs
Normal file
480
WoofWare.PawPrint.Test/sourcesPure/AdvancedStructLayout.cs
Normal file
@@ -0,0 +1,480 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
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 static ref int GetRef(ref RefReturnStruct s, int index)
|
||||
{
|
||||
if (index == 0) return ref s.A;
|
||||
if (index == 1) return ref s.B;
|
||||
return ref s.C;
|
||||
}
|
||||
}
|
||||
|
||||
static unsafe int TestUnsafePointers()
|
||||
{
|
||||
var s = new PointerTestStruct { A = 0x12345678, B = 0xAB, C = 0x1234, D = unchecked((int)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 RefReturnStruct.GetRef(ref r, 0);
|
||||
if (refA != 10) return 70;
|
||||
|
||||
// Modify through ref
|
||||
refA = 100;
|
||||
if (r.A != 100) return 71;
|
||||
|
||||
ref int refB = ref RefReturnStruct.GetRef(ref r, 1);
|
||||
refB = 200;
|
||||
if (r.B != 200) return 72;
|
||||
|
||||
ref int refC = ref RefReturnStruct.GetRef(ref r, 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(string[] argv)
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
19
WoofWare.PawPrint.Test/sourcesPure/InitializeArray.cs
Normal file
19
WoofWare.PawPrint.Test/sourcesPure/InitializeArray.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace HelloWorldApp
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static int Main(string[] args)
|
||||
{
|
||||
int[] array = new[] { 1, 2, 3 };
|
||||
|
||||
if (array.Sum() != 6)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
503
WoofWare.PawPrint.Test/sourcesPure/Initobj.cs
Normal file
503
WoofWare.PawPrint.Test/sourcesPure/Initobj.cs
Normal file
@@ -0,0 +1,503 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public class TestInitobj
|
||||
{
|
||||
// Simple struct with various primitive types
|
||||
private struct SimpleStruct
|
||||
{
|
||||
public int IntField;
|
||||
public bool BoolField;
|
||||
public char CharField;
|
||||
public double DoubleField;
|
||||
public byte ByteField;
|
||||
}
|
||||
|
||||
// Nested struct
|
||||
private struct NestedStruct
|
||||
{
|
||||
public SimpleStruct Inner;
|
||||
public int OuterField;
|
||||
}
|
||||
|
||||
// Struct with arrays and references
|
||||
private struct ComplexStruct
|
||||
{
|
||||
public object ObjectRef;
|
||||
public string StringRef;
|
||||
public int[] ArrayRef;
|
||||
public int ValueField;
|
||||
}
|
||||
|
||||
// Generic struct
|
||||
private struct GenericStruct<T>
|
||||
{
|
||||
public T Value;
|
||||
public int Count;
|
||||
}
|
||||
|
||||
// Struct for field tests
|
||||
private struct StructWithStructField
|
||||
{
|
||||
public SimpleStruct NestedField;
|
||||
public int OtherField;
|
||||
}
|
||||
|
||||
// Class with struct field for heap test
|
||||
private class ClassWithStructField
|
||||
{
|
||||
public SimpleStruct StructField;
|
||||
public int IntField;
|
||||
}
|
||||
|
||||
// Test 1: Initialize simple struct with local variable
|
||||
public static int Test1()
|
||||
{
|
||||
SimpleStruct s = new SimpleStruct
|
||||
{
|
||||
IntField = 42,
|
||||
BoolField = true,
|
||||
CharField = 'X',
|
||||
DoubleField = 3.14,
|
||||
ByteField = 255
|
||||
};
|
||||
|
||||
// Verify initial values
|
||||
if (s.IntField != 42) return 1;
|
||||
if (s.BoolField != true) return 2;
|
||||
if (s.CharField != 'X') return 3;
|
||||
if (s.DoubleField != 3.14) return 4;
|
||||
if (s.ByteField != 255) return 5;
|
||||
|
||||
// Use default to reset the struct (generates initobj)
|
||||
s = default(SimpleStruct);
|
||||
|
||||
// Verify all fields are zeroed
|
||||
if (s.IntField != 0) return 6;
|
||||
if (s.BoolField != false) return 7;
|
||||
if (s.CharField != '\0') return 8;
|
||||
if (s.DoubleField != 0.0) return 9;
|
||||
if (s.ByteField != 0) return 10;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 2: Initialize nested struct
|
||||
public static int Test2()
|
||||
{
|
||||
NestedStruct n = new NestedStruct
|
||||
{
|
||||
Inner = new SimpleStruct
|
||||
{
|
||||
IntField = 100,
|
||||
BoolField = true,
|
||||
CharField = 'A',
|
||||
DoubleField = 1.23,
|
||||
ByteField = 128
|
||||
},
|
||||
OuterField = 999
|
||||
};
|
||||
|
||||
// Verify initial values
|
||||
if (n.Inner.IntField != 100) return 20;
|
||||
if (n.OuterField != 999) return 21;
|
||||
|
||||
// Reset using default keyword (which should generate initobj)
|
||||
n = default(NestedStruct);
|
||||
|
||||
// Verify all fields are zeroed
|
||||
if (n.Inner.IntField != 0) return 22;
|
||||
if (n.Inner.BoolField != false) return 23;
|
||||
if (n.Inner.CharField != '\0') return 24;
|
||||
if (n.Inner.DoubleField != 0.0) return 25;
|
||||
if (n.Inner.ByteField != 0) return 26;
|
||||
if (n.OuterField != 0) return 27;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 3: Initialize struct with reference types
|
||||
public static int Test3()
|
||||
{
|
||||
ComplexStruct c = new ComplexStruct
|
||||
{
|
||||
ObjectRef = new object(),
|
||||
StringRef = "Hello",
|
||||
ArrayRef = new int[3],
|
||||
ValueField = 42
|
||||
};
|
||||
c.ArrayRef[0] = 1;
|
||||
c.ArrayRef[1] = 2;
|
||||
c.ArrayRef[2] = 3;
|
||||
|
||||
// Verify initial values
|
||||
if (c.ObjectRef == null) return 30;
|
||||
if (c.StringRef != "Hello") return 31;
|
||||
if (c.ArrayRef == null || c.ArrayRef.Length != 3) return 32;
|
||||
if (c.ValueField != 42) return 33;
|
||||
|
||||
// Reset using default
|
||||
c = default(ComplexStruct);
|
||||
|
||||
// Verify references are null and value is zero
|
||||
if (c.ObjectRef != null) return 34;
|
||||
if (c.StringRef != null) return 35;
|
||||
if (c.ArrayRef != null) return 36;
|
||||
if (c.ValueField != 0) return 37;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 4: Initialize generic struct
|
||||
public static int Test4()
|
||||
{
|
||||
GenericStruct<int> gi = new GenericStruct<int>
|
||||
{
|
||||
Value = 123,
|
||||
Count = 456
|
||||
};
|
||||
|
||||
if (gi.Value != 123) return 40;
|
||||
if (gi.Count != 456) return 41;
|
||||
|
||||
gi = default(GenericStruct<int>);
|
||||
|
||||
if (gi.Value != 0) return 42;
|
||||
if (gi.Count != 0) return 43;
|
||||
|
||||
// Test with reference type
|
||||
GenericStruct<string> gs = new GenericStruct<string>
|
||||
{
|
||||
Value = "Test",
|
||||
Count = 789
|
||||
};
|
||||
|
||||
if (gs.Value != "Test") return 44;
|
||||
if (gs.Count != 789) return 45;
|
||||
|
||||
gs = default(GenericStruct<string>);
|
||||
|
||||
if (gs.Value != null) return 46;
|
||||
if (gs.Count != 0) return 47;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 5: Initialize struct in array element using ref
|
||||
public static int Test5()
|
||||
{
|
||||
SimpleStruct[] array = new SimpleStruct[3];
|
||||
|
||||
// Set values in first element
|
||||
array[0].IntField = 111;
|
||||
array[0].BoolField = true;
|
||||
array[0].CharField = 'Z';
|
||||
|
||||
if (array[0].IntField != 111) return 50;
|
||||
if (array[0].BoolField != true) return 51;
|
||||
if (array[0].CharField != 'Z') return 52;
|
||||
|
||||
// Reset first element using default assignment
|
||||
array[0] = default(SimpleStruct);
|
||||
|
||||
if (array[0].IntField != 0) return 53;
|
||||
if (array[0].BoolField != false) return 54;
|
||||
if (array[0].CharField != '\0') return 55;
|
||||
|
||||
// Also test with ref local
|
||||
array[1].IntField = 222;
|
||||
ref SimpleStruct secondElement = ref array[1];
|
||||
secondElement = default(SimpleStruct);
|
||||
|
||||
if (array[1].IntField != 0) return 56;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 6: Initialize struct through method parameter
|
||||
public static int Test6()
|
||||
{
|
||||
SimpleStruct s = new SimpleStruct
|
||||
{
|
||||
IntField = 200,
|
||||
BoolField = true,
|
||||
CharField = 'M',
|
||||
DoubleField = 2.71,
|
||||
ByteField = 64
|
||||
};
|
||||
|
||||
ResetStruct(ref s);
|
||||
|
||||
if (s.IntField != 0) return 60;
|
||||
if (s.BoolField != false) return 61;
|
||||
if (s.CharField != '\0') return 62;
|
||||
if (s.DoubleField != 0.0) return 63;
|
||||
if (s.ByteField != 0) return 64;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static void ResetStruct(ref SimpleStruct s)
|
||||
{
|
||||
s = default(SimpleStruct);
|
||||
}
|
||||
|
||||
// Test 7: Initialize multiple structs
|
||||
public static int Test7()
|
||||
{
|
||||
SimpleStruct s1 = new SimpleStruct { IntField = 1 };
|
||||
SimpleStruct s2 = new SimpleStruct { IntField = 2 };
|
||||
SimpleStruct s3 = new SimpleStruct { IntField = 3 };
|
||||
|
||||
if (s1.IntField != 1) return 70;
|
||||
if (s2.IntField != 2) return 71;
|
||||
if (s3.IntField != 3) return 72;
|
||||
|
||||
s1 = default(SimpleStruct);
|
||||
s2 = default(SimpleStruct);
|
||||
s3 = default(SimpleStruct);
|
||||
|
||||
if (s1.IntField != 0) return 73;
|
||||
if (s2.IntField != 0) return 74;
|
||||
if (s3.IntField != 0) return 75;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 8: Initialize struct passed as argument (tests Argument case)
|
||||
public static int Test8()
|
||||
{
|
||||
SimpleStruct s = new SimpleStruct
|
||||
{
|
||||
IntField = 333,
|
||||
BoolField = true,
|
||||
CharField = 'Q',
|
||||
DoubleField = 4.56,
|
||||
ByteField = 77
|
||||
};
|
||||
|
||||
int result = InitializeArgumentStruct(ref s);
|
||||
if (result != 0) return result;
|
||||
|
||||
// Verify struct was reset
|
||||
if (s.IntField != 0) return 80;
|
||||
if (s.BoolField != false) return 81;
|
||||
if (s.CharField != '\0') return 82;
|
||||
if (s.DoubleField != 0.0) return 83;
|
||||
if (s.ByteField != 0) return 84;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int InitializeArgumentStruct(ref SimpleStruct arg)
|
||||
{
|
||||
// Verify initial values
|
||||
if (arg.IntField != 333) return 85;
|
||||
if (arg.BoolField != true) return 86;
|
||||
|
||||
// Reset using default - this should use initobj on the argument
|
||||
arg = default(SimpleStruct);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 9: Initialize struct field (tests Field case)
|
||||
public static int Test9()
|
||||
{
|
||||
StructWithStructField container = new StructWithStructField
|
||||
{
|
||||
NestedField = new SimpleStruct
|
||||
{
|
||||
IntField = 444,
|
||||
BoolField = true,
|
||||
CharField = 'F',
|
||||
DoubleField = 7.89,
|
||||
ByteField = 88
|
||||
},
|
||||
OtherField = 555
|
||||
};
|
||||
|
||||
// Verify initial values
|
||||
if (container.NestedField.IntField != 444) return 90;
|
||||
if (container.OtherField != 555) return 91;
|
||||
|
||||
// Reset the nested field using ref
|
||||
ref SimpleStruct fieldRef = ref container.NestedField;
|
||||
fieldRef = default(SimpleStruct);
|
||||
|
||||
// Verify nested field was reset but other field unchanged
|
||||
if (container.NestedField.IntField != 0) return 92;
|
||||
if (container.NestedField.BoolField != false) return 93;
|
||||
if (container.NestedField.CharField != '\0') return 94;
|
||||
if (container.NestedField.DoubleField != 0.0) return 95;
|
||||
if (container.NestedField.ByteField != 0) return 96;
|
||||
if (container.OtherField != 555) return 97;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 10: Initialize struct in heap-allocated object (tests Heap case)
|
||||
public static int Test10()
|
||||
{
|
||||
ClassWithStructField obj = new ClassWithStructField
|
||||
{
|
||||
StructField = new SimpleStruct
|
||||
{
|
||||
IntField = 666,
|
||||
BoolField = true,
|
||||
CharField = 'H',
|
||||
DoubleField = 9.99,
|
||||
ByteField = 99
|
||||
},
|
||||
IntField = 777
|
||||
};
|
||||
|
||||
// Verify initial values
|
||||
if (obj.StructField.IntField != 666) return 100;
|
||||
if (obj.IntField != 777) return 101;
|
||||
|
||||
// Reset the struct field
|
||||
obj.StructField = default(SimpleStruct);
|
||||
|
||||
// Verify struct field was reset but other field unchanged
|
||||
if (obj.StructField.IntField != 0) return 102;
|
||||
if (obj.StructField.BoolField != false) return 103;
|
||||
if (obj.StructField.CharField != '\0') return 104;
|
||||
if (obj.StructField.DoubleField != 0.0) return 105;
|
||||
if (obj.StructField.ByteField != 0) return 106;
|
||||
if (obj.IntField != 777) return 107;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 11: Initialize struct through unsafe pointer manipulation
|
||||
public static unsafe int Test11()
|
||||
{
|
||||
SimpleStruct s = new SimpleStruct
|
||||
{
|
||||
IntField = 888,
|
||||
BoolField = true,
|
||||
CharField = 'P',
|
||||
DoubleField = 11.11,
|
||||
ByteField = 111
|
||||
};
|
||||
|
||||
// Get a pointer to the struct
|
||||
SimpleStruct* ptr = &s;
|
||||
|
||||
// Initialize through pointer
|
||||
*ptr = default(SimpleStruct);
|
||||
|
||||
// Verify all fields are zeroed
|
||||
if (s.IntField != 0) return 110;
|
||||
if (s.BoolField != false) return 111;
|
||||
if (s.CharField != '\0') return 112;
|
||||
if (s.DoubleField != 0.0) return 113;
|
||||
if (s.ByteField != 0) return 114;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 12: Initialize struct through Unsafe.AsRef
|
||||
public static int Test12()
|
||||
{
|
||||
SimpleStruct s = new SimpleStruct
|
||||
{
|
||||
IntField = 999,
|
||||
BoolField = true,
|
||||
CharField = 'U',
|
||||
DoubleField = 12.34,
|
||||
ByteField = 200
|
||||
};
|
||||
|
||||
// Use Unsafe to get a ref and initialize it
|
||||
ref SimpleStruct sRef = ref s;
|
||||
sRef = default(SimpleStruct);
|
||||
|
||||
// Verify all fields are zeroed
|
||||
if (s.IntField != 0) return 120;
|
||||
if (s.BoolField != false) return 121;
|
||||
if (s.CharField != '\0') return 122;
|
||||
if (s.DoubleField != 0.0) return 123;
|
||||
if (s.ByteField != 0) return 124;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 13: Initialize readonly struct
|
||||
public static int Test13()
|
||||
{
|
||||
ReadonlyStruct ros = new ReadonlyStruct(100, true);
|
||||
|
||||
// Verify initial values through properties
|
||||
if (ros.IntValue != 100) return 130;
|
||||
if (ros.BoolValue != true) return 131;
|
||||
|
||||
// Reset using default
|
||||
ros = default(ReadonlyStruct);
|
||||
|
||||
// Verify zeroed
|
||||
if (ros.IntValue != 0) return 132;
|
||||
if (ros.BoolValue != false) return 133;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private readonly struct ReadonlyStruct
|
||||
{
|
||||
public readonly int IntValue;
|
||||
public readonly bool BoolValue;
|
||||
|
||||
public ReadonlyStruct(int i, bool b)
|
||||
{
|
||||
IntValue = i;
|
||||
BoolValue = b;
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main(string[] argv)
|
||||
{
|
||||
var result = Test1();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test2();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test3();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test4();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test5();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test6();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test7();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test8();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test9();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test10();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test11();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test12();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test13();
|
||||
if (result != 0) return result;
|
||||
|
||||
// All tests passed
|
||||
return 0;
|
||||
}
|
||||
}
|
339
WoofWare.PawPrint.Test/sourcesPure/InterfaceDispatch.cs
Normal file
339
WoofWare.PawPrint.Test/sourcesPure/InterfaceDispatch.cs
Normal file
@@ -0,0 +1,339 @@
|
||||
using System;
|
||||
|
||||
public class InterfaceDispatchTests
|
||||
{
|
||||
public static int Main(string[] argv)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
result |= TestBasicInterface();
|
||||
result |= TestExplicitImplementation() << 1;
|
||||
result |= TestMultipleInterfaces() << 2;
|
||||
result |= TestInterfaceInheritance() << 3;
|
||||
result |= TestDiamondInheritance() << 4;
|
||||
result |= TestGenericInterface() << 5;
|
||||
result |= TestCovariantInterface() << 6;
|
||||
result |= TestReimplementation() << 7;
|
||||
// TODO
|
||||
/*
|
||||
result |= TestStructInterface() << 8;
|
||||
result |= TestNullDispatch() << 9;
|
||||
*/
|
||||
result |= TestSharedMethodSignature() << 10;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Test 1: Basic interface dispatch
|
||||
static int TestBasicInterface()
|
||||
{
|
||||
ISimple obj = new SimpleImpl();
|
||||
return obj.GetValue() == 42 ? 0 : 1;
|
||||
}
|
||||
|
||||
// Test 2: Explicit interface implementation
|
||||
static int TestExplicitImplementation()
|
||||
{
|
||||
var obj = new ExplicitImpl();
|
||||
IExplicit iface = obj;
|
||||
|
||||
// Direct call should return 10, interface call should return 20
|
||||
if (obj.GetValue() != 10) return 1;
|
||||
if (iface.GetValue() != 20) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 3: Multiple interfaces
|
||||
static int TestMultipleInterfaces()
|
||||
{
|
||||
var obj = new MultiImpl();
|
||||
IFirst first = obj;
|
||||
ISecond second = obj;
|
||||
|
||||
if (first.GetFirst() != 1) return 1;
|
||||
if (second.GetSecond() != 2) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 4: Interface inheritance
|
||||
static int TestInterfaceInheritance()
|
||||
{
|
||||
IDerived obj = new DerivedImpl();
|
||||
IBase baseIface = obj;
|
||||
|
||||
if (baseIface.GetBase() != 100) return 1;
|
||||
if (obj.GetDerived() != 200) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 5: Diamond inheritance pattern
|
||||
static int TestDiamondInheritance()
|
||||
{
|
||||
var obj = new DiamondImpl();
|
||||
ILeft left = obj;
|
||||
IRight right = obj;
|
||||
IDiamond diamond = obj;
|
||||
|
||||
if (left.GetValue() != 300) return 1;
|
||||
if (right.GetValue() != 300) return 1;
|
||||
if (diamond.GetDiamondValue() != 400) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 6: Generic interface dispatch
|
||||
static int TestGenericInterface()
|
||||
{
|
||||
IGeneric<int> intObj = new GenericImpl<int>();
|
||||
IGeneric<string> strObj = new GenericImpl<string>();
|
||||
|
||||
if (intObj.Process(5) != 3) return 1;
|
||||
if (strObj.Process("test") != 5) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 7: Covariant interface dispatch
|
||||
static int TestCovariantInterface()
|
||||
{
|
||||
ICovariant<string> strCov = new CovariantImpl();
|
||||
ICovariant<object> objCov = strCov; // Covariance allows this
|
||||
|
||||
object result = objCov.Get();
|
||||
if (!(result is string s && s == "covariant")) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 8: Interface reimplementation in derived class
|
||||
static int TestReimplementation()
|
||||
{
|
||||
BaseClass baseObj = new DerivedClass();
|
||||
IReimpl iface = baseObj;
|
||||
|
||||
// Should call derived implementation
|
||||
if (iface.Method() != 500) return 1;
|
||||
|
||||
// Now test with base reference
|
||||
BaseClass pureBase = new BaseClass();
|
||||
IReimpl baseIface = pureBase;
|
||||
if (baseIface.Method() != 600) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 9: Struct implementing interface
|
||||
static int TestStructInterface()
|
||||
{
|
||||
StructImpl s = new StructImpl { Value = 700 };
|
||||
ISimple boxed = s; // Boxing happens here
|
||||
|
||||
if (boxed.GetValue() != 700) return 1;
|
||||
|
||||
// Verify boxing created a copy
|
||||
s.Value = 800;
|
||||
if (boxed.GetValue() != 700) return 1; // Should still be 700
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 10: Null dispatch (should throw)
|
||||
static int TestNullDispatch()
|
||||
{
|
||||
ISimple nullRef = null;
|
||||
try
|
||||
{
|
||||
nullRef.GetValue();
|
||||
return 1; // Should have thrown
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
return 0; // Expected
|
||||
}
|
||||
}
|
||||
|
||||
// Test 11: Same method signature on multiple unrelated interfaces
|
||||
static int TestSharedMethodSignature()
|
||||
{
|
||||
var obj = new SharedMethodImpl();
|
||||
IReader reader = obj;
|
||||
IScanner scanner = obj;
|
||||
|
||||
// Both interfaces should be satisfied by the single implementation
|
||||
if (reader.Read() != "shared") return 1;
|
||||
if (scanner.Read() != "shared") return 1;
|
||||
|
||||
// Also test with explicit + implicit combination
|
||||
var mixed = new MixedSharedImpl();
|
||||
IReader readerMixed = mixed;
|
||||
IScanner scannerMixed = mixed;
|
||||
|
||||
if (readerMixed.Read() != "explicit-reader") return 1;
|
||||
if (scannerMixed.Read() != "implicit-scanner") return 1;
|
||||
if (mixed.Read() != "implicit-scanner") return 1; // Public method
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test interfaces and implementations
|
||||
|
||||
interface ISimple
|
||||
{
|
||||
int GetValue();
|
||||
}
|
||||
|
||||
class SimpleImpl : ISimple
|
||||
{
|
||||
public int GetValue() => 42;
|
||||
}
|
||||
|
||||
interface IExplicit
|
||||
{
|
||||
int GetValue();
|
||||
}
|
||||
|
||||
class ExplicitImpl : IExplicit
|
||||
{
|
||||
public int GetValue() => 10;
|
||||
int IExplicit.GetValue() => 20;
|
||||
}
|
||||
|
||||
interface IFirst
|
||||
{
|
||||
int GetFirst();
|
||||
}
|
||||
|
||||
interface ISecond
|
||||
{
|
||||
int GetSecond();
|
||||
}
|
||||
|
||||
class MultiImpl : IFirst, ISecond
|
||||
{
|
||||
public int GetFirst() => 1;
|
||||
public int GetSecond() => 2;
|
||||
}
|
||||
|
||||
interface IBase
|
||||
{
|
||||
int GetBase();
|
||||
}
|
||||
|
||||
interface IDerived : IBase
|
||||
{
|
||||
int GetDerived();
|
||||
}
|
||||
|
||||
class DerivedImpl : IDerived
|
||||
{
|
||||
public int GetBase() => 100;
|
||||
public int GetDerived() => 200;
|
||||
}
|
||||
|
||||
interface ICommon
|
||||
{
|
||||
int GetValue();
|
||||
}
|
||||
|
||||
interface ILeft : ICommon
|
||||
{
|
||||
}
|
||||
|
||||
interface IRight : ICommon
|
||||
{
|
||||
}
|
||||
|
||||
interface IDiamond : ILeft, IRight
|
||||
{
|
||||
int GetDiamondValue();
|
||||
}
|
||||
|
||||
class DiamondImpl : IDiamond
|
||||
{
|
||||
public int GetValue() => 300;
|
||||
public int GetDiamondValue() => 400;
|
||||
}
|
||||
|
||||
interface IGeneric<T>
|
||||
{
|
||||
int Process(T value);
|
||||
}
|
||||
|
||||
class GenericImpl<T> : IGeneric<T>
|
||||
{
|
||||
public int Process(T value)
|
||||
{
|
||||
if (typeof(T) == typeof(int))
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(string))
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
interface ICovariant<out T>
|
||||
{
|
||||
T Get();
|
||||
}
|
||||
|
||||
class CovariantImpl : ICovariant<string>
|
||||
{
|
||||
public string Get() => "covariant";
|
||||
}
|
||||
|
||||
interface IReimpl
|
||||
{
|
||||
int Method();
|
||||
}
|
||||
|
||||
class BaseClass : IReimpl
|
||||
{
|
||||
public virtual int Method() => 600;
|
||||
}
|
||||
|
||||
class DerivedClass : BaseClass, IReimpl
|
||||
{
|
||||
public override int Method() => 500;
|
||||
}
|
||||
|
||||
struct StructImpl : ISimple
|
||||
{
|
||||
public int Value;
|
||||
public int GetValue() => Value;
|
||||
}
|
||||
|
||||
interface IReader
|
||||
{
|
||||
string Read();
|
||||
}
|
||||
|
||||
interface IScanner
|
||||
{
|
||||
string Read(); // Same signature as IReader.Read()
|
||||
}
|
||||
|
||||
// Single implicit implementation satisfies both interfaces
|
||||
class SharedMethodImpl : IReader, IScanner
|
||||
{
|
||||
public string Read() => "shared";
|
||||
}
|
||||
|
||||
// Mixed: explicit for one, implicit for the other
|
||||
class MixedSharedImpl : IReader, IScanner
|
||||
{
|
||||
// Explicit implementation for IReader
|
||||
string IReader.Read() => "explicit-reader";
|
||||
|
||||
// Implicit implementation (public) - satisfies IScanner
|
||||
public string Read() => "implicit-scanner";
|
||||
}
|
||||
}
|
@@ -39,7 +39,7 @@ unsafe class LdindTest
|
||||
failures += TestTruncation();
|
||||
|
||||
// Test with managed pointers (ref)
|
||||
// failures += TestManagedPointers();
|
||||
failures += TestManagedPointers();
|
||||
|
||||
// Test Ldind.i (native int)
|
||||
failures += TestLdindI();
|
||||
@@ -325,7 +325,10 @@ unsafe class LdindTest
|
||||
}
|
||||
|
||||
// Test with array element
|
||||
int[] array = { 10, 20, 30 };
|
||||
int[] array = new int[3];
|
||||
array[0] = 10;
|
||||
array[1] = 20;
|
||||
array[2] = 30;
|
||||
ref int element = ref array[1];
|
||||
if (element != 20)
|
||||
{
|
||||
|
128
WoofWare.PawPrint.Test/sourcesPure/LdtokenField.cs
Normal file
128
WoofWare.PawPrint.Test/sourcesPure/LdtokenField.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LdtokenFieldTest
|
||||
{
|
||||
class Program
|
||||
{
|
||||
// Various field types to test ldtoken with
|
||||
public static int StaticIntField = 42;
|
||||
public string InstanceStringField = "test";
|
||||
private readonly double PrivateReadonlyField = 3.14;
|
||||
internal decimal InternalField;
|
||||
protected bool ProtectedField;
|
||||
public static readonly DateTime StaticReadonlyField = DateTime.MinValue;
|
||||
|
||||
// Generic type fields
|
||||
public GenericClass<int>.NestedClass<string> GenericField;
|
||||
|
||||
static int Main(string[] args)
|
||||
{
|
||||
int testsFailed = 0;
|
||||
|
||||
// Test 1: Static field via FieldInfo
|
||||
FieldInfo staticField = typeof(Program).GetField(nameof(StaticIntField));
|
||||
if (staticField == null || staticField.FieldType != typeof(int))
|
||||
{
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 2: Instance field via FieldInfo
|
||||
FieldInfo instanceField = typeof(Program).GetField(nameof(InstanceStringField));
|
||||
if (instanceField == null || instanceField.FieldType != typeof(string))
|
||||
{
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 3: Private field via FieldInfo with binding flags
|
||||
FieldInfo privateField = typeof(Program).GetField("PrivateReadonlyField",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (privateField == null || privateField.FieldType != typeof(double))
|
||||
{
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 4: Using RuntimeFieldHandle directly
|
||||
RuntimeFieldHandle handle = staticField.FieldHandle;
|
||||
FieldInfo fieldFromHandle = FieldInfo.GetFieldFromHandle(handle);
|
||||
if (!ReferenceEquals(fieldFromHandle, staticField))
|
||||
{
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 5: Field from generic type
|
||||
Type genericType = typeof(GenericClass<>);
|
||||
FieldInfo genericFieldInfo = genericType.GetField("GenericField");
|
||||
if (genericFieldInfo == null)
|
||||
{
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 6: Field from nested type
|
||||
Type nestedType = typeof(OuterClass.InnerClass);
|
||||
FieldInfo nestedField = nestedType.GetField("NestedField");
|
||||
if (nestedField == null || nestedField.FieldType != typeof(int))
|
||||
{
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 7: Field handle with generic context
|
||||
Type constructedGeneric = typeof(GenericClass<int>);
|
||||
FieldInfo constructedField = constructedGeneric.GetField("GenericField");
|
||||
RuntimeFieldHandle genericHandle = constructedField.FieldHandle;
|
||||
FieldInfo reconstructed = FieldInfo.GetFieldFromHandle(genericHandle, constructedGeneric.TypeHandle);
|
||||
if (reconstructed.DeclaringType != constructedGeneric)
|
||||
{
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 8: Struct field
|
||||
Type structType = typeof(TestStruct);
|
||||
FieldInfo structField = structType.GetField("StructField");
|
||||
if (structField == null || structField.FieldType != typeof(long))
|
||||
{
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 9: Volatile field
|
||||
FieldInfo volatileField = typeof(VolatileFieldClass).GetField("VolatileField");
|
||||
if (volatileField == null || !volatileField.GetRequiredCustomModifiers().Any(t => t == typeof(IsVolatile)))
|
||||
{
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
return testsFailed;
|
||||
}
|
||||
}
|
||||
|
||||
// Supporting types for testing
|
||||
public class GenericClass<T>
|
||||
{
|
||||
public T GenericField;
|
||||
|
||||
public class NestedClass<U>
|
||||
{
|
||||
public U NestedGenericField;
|
||||
}
|
||||
}
|
||||
|
||||
public class OuterClass
|
||||
{
|
||||
public class InnerClass
|
||||
{
|
||||
public int NestedField = 100;
|
||||
}
|
||||
}
|
||||
|
||||
public struct TestStruct
|
||||
{
|
||||
public long StructField;
|
||||
}
|
||||
|
||||
public class VolatileFieldClass
|
||||
{
|
||||
public volatile int VolatileField;
|
||||
}
|
||||
}
|
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 = unchecked((int)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 = 5005 };
|
||||
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(string[] argv)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
result = TestExplicitUnion();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = TestSequentialLayout();
|
||||
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
|
||||
}
|
||||
}
|
118
WoofWare.PawPrint.Test/sourcesPure/Sizeof.cs
Normal file
118
WoofWare.PawPrint.Test/sourcesPure/Sizeof.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
unsafe public class Program
|
||||
{
|
||||
public struct SmallStruct
|
||||
{
|
||||
public byte Value;
|
||||
}
|
||||
|
||||
public struct MediumStruct
|
||||
{
|
||||
public int MediumValue1;
|
||||
public int MediumValue2;
|
||||
}
|
||||
|
||||
public struct LargeStruct
|
||||
{
|
||||
public long LongValue1;
|
||||
public long LongValue2;
|
||||
public long LongValue3;
|
||||
public long LongValue4;
|
||||
}
|
||||
|
||||
public struct NestedStruct
|
||||
{
|
||||
public SmallStruct Small;
|
||||
public MediumStruct Medium;
|
||||
public int Extra;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct UnionStruct
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public int AsInt;
|
||||
[FieldOffset(0)]
|
||||
public float AsFloat;
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
// Test 1: Basic primitive types
|
||||
if (sizeof(byte) != 1) return 1;
|
||||
if (sizeof(sbyte) != 1) return 2;
|
||||
if (sizeof(short) != 2) return 3;
|
||||
if (sizeof(ushort) != 2) return 4;
|
||||
if (sizeof(int) != 4) return 5;
|
||||
if (sizeof(uint) != 4) return 6;
|
||||
if (sizeof(long) != 8) return 7;
|
||||
if (sizeof(ulong) != 8) return 8;
|
||||
if (sizeof(float) != 4) return 9;
|
||||
if (sizeof(double) != 8) return 10;
|
||||
if (sizeof(char) != 2) return 11;
|
||||
if (sizeof(bool) != 1) return 12;
|
||||
|
||||
// Test 2: Struct sizes
|
||||
if (sizeof(SmallStruct) != 1) return 13;
|
||||
if (sizeof(MediumStruct) != 8) return 14;
|
||||
if (sizeof(LargeStruct) != 32) return 15;
|
||||
|
||||
// Test 3: Nested struct size
|
||||
// SmallStruct (1) + padding (3) + MediumStruct (8) + int (4) = 16
|
||||
if (sizeof(NestedStruct) != 16) return 16;
|
||||
|
||||
// Test 4: Union struct size
|
||||
if (sizeof(UnionStruct) != 4) return 17;
|
||||
|
||||
// Test 5: Enum size (underlying type is int)
|
||||
if (sizeof(DayOfWeek) != 4) return 18;
|
||||
|
||||
// Test 6: Pointer types
|
||||
unsafe
|
||||
{
|
||||
if (sizeof(IntPtr) != sizeof(void*)) return 19;
|
||||
if (sizeof(UIntPtr) != sizeof(void*)) return 20;
|
||||
}
|
||||
|
||||
// Test 7: Using sizeof in expressions
|
||||
int totalSize = sizeof(int) + sizeof(long) + sizeof(byte);
|
||||
if (totalSize != 13) return 21;
|
||||
|
||||
// Test 8: Array element size calculation
|
||||
int arrayElementSize = sizeof(MediumStruct);
|
||||
int arraySize = arrayElementSize * 3;
|
||||
if (arraySize != 24) return 22;
|
||||
|
||||
// Test 9: Conditional using sizeof
|
||||
bool is32Bit = sizeof(IntPtr) == 4;
|
||||
bool is64Bit = sizeof(IntPtr) == 8;
|
||||
if (!is32Bit && !is64Bit) return 23;
|
||||
if (is32Bit && is64Bit) return 24;
|
||||
|
||||
// Test 10: Sizeof in switch statement
|
||||
int result = 0;
|
||||
switch (sizeof(int))
|
||||
{
|
||||
case 1:
|
||||
result = 1;
|
||||
break;
|
||||
case 2:
|
||||
result = 2;
|
||||
break;
|
||||
case 4:
|
||||
result = 4;
|
||||
break;
|
||||
case 8:
|
||||
result = 8;
|
||||
break;
|
||||
default:
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
if (result != 4) return 25;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
235
WoofWare.PawPrint.Test/sourcesPure/Sizeof2.cs
Normal file
235
WoofWare.PawPrint.Test/sourcesPure/Sizeof2.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
unsafe public class Program
|
||||
{
|
||||
// Test for empty struct (should be 1 byte, not 0)
|
||||
public struct EmptyStruct
|
||||
{
|
||||
}
|
||||
|
||||
// Test for char alignment (should align to 2, not 1)
|
||||
public struct CharStruct
|
||||
{
|
||||
public byte B;
|
||||
public char C; // Should be at offset 2, not 1
|
||||
}
|
||||
|
||||
// Test for end padding
|
||||
public struct NeedsEndPadding
|
||||
{
|
||||
public int X;
|
||||
public byte Y;
|
||||
// Should pad to 8 bytes total (multiple of 4)
|
||||
}
|
||||
|
||||
// Test Pack=1 (no padding)
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct PackedStruct
|
||||
{
|
||||
public byte B;
|
||||
public int I; // At offset 1, not 4
|
||||
public byte B2;
|
||||
// Total 6 bytes, no padding
|
||||
}
|
||||
|
||||
// Test Pack=2
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 2)]
|
||||
public struct Pack2Struct
|
||||
{
|
||||
public byte B;
|
||||
public int I; // At offset 2 (2-byte aligned, not 4)
|
||||
public byte B2;
|
||||
// Should pad to 8 bytes (multiple of 2)
|
||||
}
|
||||
|
||||
// Test custom size smaller than natural size
|
||||
[StructLayout(LayoutKind.Sequential, Size = 12)]
|
||||
public struct CustomSizeSmaller
|
||||
{
|
||||
public long L1;
|
||||
public long L2;
|
||||
// Natural size is 16, but Size=12 is ignored (12 < 16)
|
||||
}
|
||||
|
||||
// Test custom size larger than natural size
|
||||
[StructLayout(LayoutKind.Sequential, Size = 20)]
|
||||
public struct CustomSizeLarger
|
||||
{
|
||||
public long L;
|
||||
// Natural size is 8, custom size 20 should win
|
||||
}
|
||||
|
||||
// Test custom size not multiple of alignment
|
||||
[StructLayout(LayoutKind.Sequential, Size = 15)]
|
||||
public struct CustomSizeOdd
|
||||
{
|
||||
public long L;
|
||||
// Size=15 should be honored even though not multiple of 8
|
||||
}
|
||||
|
||||
// Test Pack=0 (means default, not 0)
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 0)]
|
||||
public struct Pack0Struct
|
||||
{
|
||||
public byte B;
|
||||
public int I; // Should be at offset 4 (default packing)
|
||||
}
|
||||
|
||||
// Test both Pack and Size
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 10)]
|
||||
public struct PackAndSize
|
||||
{
|
||||
public byte B;
|
||||
public int I;
|
||||
// Natural packed size is 5, custom size 10 should win
|
||||
}
|
||||
|
||||
// Test explicit with custom Size
|
||||
[StructLayout(LayoutKind.Explicit, Size = 10)]
|
||||
public struct ExplicitWithSize
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public int I;
|
||||
[FieldOffset(2)]
|
||||
public short S;
|
||||
// Max offset+size is 4, but Size=10 should win
|
||||
}
|
||||
|
||||
public struct SmallStruct
|
||||
{
|
||||
public byte Value;
|
||||
}
|
||||
|
||||
public struct MediumStruct
|
||||
{
|
||||
public int MediumValue1;
|
||||
public int MediumValue2;
|
||||
}
|
||||
|
||||
public struct LargeStruct
|
||||
{
|
||||
public long LongValue1;
|
||||
public long LongValue2;
|
||||
public long LongValue3;
|
||||
public long LongValue4;
|
||||
}
|
||||
|
||||
public struct NestedStruct
|
||||
{
|
||||
public SmallStruct Small;
|
||||
public MediumStruct Medium;
|
||||
public int Extra;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct UnionStruct
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public int AsInt;
|
||||
[FieldOffset(0)]
|
||||
public float AsFloat;
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
// Test 1: Basic primitive types
|
||||
if (sizeof(byte) != 1) return 1;
|
||||
if (sizeof(sbyte) != 1) return 2;
|
||||
if (sizeof(short) != 2) return 3;
|
||||
if (sizeof(ushort) != 2) return 4;
|
||||
if (sizeof(int) != 4) return 5;
|
||||
if (sizeof(uint) != 4) return 6;
|
||||
if (sizeof(long) != 8) return 7;
|
||||
if (sizeof(ulong) != 8) return 8;
|
||||
if (sizeof(float) != 4) return 9;
|
||||
if (sizeof(double) != 8) return 10;
|
||||
if (sizeof(char) != 2) return 11;
|
||||
if (sizeof(bool) != 1) return 12;
|
||||
|
||||
// Test 2: Struct sizes
|
||||
if (sizeof(SmallStruct) != 1) return 13;
|
||||
if (sizeof(MediumStruct) != 8) return 14;
|
||||
if (sizeof(LargeStruct) != 32) return 15;
|
||||
|
||||
// Test 3: Nested struct size
|
||||
// SmallStruct (1) + padding (3) + MediumStruct (8) + int (4) = 16
|
||||
if (sizeof(NestedStruct) != 16) return 16;
|
||||
|
||||
// Test 4: Union struct size
|
||||
if (sizeof(UnionStruct) != 4) return 17;
|
||||
|
||||
// Test 5: Enum size (underlying type is int)
|
||||
if (sizeof(DayOfWeek) != 4) return 18;
|
||||
|
||||
// Test 6: Empty struct (should be 1, not 0)
|
||||
if (sizeof(EmptyStruct) != 1) return 19;
|
||||
|
||||
// Test 7: Char alignment
|
||||
// byte (1) + padding (1) + char (2) = 4
|
||||
if (sizeof(CharStruct) != 4) return 20;
|
||||
|
||||
// Test 8: End padding
|
||||
// int (4) + byte (1) + padding (3) = 8
|
||||
if (sizeof(NeedsEndPadding) != 8) return 21;
|
||||
|
||||
// Test 9: Pack=1 removes all padding
|
||||
// byte (1) + int (4) + byte (1) = 6
|
||||
if (sizeof(PackedStruct) != 6) return 22;
|
||||
|
||||
// Test 10: Pack=2
|
||||
// byte (1) + padding (1) + int (4) + byte (1) + padding (1) = 8
|
||||
if (sizeof(Pack2Struct) != 8) return 23;
|
||||
|
||||
// Test 11: Custom size smaller than natural (ignored)
|
||||
if (sizeof(CustomSizeSmaller) != 16) return 24;
|
||||
|
||||
// Test 12: Custom size larger than natural (honored)
|
||||
if (sizeof(CustomSizeLarger) != 20) return 25;
|
||||
|
||||
// Test 13: Custom size not multiple of alignment (honored)
|
||||
if (sizeof(CustomSizeOdd) != 15) return 26;
|
||||
|
||||
// Test 14: Pack=0 means default packing
|
||||
// byte (1) + padding (3) + int (4) = 8
|
||||
if (sizeof(Pack0Struct) != 8) return 27;
|
||||
|
||||
// Test 15: Pack and Size together
|
||||
// Natural packed: byte (1) + int (4) = 5, but Size=10
|
||||
if (sizeof(PackAndSize) != 10) return 28;
|
||||
|
||||
// Test 16: Explicit with Size
|
||||
// Max used is 4, but Size=10
|
||||
if (sizeof(ExplicitWithSize) != 10) return 29;
|
||||
|
||||
// Test 17: Pointer types
|
||||
unsafe
|
||||
{
|
||||
if (sizeof(IntPtr) != sizeof(void*)) return 30;
|
||||
if (sizeof(UIntPtr) != sizeof(void*)) return 31;
|
||||
}
|
||||
|
||||
// Test 18: Using sizeof in expressions
|
||||
int totalSize = sizeof(int) + sizeof(long) + sizeof(byte);
|
||||
if (totalSize != 13) return 32;
|
||||
|
||||
// Test 19: Array element size calculation
|
||||
int arrayElementSize = sizeof(MediumStruct);
|
||||
int arraySize = arrayElementSize * 3;
|
||||
if (arraySize != 24) return 33;
|
||||
|
||||
// Test 20: Complex nested struct with Pack
|
||||
// byte (1) + CharStruct (4) + byte (1) = 6
|
||||
if (sizeof(PackedNested) != 6) return 34;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct PackedNested
|
||||
{
|
||||
public byte B;
|
||||
public CharStruct C;
|
||||
public byte B2;
|
||||
}
|
||||
}
|
276
WoofWare.PawPrint.Test/sourcesPure/UnsafeAs.cs
Normal file
276
WoofWare.PawPrint.Test/sourcesPure/UnsafeAs.cs
Normal file
@@ -0,0 +1,276 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
public class TestUnsafeAs
|
||||
{
|
||||
private struct Int32Wrapper
|
||||
{
|
||||
public int Value;
|
||||
}
|
||||
|
||||
private struct UInt32Wrapper
|
||||
{
|
||||
public uint Value;
|
||||
}
|
||||
|
||||
private struct TwoInt16s
|
||||
{
|
||||
public short First;
|
||||
public short Second;
|
||||
}
|
||||
|
||||
private struct FourBytes
|
||||
{
|
||||
public byte B0;
|
||||
public byte B1;
|
||||
public byte B2;
|
||||
public byte B3;
|
||||
}
|
||||
|
||||
private enum TestEnum : int
|
||||
{
|
||||
Value1 = 0x12345678,
|
||||
Value2 = -1
|
||||
}
|
||||
|
||||
// Test 1: Int32 -> UInt32 reinterpretation
|
||||
public static int Test1()
|
||||
{
|
||||
int original = -1;
|
||||
ref uint reinterpreted = ref Unsafe.As<int, uint>(ref original);
|
||||
|
||||
if (reinterpreted != 0xFFFFFFFF)
|
||||
return 1;
|
||||
|
||||
reinterpreted = 0x12345678;
|
||||
if (original != 0x12345678)
|
||||
return 2;
|
||||
|
||||
original = int.MinValue;
|
||||
if (reinterpreted != 0x80000000)
|
||||
return 3;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 2: Struct -> Struct reinterpretation
|
||||
public static int Test2()
|
||||
{
|
||||
Int32Wrapper wrapper = new Int32Wrapper { Value = 0x01020304 };
|
||||
ref FourBytes bytes = ref Unsafe.As<Int32Wrapper, FourBytes>(ref wrapper);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
if (bytes.B0 != 0x04) return 10;
|
||||
if (bytes.B1 != 0x03) return 11;
|
||||
if (bytes.B2 != 0x02) return 12;
|
||||
if (bytes.B3 != 0x01) return 13;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bytes.B0 != 0x01) return 14;
|
||||
if (bytes.B1 != 0x02) return 15;
|
||||
if (bytes.B2 != 0x03) return 16;
|
||||
if (bytes.B3 != 0x04) return 17;
|
||||
}
|
||||
|
||||
bytes.B0 = 0xFF;
|
||||
int expectedValue = BitConverter.IsLittleEndian ? 0x010203FF : unchecked((int)0xFF020304);
|
||||
if (wrapper.Value != expectedValue)
|
||||
return 18;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 3: Int32 -> Two Int16s
|
||||
public static int Test3()
|
||||
{
|
||||
int value = 0x12345678;
|
||||
ref TwoInt16s halves = ref Unsafe.As<int, TwoInt16s>(ref value);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
if (halves.First != unchecked((short)0x5678)) return 20;
|
||||
if (halves.Second != 0x1234) return 21;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (halves.First != 0x1234) return 22;
|
||||
if (halves.Second != unchecked((short)0x5678)) return 23;
|
||||
}
|
||||
|
||||
halves.First = -1;
|
||||
int expectedValue = BitConverter.IsLittleEndian ? 0x1234FFFF : unchecked((int)0xFFFF5678);
|
||||
if (value != expectedValue)
|
||||
return 24;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 4: Array element reinterpretation
|
||||
public static int Test4()
|
||||
{
|
||||
int[] intArray = new int[] { 0x01020304, 0x05060708 };
|
||||
ref uint uintRef = ref Unsafe.As<int, uint>(ref intArray[0]);
|
||||
|
||||
if (uintRef != 0x01020304u)
|
||||
return 30;
|
||||
|
||||
uintRef = 0xAABBCCDD;
|
||||
if (intArray[0] != unchecked((int)0xAABBCCDD))
|
||||
return 31;
|
||||
if (intArray[1] != 0x05060708)
|
||||
return 32;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 5: Bool -> Byte
|
||||
public static int Test5()
|
||||
{
|
||||
bool trueValue = true;
|
||||
bool falseValue = false;
|
||||
|
||||
ref byte trueByte = ref Unsafe.As<bool, byte>(ref trueValue);
|
||||
ref byte falseByte = ref Unsafe.As<bool, byte>(ref falseValue);
|
||||
|
||||
if (trueByte != 1)
|
||||
return 40;
|
||||
if (falseByte != 0)
|
||||
return 41;
|
||||
|
||||
// Modify through byte reference
|
||||
trueByte = 0;
|
||||
if (trueValue != false)
|
||||
return 42;
|
||||
|
||||
falseByte = 1;
|
||||
if (falseValue != true)
|
||||
return 43;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 6: Char -> UInt16
|
||||
public static int Test6()
|
||||
{
|
||||
char ch = 'A';
|
||||
ref ushort asUInt16 = ref Unsafe.As<char, ushort>(ref ch);
|
||||
|
||||
if (asUInt16 != 65)
|
||||
return 50;
|
||||
|
||||
asUInt16 = 0x03B1; // Greek lowercase alpha
|
||||
if (ch != 'α')
|
||||
return 51;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 7: Float -> Int32
|
||||
public static int Test7()
|
||||
{
|
||||
float floatValue = 1.0f;
|
||||
ref int intBits = ref Unsafe.As<float, int>(ref floatValue);
|
||||
|
||||
// IEEE 754: 1.0f = 0x3F800000
|
||||
if (intBits != 0x3F800000)
|
||||
return 60;
|
||||
|
||||
intBits = 0x40000000; // 2.0f in IEEE 754
|
||||
if (floatValue != 2.0f)
|
||||
return 61;
|
||||
|
||||
floatValue = -0.0f;
|
||||
if (intBits != unchecked((int)0x80000000))
|
||||
return 62;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 8: Double -> Int64
|
||||
public static int Test8()
|
||||
{
|
||||
double doubleValue = 1.0;
|
||||
ref long longBits = ref Unsafe.As<double, long>(ref doubleValue);
|
||||
|
||||
// IEEE 754: 1.0 = 0x3FF0000000000000
|
||||
if (longBits != 0x3FF0000000000000L)
|
||||
return 70;
|
||||
|
||||
longBits = 0x4000000000000000L; // 2.0 in IEEE 754
|
||||
if (doubleValue != 2.0)
|
||||
return 71;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 9: Enum -> Underlying type
|
||||
public static int Test9()
|
||||
{
|
||||
TestEnum enumValue = TestEnum.Value1;
|
||||
ref int underlying = ref Unsafe.As<TestEnum, int>(ref enumValue);
|
||||
|
||||
if (underlying != 0x12345678)
|
||||
return 80;
|
||||
|
||||
underlying = -1;
|
||||
if (enumValue != TestEnum.Value2)
|
||||
return 81;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 10: Local variable reinterpretation
|
||||
public static int Test10()
|
||||
{
|
||||
int local = unchecked((int)0xDEADBEEF);
|
||||
ref uint localAsUint = ref Unsafe.As<int, uint>(ref local);
|
||||
|
||||
if (localAsUint != 0xDEADBEEF)
|
||||
return 90;
|
||||
|
||||
localAsUint = 0xCAFEBABE;
|
||||
if (local != unchecked((int)0xCAFEBABE))
|
||||
return 91;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int Main(string[] argv)
|
||||
{
|
||||
var result = Test1();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test2();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test3();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test4();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test5();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test6();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test7();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test8();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test9();
|
||||
if (result != 0) return result;
|
||||
|
||||
result = Test10();
|
||||
if (result != 0) return result;
|
||||
|
||||
// All tests passed
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System.Collections.Immutable
|
||||
open Microsoft.Extensions.Logging
|
||||
open Microsoft.FSharp.Core
|
||||
open WoofWare.PawPrint.ExternImplementations
|
||||
@@ -43,7 +42,7 @@ module AbstractMachine =
|
||||
| Some {
|
||||
WasConstructingObj = Some _
|
||||
} ->
|
||||
IlMachineState.executeDelegateConstructor instruction state
|
||||
IlMachineState.executeDelegateConstructor baseClassTypes instruction state
|
||||
// can't advance the program counter here - there's no IL instructions executing!
|
||||
|> IlMachineState.returnStackFrame loggerFactory baseClassTypes thread
|
||||
|> Option.get
|
||||
@@ -55,18 +54,19 @@ module AbstractMachine =
|
||||
// We've been instructed to run a delegate.
|
||||
let delegateToRunAddr =
|
||||
match instruction.Arguments.[0] with
|
||||
| CliType.RuntimePointer (CliRuntimePointer.Managed (ManagedPointerSource.Heap addr))
|
||||
| CliType.ObjectRef (Some addr) -> addr
|
||||
| _ -> failwith "expected a managed object ref to delegate"
|
||||
|
||||
let delegateToRun = state.ManagedHeap.NonArrayObjects.[delegateToRunAddr]
|
||||
|
||||
let target =
|
||||
match delegateToRun.Fields.["_target"] with
|
||||
match delegateToRun |> AllocatedNonArrayObject.DereferenceField "_target" with
|
||||
| CliType.ObjectRef addr -> addr
|
||||
| x -> failwith $"TODO: delegate target wasn't an object ref: %O{x}"
|
||||
|
||||
let methodPtr =
|
||||
match delegateToRun.Fields.["_methodPtr"] with
|
||||
match delegateToRun |> AllocatedNonArrayObject.DereferenceField "_methodPtr" with
|
||||
| CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer mi)) -> mi
|
||||
| d -> failwith $"unexpectedly not a method pointer in delegate invocation: {d}"
|
||||
|
||||
@@ -103,13 +103,14 @@ module AbstractMachine =
|
||||
let currentThreadState = state.ThreadState.[thread]
|
||||
|
||||
let state =
|
||||
IlMachineState.callMethod
|
||||
IlMachineStateExecution.callMethod
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
None
|
||||
constructing
|
||||
false
|
||||
false
|
||||
false
|
||||
methodGenerics
|
||||
methodPtr
|
||||
thread
|
||||
@@ -125,42 +126,55 @@ module AbstractMachine =
|
||||
targetType.Namespace,
|
||||
targetType.Name,
|
||||
instruction.ExecutingMethod.Name,
|
||||
instruction.ExecutingMethod.RawSignature.ParameterTypes,
|
||||
instruction.ExecutingMethod.RawSignature.ReturnType
|
||||
instruction.ExecutingMethod.Signature.ParameterTypes,
|
||||
instruction.ExecutingMethod.Signature.ReturnType
|
||||
with
|
||||
| "System.Private.CoreLib",
|
||||
"System",
|
||||
"Environment",
|
||||
"GetProcessorCount",
|
||||
[],
|
||||
TypeDefn.PrimitiveType PrimitiveType.Int32 ->
|
||||
ConcretePrimitive state.ConcreteTypes PrimitiveType.Int32 ->
|
||||
let env = ISystem_Environment_Env.get impls
|
||||
env.GetProcessorCount thread state
|
||||
| "System.Private.CoreLib",
|
||||
"System",
|
||||
"Environment",
|
||||
"_Exit",
|
||||
[ TypeDefn.PrimitiveType PrimitiveType.Int32 ],
|
||||
TypeDefn.Void ->
|
||||
[ ConcretePrimitive state.ConcreteTypes PrimitiveType.Int32 ],
|
||||
ConcreteVoid state.ConcreteTypes ->
|
||||
let env = ISystem_Environment_Env.get impls
|
||||
env._Exit thread state
|
||||
| "System.Private.CoreLib",
|
||||
"System.Threading",
|
||||
"Monitor",
|
||||
"ReliableEnter",
|
||||
[ TypeDefn.PrimitiveType PrimitiveType.Object
|
||||
TypeDefn.Byref (TypeDefn.PrimitiveType PrimitiveType.Boolean) ],
|
||||
TypeDefn.Void ->
|
||||
[ ConcretePrimitive state.ConcreteTypes PrimitiveType.Object
|
||||
ConcreteByref (ConcretePrimitive state.ConcreteTypes PrimitiveType.Boolean) ],
|
||||
ConcreteVoid state.ConcreteTypes ->
|
||||
let env = ISystem_Threading_Monitor_Env.get impls
|
||||
env.ReliableEnter thread state
|
||||
| "System.Private.CoreLib",
|
||||
"System.Threading",
|
||||
"Monitor",
|
||||
"Exit",
|
||||
[ TypeDefn.PrimitiveType PrimitiveType.Object ],
|
||||
TypeDefn.Void ->
|
||||
[ ConcretePrimitive state.ConcreteTypes PrimitiveType.Object ],
|
||||
ConcreteVoid state.ConcreteTypes ->
|
||||
let env = ISystem_Threading_Monitor_Env.get impls
|
||||
env.Exit thread state
|
||||
| "System.Private.CoreLib",
|
||||
"System",
|
||||
"Type",
|
||||
"GetField",
|
||||
[ ConcretePrimitive state.ConcreteTypes PrimitiveType.String ; ty ],
|
||||
ret ->
|
||||
let ty = AllConcreteTypes.lookup ty state.ConcreteTypes |> Option.get
|
||||
let ret = AllConcreteTypes.lookup ret state.ConcreteTypes |> Option.get
|
||||
|
||||
match ty.Namespace, ty.Name, ty.Generics.IsEmpty, ret.Namespace, ret.Name, ret.Generics.IsEmpty with
|
||||
| "System.Reflection", "BindingFlags", true, "System.Reflection", "FieldInfo", true ->
|
||||
failwith "TODO: GetField"
|
||||
| _ -> failwith "unexpected signature for Type.GetField"
|
||||
| assy, ns, typeName, methName, param, retType ->
|
||||
failwith
|
||||
$"TODO: tried to IL-interpret a method in {assy} {ns}.{typeName} named {methName} with no implementation; {param} -> {retType}"
|
||||
@@ -208,5 +222,5 @@ module AbstractMachine =
|
||||
|> ExecutionResult.Stepped
|
||||
| IlOp.Switch immutableArray -> failwith "TODO: Switch unimplemented"
|
||||
| IlOp.UnaryStringToken (unaryStringTokenIlOp, stringHandle) ->
|
||||
UnaryStringTokenIlOp.execute baseClassTypes unaryStringTokenIlOp stringHandle state thread
|
||||
UnaryStringTokenIlOp.execute loggerFactory baseClassTypes unaryStringTokenIlOp stringHandle state thread
|
||||
|> ExecutionResult.Stepped
|
||||
|
@@ -14,6 +14,3 @@ type ManagedHeapAddress =
|
||||
override this.ToString () : string =
|
||||
match this with
|
||||
| ManagedHeapAddress.ManagedHeapAddress i -> $"<object #%i{i}>"
|
||||
|
||||
[<Measure>]
|
||||
type typeHandle
|
||||
|
@@ -1,8 +1,9 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection
|
||||
open System.Reflection.Metadata
|
||||
open Checked
|
||||
|
||||
/// Source:
|
||||
/// Table I.6: Data Types Directly Supported by the CLI
|
||||
@@ -41,7 +42,9 @@ type ManagedPointerSource =
|
||||
| Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
|
||||
| Heap of ManagedHeapAddress
|
||||
| ArrayIndex of arr : ManagedHeapAddress * index : int
|
||||
| Field of ManagedPointerSource * fieldName : string
|
||||
| Null
|
||||
| InterpretedAsType of ManagedPointerSource * ConcreteType<ConcreteTypeHandle>
|
||||
|
||||
override this.ToString () =
|
||||
match this with
|
||||
@@ -52,6 +55,8 @@ type ManagedPointerSource =
|
||||
| ManagedPointerSource.Argument (source, method, var) ->
|
||||
$"<argument %i{var} in method frame %i{method} of thread %O{source}>"
|
||||
| ManagedPointerSource.ArrayIndex (arr, index) -> $"<index %i{index} of array %O{arr}>"
|
||||
| ManagedPointerSource.Field (source, name) -> $"<field %s{name} of %O{source}>"
|
||||
| ManagedPointerSource.InterpretedAsType (src, ty) -> $"<%O{src} as %s{ty.Namespace}.%s{ty.Name}>"
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type UnsignedNativeIntSource =
|
||||
@@ -63,7 +68,8 @@ type NativeIntSource =
|
||||
| Verbatim of int64
|
||||
| ManagedPointer of ManagedPointerSource
|
||||
| FunctionPointer of MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>
|
||||
| TypeHandlePtr of int64<typeHandle>
|
||||
| TypeHandlePtr of ConcreteTypeHandle
|
||||
| FieldHandlePtr of int64
|
||||
|
||||
override this.ToString () : string =
|
||||
match this with
|
||||
@@ -71,13 +77,15 @@ type NativeIntSource =
|
||||
| NativeIntSource.ManagedPointer ptr -> $"<managed pointer {ptr}>"
|
||||
| NativeIntSource.FunctionPointer methodDefinition ->
|
||||
$"<pointer to {methodDefinition.Name} in {methodDefinition.DeclaringType.Assembly.Name}>"
|
||||
| NativeIntSource.TypeHandlePtr ptr -> $"<type ID %i{ptr}>"
|
||||
| NativeIntSource.TypeHandlePtr ptr -> $"<type ID %O{ptr}>"
|
||||
| NativeIntSource.FieldHandlePtr ptr -> $"<field ID %O{ptr}>"
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module NativeIntSource =
|
||||
let isZero (n : NativeIntSource) : bool =
|
||||
match n with
|
||||
| NativeIntSource.Verbatim i -> i = 0L
|
||||
| NativeIntSource.FieldHandlePtr _
|
||||
| NativeIntSource.TypeHandlePtr _ -> false
|
||||
| NativeIntSource.FunctionPointer _ -> failwith "TODO"
|
||||
| NativeIntSource.ManagedPointer src ->
|
||||
@@ -89,6 +97,7 @@ module NativeIntSource =
|
||||
match n with
|
||||
| NativeIntSource.Verbatim i -> i >= 0L
|
||||
| NativeIntSource.FunctionPointer _ -> failwith "TODO"
|
||||
| NativeIntSource.FieldHandlePtr _
|
||||
| NativeIntSource.TypeHandlePtr _ -> true
|
||||
| NativeIntSource.ManagedPointer _ -> true
|
||||
|
||||
@@ -113,29 +122,51 @@ type CliNumericType =
|
||||
| Float32 of float32
|
||||
| Float64 of float
|
||||
|
||||
type CliValueType =
|
||||
private
|
||||
| Bool of byte
|
||||
/// A UTF-16 code unit, i.e. two bytes. We store the most significant one first.
|
||||
| Char of byte * byte
|
||||
| UInt8 of uint8
|
||||
| UInt16 of uint16
|
||||
| Int8 of int8
|
||||
| Int16 of int16
|
||||
| Float32 of float32
|
||||
| Float64 of float
|
||||
static member SizeOf (t : CliNumericType) : int =
|
||||
match t with
|
||||
| CliNumericType.Int32 _ -> 4
|
||||
| CliNumericType.Int64 _ -> 8
|
||||
| CliNumericType.NativeInt _ -> 8
|
||||
| CliNumericType.NativeFloat _ -> 8
|
||||
| CliNumericType.Int8 _ -> 1
|
||||
| CliNumericType.Int16 _ -> 2
|
||||
| CliNumericType.UInt8 _ -> 1
|
||||
| CliNumericType.UInt16 _ -> 2
|
||||
| CliNumericType.Float32 _ -> 4
|
||||
| CliNumericType.Float64 _ -> 8
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type CliRuntimePointerSource =
|
||||
| LocalVariable of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
|
||||
| Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
|
||||
| Heap of ManagedHeapAddress
|
||||
| ArrayIndex of arr : ManagedHeapAddress * index : int
|
||||
| Null
|
||||
static member ToBytes (t : CliNumericType) : byte[] =
|
||||
match t with
|
||||
| CliNumericType.Int32 i -> BitConverter.GetBytes i
|
||||
| CliNumericType.Int64 i -> BitConverter.GetBytes i
|
||||
| CliNumericType.NativeInt src ->
|
||||
match src with
|
||||
| NativeIntSource.Verbatim i -> BitConverter.GetBytes i
|
||||
| NativeIntSource.ManagedPointer src ->
|
||||
match src with
|
||||
| ManagedPointerSource.Null -> BitConverter.GetBytes 0L
|
||||
| _ -> failwith "refusing to express pointer as bytes"
|
||||
| NativeIntSource.FieldHandlePtr _ -> failwith "refusing to express FieldHandlePtr as bytes"
|
||||
| NativeIntSource.FunctionPointer _ -> failwith "refusing to express FunctionPointer as bytes"
|
||||
| NativeIntSource.TypeHandlePtr _ -> failwith "refusing to express TypeHandlePtr as bytes"
|
||||
| CliNumericType.NativeFloat f -> BitConverter.GetBytes f
|
||||
| CliNumericType.Int8 i -> BitConverter.GetBytes i
|
||||
| CliNumericType.Int16 i -> BitConverter.GetBytes i
|
||||
| CliNumericType.UInt8 i -> BitConverter.GetBytes i
|
||||
| CliNumericType.UInt16 i -> BitConverter.GetBytes i
|
||||
| CliNumericType.Float32 i -> BitConverter.GetBytes i
|
||||
| CliNumericType.Float64 i -> BitConverter.GetBytes i
|
||||
|
||||
type CliRuntimePointer =
|
||||
| Unmanaged of int64
|
||||
| Managed of CliRuntimePointerSource
|
||||
| Verbatim of int64
|
||||
| FieldRegistryHandle of int64
|
||||
| Managed of ManagedPointerSource
|
||||
|
||||
type SizeofResult =
|
||||
{
|
||||
Alignment : int
|
||||
Size : int
|
||||
}
|
||||
|
||||
/// This is the kind of type that can be stored in arguments, local variables, statics, array elements, fields.
|
||||
type CliType =
|
||||
@@ -151,15 +182,351 @@ type CliType =
|
||||
| RuntimePointer of CliRuntimePointer
|
||||
/// This is *not* a CLI type as such. I don't actually know its status. A value type is represented simply
|
||||
/// as a concatenated list of its fields.
|
||||
| ValueType of (string * CliType) list
|
||||
| ValueType of CliValueType
|
||||
|
||||
/// In fact any non-zero value will do for True, but we'll use 1
|
||||
static member OfBool (b : bool) = CliType.Bool (if b then 1uy else 0uy)
|
||||
static member SizeOf (t : CliType) : SizeofResult =
|
||||
match t with
|
||||
| CliType.Numeric ty ->
|
||||
let size = CliNumericType.SizeOf ty
|
||||
|
||||
static member OfChar (c : char) =
|
||||
CliType.Char (byte (int c / 256), byte (int c % 256))
|
||||
{
|
||||
Size = size
|
||||
Alignment = size
|
||||
}
|
||||
| CliType.Bool _ ->
|
||||
{
|
||||
Size = 1
|
||||
Alignment = 1
|
||||
}
|
||||
| CliType.Char _ ->
|
||||
{
|
||||
Size = 2
|
||||
Alignment = 2
|
||||
}
|
||||
| CliType.ObjectRef _ ->
|
||||
{
|
||||
Size = 8
|
||||
Alignment = 8
|
||||
}
|
||||
| CliType.RuntimePointer _ ->
|
||||
{
|
||||
Size = 8
|
||||
Alignment = 8
|
||||
}
|
||||
| CliType.ValueType vt -> CliValueType.SizeOf vt
|
||||
|
||||
static member OfManagedObject (ptr : ManagedHeapAddress) = CliType.ObjectRef (Some ptr)
|
||||
static member ToBytes (t : CliType) : byte[] =
|
||||
match t with
|
||||
| CliType.Numeric n -> CliNumericType.ToBytes n
|
||||
| CliType.Bool b -> [| b |]
|
||||
| CliType.Char (high, low) -> [| low ; high |]
|
||||
| CliType.ObjectRef None -> Array.zeroCreate NATIVE_INT_SIZE
|
||||
| CliType.ObjectRef (Some i) -> failwith "todo"
|
||||
| CliType.RuntimePointer cliRuntimePointer -> failwith "todo"
|
||||
| CliType.ValueType cvt -> CliValueType.ToBytes cvt
|
||||
|
||||
static member OfBytesAsType (targetType : ConcreteTypeHandle) (bytes : byte[]) : CliType = failwith "TODO"
|
||||
|
||||
and CliField =
|
||||
{
|
||||
Name : string
|
||||
Contents : CliType
|
||||
/// "None" for "no explicit offset specified"; we expect most offsets to be None.
|
||||
Offset : int option
|
||||
Type : ConcreteTypeHandle
|
||||
}
|
||||
|
||||
and CliConcreteField =
|
||||
private
|
||||
{
|
||||
Name : string
|
||||
Contents : CliType
|
||||
Offset : int
|
||||
Size : int
|
||||
Alignment : int
|
||||
ConfiguredOffset : int option
|
||||
EditedAtTime : uint64
|
||||
Type : ConcreteTypeHandle
|
||||
}
|
||||
|
||||
static member ToCliField (this : CliConcreteField) : CliField =
|
||||
{
|
||||
Offset = this.ConfiguredOffset
|
||||
Contents = this.Contents
|
||||
Name = this.Name
|
||||
Type = this.Type
|
||||
}
|
||||
|
||||
and CliValueType =
|
||||
private
|
||||
{
|
||||
_Fields : CliConcreteField list
|
||||
Layout : Layout
|
||||
/// We track dependency orderings between updates to overlapping fields with a monotonically increasing
|
||||
/// timestamp.
|
||||
NextTimestamp : uint64
|
||||
}
|
||||
|
||||
static member private ComputeConcreteFields (layout : Layout) (fields : CliField list) : CliConcreteField list =
|
||||
// Minimum size only matters for `sizeof` computation
|
||||
let _minimumSize, packingSize =
|
||||
match layout with
|
||||
| Layout.Custom (size = size ; packingSize = packing) ->
|
||||
size, if packing = 0 then DEFAULT_STRUCT_ALIGNMENT else packing
|
||||
| Layout.Default -> 0, DEFAULT_STRUCT_ALIGNMENT
|
||||
|
||||
let seqFields, nonSeqFields =
|
||||
fields |> List.partition (fun field -> field.Offset.IsNone)
|
||||
|
||||
match seqFields, nonSeqFields with
|
||||
| [], [] -> []
|
||||
| _ :: _, [] ->
|
||||
// Sequential layout: compute offsets respecting alignment
|
||||
let _, concreteFields =
|
||||
((0, []), seqFields)
|
||||
||> List.fold (fun (currentOffset, acc) field ->
|
||||
let size = CliType.SizeOf field.Contents
|
||||
let alignmentCap = min size.Alignment packingSize
|
||||
let error = currentOffset % alignmentCap
|
||||
|
||||
let alignedOffset =
|
||||
if error > 0 then
|
||||
currentOffset + (alignmentCap - error)
|
||||
else
|
||||
currentOffset
|
||||
|
||||
let concreteField =
|
||||
{
|
||||
Name = field.Name
|
||||
Contents = field.Contents
|
||||
Offset = alignedOffset
|
||||
Size = size.Size
|
||||
Alignment = size.Alignment
|
||||
ConfiguredOffset = field.Offset
|
||||
EditedAtTime = 0UL
|
||||
Type = field.Type
|
||||
}
|
||||
|
||||
alignedOffset + size.Size, concreteField :: acc
|
||||
)
|
||||
|
||||
List.rev concreteFields
|
||||
|
||||
| [], _ :: _ ->
|
||||
// Explicit layout: use provided offsets
|
||||
nonSeqFields
|
||||
|> List.map (fun field ->
|
||||
let size = CliType.SizeOf field.Contents
|
||||
|
||||
{
|
||||
Name = field.Name
|
||||
Contents = field.Contents
|
||||
Offset = field.Offset.Value
|
||||
Size = size.Size
|
||||
Alignment = size.Alignment
|
||||
ConfiguredOffset = field.Offset
|
||||
EditedAtTime = 0UL
|
||||
Type = field.Type
|
||||
}
|
||||
)
|
||||
|
||||
| _ :: _, _ :: _ -> failwith "unexpectedly mixed explicit and automatic layout of fields"
|
||||
|
||||
static member ToBytes (cvt : CliValueType) : byte[] =
|
||||
let bytes = Array.zeroCreate<byte> (CliValueType.SizeOf(cvt).Size)
|
||||
|
||||
cvt._Fields
|
||||
|> List.sortBy _.EditedAtTime
|
||||
|> List.iter (fun candidateField ->
|
||||
let fieldBytes : byte[] = CliType.ToBytes candidateField.Contents
|
||||
|
||||
for i = 0 to candidateField.Size - 1 do
|
||||
bytes.[candidateField.Offset + i] <- fieldBytes.[i]
|
||||
)
|
||||
|
||||
bytes
|
||||
|
||||
static member OfFields (layout : Layout) (f : CliField list) : CliValueType =
|
||||
let fields = CliValueType.ComputeConcreteFields layout f
|
||||
|
||||
{
|
||||
_Fields = fields
|
||||
Layout = layout
|
||||
NextTimestamp = 1UL
|
||||
}
|
||||
|
||||
static member AddField (f : CliField) (vt : CliValueType) : CliValueType =
|
||||
// Recompute all fields with the new one added
|
||||
// TODO: the existence of this function at all is rather dubious, but it's there
|
||||
// at the moment to support delegate types.
|
||||
// The whole function is just a bodge and it will hopefully go away soon; I just don't know how.
|
||||
let prevFields = vt._Fields |> List.map (fun f -> f.Name, f) |> Map.ofList
|
||||
|
||||
let allFields =
|
||||
f
|
||||
:: (vt._Fields
|
||||
|> List.map (fun cf ->
|
||||
{
|
||||
Name = cf.Name
|
||||
Contents = cf.Contents
|
||||
Offset =
|
||||
match vt.Layout with
|
||||
| Layout.Default -> None
|
||||
| Layout.Custom _ -> Some cf.Offset
|
||||
Type = cf.Type
|
||||
}
|
||||
))
|
||||
|
||||
let newFields =
|
||||
CliValueType.ComputeConcreteFields vt.Layout allFields
|
||||
|> List.map (fun field ->
|
||||
match Map.tryFind field.Name prevFields with
|
||||
| Some prev ->
|
||||
{ field with
|
||||
EditedAtTime = prev.EditedAtTime
|
||||
}
|
||||
| None ->
|
||||
{ field with
|
||||
EditedAtTime = vt.NextTimestamp
|
||||
}
|
||||
)
|
||||
|
||||
{
|
||||
_Fields = newFields
|
||||
Layout = vt.Layout
|
||||
NextTimestamp = vt.NextTimestamp + 1UL
|
||||
}
|
||||
|
||||
/// Returns the offset and size.
|
||||
static member GetFieldLayout (field : string) (cvt : CliValueType) : int * int =
|
||||
let targetField =
|
||||
cvt._Fields
|
||||
|> List.tryFind (fun f -> f.Name = field)
|
||||
|> Option.defaultWith (fun () -> failwithf $"Field '%s{field}' not found")
|
||||
|
||||
targetField.Offset, targetField.Size
|
||||
|
||||
// TODO: use DereferenceFieldAt for the implementation.
|
||||
// We should eventually be able to dereference an arbitrary field of a struct
|
||||
// as though it were any other field of any other type, to accommodate Unsafe.As.
|
||||
static member DereferenceField (field : string) (cvt : CliValueType) : CliType =
|
||||
let targetField =
|
||||
cvt._Fields
|
||||
|> List.tryFind (fun f -> f.Name = field)
|
||||
|> Option.defaultWith (fun () -> failwithf $"Field '%s{field}' not found")
|
||||
|
||||
// Identify all fields that overlap with the target field's memory range
|
||||
let targetStart = targetField.Offset
|
||||
let targetEnd = targetField.Offset + targetField.Size
|
||||
|
||||
let affectedFields =
|
||||
cvt._Fields
|
||||
|> List.filter (fun f ->
|
||||
let fieldStart = f.Offset
|
||||
let fieldEnd = f.Offset + f.Size
|
||||
// Fields overlap if their ranges intersect
|
||||
fieldStart < targetEnd && targetStart < fieldEnd
|
||||
)
|
||||
|
||||
match affectedFields with
|
||||
| [] -> failwith "unexpectedly didn't dereference a field"
|
||||
| [ f ] -> f.Contents
|
||||
| fields ->
|
||||
let bytes = CliValueType.ToBytes cvt
|
||||
|
||||
let fieldBytes =
|
||||
bytes.[targetField.Offset .. targetField.Offset + targetField.Size - 1]
|
||||
|
||||
CliType.OfBytesAsType targetField.Type fieldBytes
|
||||
|
||||
static member FieldsAt (offset : int) (cvt : CliValueType) : CliConcreteField list =
|
||||
cvt._Fields |> List.filter (fun f -> f.Offset = offset)
|
||||
|
||||
static member DereferenceFieldAt (offset : int) (size : int) (cvt : CliValueType) : CliType =
|
||||
let targetField =
|
||||
CliValueType.FieldsAt offset cvt |> List.tryFind (fun f -> f.Size = size)
|
||||
|
||||
match targetField with
|
||||
| None -> failwith "TODO: couldn't find the field"
|
||||
| Some f -> f.Contents
|
||||
|
||||
static member SizeOf (vt : CliValueType) : SizeofResult =
|
||||
let minimumSize, packingSize =
|
||||
match vt.Layout with
|
||||
| Layout.Custom (size = size ; packingSize = packing) ->
|
||||
size, if packing = 0 then DEFAULT_STRUCT_ALIGNMENT else packing
|
||||
| Layout.Default -> 0, DEFAULT_STRUCT_ALIGNMENT
|
||||
|
||||
if vt._Fields.IsEmpty then
|
||||
{
|
||||
Size = minimumSize
|
||||
Alignment = 1
|
||||
}
|
||||
else
|
||||
// Now we can just use the precomputed offsets and sizes
|
||||
let finalOffset, alignment =
|
||||
vt._Fields
|
||||
|> List.fold
|
||||
(fun (maxEnd, maxAlign) field ->
|
||||
let fieldEnd = field.Offset + field.Size
|
||||
let alignmentCap = min field.Alignment packingSize
|
||||
max maxEnd fieldEnd, max maxAlign alignmentCap
|
||||
)
|
||||
(0, 0)
|
||||
|
||||
let error = finalOffset % alignment
|
||||
|
||||
let size =
|
||||
if error = 0 then
|
||||
finalOffset
|
||||
else
|
||||
finalOffset + (alignment - error)
|
||||
|
||||
{
|
||||
Size = max size minimumSize
|
||||
Alignment = alignment
|
||||
}
|
||||
|
||||
/// Sets the value of the specified field, *without* touching any overlapping fields.
|
||||
/// `DereferenceField` handles resolving conflicts between overlapping fields.
|
||||
static member WithFieldSet (field : string) (value : CliType) (cvt : CliValueType) : CliValueType =
|
||||
{
|
||||
Layout = cvt.Layout
|
||||
_Fields =
|
||||
cvt._Fields
|
||||
|> List.replaceWhere (fun f ->
|
||||
if f.Name = field then
|
||||
{ f with
|
||||
Contents = value
|
||||
EditedAtTime = cvt.NextTimestamp
|
||||
}
|
||||
|> Some
|
||||
else
|
||||
None
|
||||
)
|
||||
NextTimestamp = cvt.NextTimestamp + 1UL
|
||||
}
|
||||
|
||||
/// To facilitate bodges. This function absolutely should not exist.
|
||||
static member TryExactlyOneField (cvt : CliValueType) : CliField option =
|
||||
match cvt._Fields with
|
||||
| [] -> None
|
||||
| [ x ] ->
|
||||
if x.Offset = 0 then
|
||||
Some (CliConcreteField.ToCliField x)
|
||||
else
|
||||
None
|
||||
| _ -> None
|
||||
|
||||
/// To facilitate bodges. This function absolutely should not exist.
|
||||
static member TrySequentialFields (cvt : CliValueType) : CliField list option =
|
||||
let isNone, isSome =
|
||||
cvt._Fields |> List.partition (fun field -> field.ConfiguredOffset.IsNone)
|
||||
|
||||
match isSome with
|
||||
| [] -> Some (isNone |> List.map CliConcreteField.ToCliField)
|
||||
| [ field ] when field.ConfiguredOffset = Some 0 -> Some [ CliConcreteField.ToCliField field ]
|
||||
| _ -> None
|
||||
|
||||
type CliTypeResolutionResult =
|
||||
| Resolved of CliType
|
||||
@@ -167,7 +534,22 @@ type CliTypeResolutionResult =
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module CliType =
|
||||
let zeroOfPrimitive (primitiveType : PrimitiveType) : CliType =
|
||||
/// In fact any non-zero value will do for True, but we'll use 1
|
||||
let ofBool (b : bool) : CliType = CliType.Bool (if b then 1uy else 0uy)
|
||||
|
||||
let ofChar (c : char) : CliType =
|
||||
CliType.Char (byte (int c / 256), byte (int c % 256))
|
||||
|
||||
let ofManagedObject (ptr : ManagedHeapAddress) : CliType = CliType.ObjectRef (Some ptr)
|
||||
|
||||
let sizeOf (ty : CliType) : int = CliType.SizeOf(ty).Size
|
||||
|
||||
let zeroOfPrimitive
|
||||
(concreteTypes : AllConcreteTypes)
|
||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||
(primitiveType : PrimitiveType)
|
||||
: CliType
|
||||
=
|
||||
match primitiveType with
|
||||
| PrimitiveType.Boolean -> CliType.Bool 0uy
|
||||
| PrimitiveType.Char -> CliType.Char (0uy, 0uy)
|
||||
@@ -187,8 +569,40 @@ module CliType =
|
||||
| PrimitiveType.Double -> CliType.Numeric (CliNumericType.Float64 0.0)
|
||||
| PrimitiveType.String -> CliType.ObjectRef None
|
||||
| PrimitiveType.TypedReference -> failwith "todo"
|
||||
| PrimitiveType.IntPtr -> CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
|
||||
| PrimitiveType.UIntPtr -> CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
|
||||
| PrimitiveType.IntPtr ->
|
||||
{
|
||||
Name = "_value"
|
||||
Contents =
|
||||
CliType.Numeric (
|
||||
CliNumericType.NativeInt (NativeIntSource.ManagedPointer ManagedPointerSource.Null)
|
||||
)
|
||||
Offset = None
|
||||
Type =
|
||||
AllConcreteTypes.findExistingConcreteType
|
||||
concreteTypes
|
||||
(corelib.IntPtr.Assembly, corelib.IntPtr.Namespace, corelib.IntPtr.Name, ImmutableArray.Empty)
|
||||
|> Option.get
|
||||
}
|
||||
|> List.singleton
|
||||
|> CliValueType.OfFields Layout.Default
|
||||
|> CliType.ValueType
|
||||
| PrimitiveType.UIntPtr ->
|
||||
{
|
||||
Name = "_value"
|
||||
Contents =
|
||||
CliType.Numeric (
|
||||
CliNumericType.NativeInt (NativeIntSource.ManagedPointer ManagedPointerSource.Null)
|
||||
)
|
||||
Offset = None
|
||||
Type =
|
||||
AllConcreteTypes.findExistingConcreteType
|
||||
concreteTypes
|
||||
(corelib.UIntPtr.Assembly, corelib.UIntPtr.Namespace, corelib.UIntPtr.Name, ImmutableArray.Empty)
|
||||
|> Option.get
|
||||
}
|
||||
|> List.singleton
|
||||
|> CliValueType.OfFields Layout.Default
|
||||
|> CliType.ValueType
|
||||
| PrimitiveType.Object -> CliType.ObjectRef None
|
||||
|
||||
let rec zeroOf
|
||||
@@ -213,11 +627,11 @@ module CliType =
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Byref _ ->
|
||||
// Byref types are managed references - the zero value is a null reference
|
||||
CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null), concreteTypes
|
||||
CliType.RuntimePointer (CliRuntimePointer.Managed ManagedPointerSource.Null), concreteTypes
|
||||
|
||||
| ConcreteTypeHandle.Pointer _ ->
|
||||
// Pointer types are unmanaged pointers - the zero value is a null pointer
|
||||
CliType.RuntimePointer (CliRuntimePointer.Unmanaged 0L), concreteTypes
|
||||
CliType.RuntimePointer (CliRuntimePointer.Managed ManagedPointerSource.Null), concreteTypes
|
||||
|
||||
| ConcreteTypeHandle.Concrete _ ->
|
||||
// This is a concrete type - look it up in the mapping
|
||||
@@ -231,72 +645,82 @@ module CliType =
|
||||
let typeDef = assembly.TypeDefs.[concreteType.Definition.Get]
|
||||
|
||||
// Check if it's a primitive type by comparing with corelib types FIRST
|
||||
if concreteType.Assembly = corelib.Corelib.Name && concreteType.Generics.IsEmpty then
|
||||
if
|
||||
concreteType.Assembly.FullName = corelib.Corelib.Name.FullName
|
||||
&& concreteType.Generics.IsEmpty
|
||||
then
|
||||
// Check against known primitive types
|
||||
if TypeInfo.NominallyEqual typeDef corelib.Boolean then
|
||||
zeroOfPrimitive PrimitiveType.Boolean, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.Boolean, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.Char then
|
||||
zeroOfPrimitive PrimitiveType.Char, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.Char, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.SByte then
|
||||
zeroOfPrimitive PrimitiveType.SByte, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.SByte, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.Byte then
|
||||
zeroOfPrimitive PrimitiveType.Byte, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.Byte, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.Int16 then
|
||||
zeroOfPrimitive PrimitiveType.Int16, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.Int16, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.UInt16 then
|
||||
zeroOfPrimitive PrimitiveType.UInt16, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.UInt16, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.Int32 then
|
||||
zeroOfPrimitive PrimitiveType.Int32, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.Int32, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.UInt32 then
|
||||
zeroOfPrimitive PrimitiveType.UInt32, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.UInt32, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.Int64 then
|
||||
zeroOfPrimitive PrimitiveType.Int64, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.Int64, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.UInt64 then
|
||||
zeroOfPrimitive PrimitiveType.UInt64, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.UInt64, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.Single then
|
||||
zeroOfPrimitive PrimitiveType.Single, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.Single, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.Double then
|
||||
zeroOfPrimitive PrimitiveType.Double, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.Double, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.String then
|
||||
zeroOfPrimitive PrimitiveType.String, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.String, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.Object then
|
||||
zeroOfPrimitive PrimitiveType.Object, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.Object, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.IntPtr then
|
||||
zeroOfPrimitive PrimitiveType.IntPtr, concreteTypes
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.IntPtr, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.UIntPtr then
|
||||
zeroOfPrimitive PrimitiveType.UIntPtr, concreteTypes
|
||||
else if
|
||||
// Check if it's an array type
|
||||
typeDef = corelib.Array
|
||||
then
|
||||
CliType.ObjectRef None, concreteTypes // Arrays are reference types
|
||||
zeroOfPrimitive concreteTypes corelib PrimitiveType.UIntPtr, concreteTypes
|
||||
elif TypeInfo.NominallyEqual typeDef corelib.Array then
|
||||
// Arrays are reference types
|
||||
CliType.ObjectRef None, concreteTypes
|
||||
else if
|
||||
|
||||
// Not a known primitive, now check for cycles
|
||||
Set.contains handle visited
|
||||
then
|
||||
// We're in a cycle - return a default zero value for the type
|
||||
// For value types in cycles, we'll return a null reference as a safe fallback
|
||||
// This should only happen with self-referential types
|
||||
// Value types can't be self-referential unless they are specifically known to the
|
||||
// runtime - for example, System.Byte is a value type with a single field,
|
||||
// of type System.Byte.
|
||||
// Since we check for (nominal) equality against all such types in the first branch,
|
||||
// this code path is only hit with reference types.
|
||||
CliType.ObjectRef None, concreteTypes
|
||||
else
|
||||
let visited = Set.add handle visited
|
||||
// Not a known primitive, check if it's a value type or reference type
|
||||
determineZeroForCustomType concreteTypes assemblies corelib handle concreteType typeDef visited
|
||||
else if
|
||||
|
||||
// Not from corelib or has generics
|
||||
concreteType.Assembly = corelib.Corelib.Name
|
||||
&& typeDef = corelib.Array
|
||||
&& concreteType.Generics.Length = 1
|
||||
then
|
||||
// This is an array type
|
||||
// This is an array type, so null is appropriate
|
||||
CliType.ObjectRef None, concreteTypes
|
||||
else if
|
||||
|
||||
// Custom type - now check for cycles
|
||||
Set.contains handle visited
|
||||
then
|
||||
// We're in a cycle - return a default zero value for the type
|
||||
// For value types in cycles, we'll return a null reference as a safe fallback
|
||||
// This should only happen with self-referential types
|
||||
// We're in a cycle - return a default zero value for the type.
|
||||
// Value types can't be self-referential unless they are specifically known to the
|
||||
// runtime - for example, System.Byte is a value type with a single field,
|
||||
// of type System.Byte.
|
||||
// Since we check for (nominal) equality against all such types in the first branch,
|
||||
// this code path is only hit with reference types.
|
||||
CliType.ObjectRef None, concreteTypes
|
||||
else
|
||||
let visited = Set.add handle visited
|
||||
@@ -309,7 +733,7 @@ module CliType =
|
||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||
(handle : ConcreteTypeHandle)
|
||||
(concreteType : ConcreteType<ConcreteTypeHandle>)
|
||||
(typeDef : WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>)
|
||||
(typeDef : WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn>)
|
||||
(visited : Set<ConcreteTypeHandle>)
|
||||
: CliType * AllConcreteTypes
|
||||
=
|
||||
@@ -326,7 +750,7 @@ module CliType =
|
||||
// It's a value type - need to create zero values for all non-static fields
|
||||
let mutable currentConcreteTypes = concreteTypes
|
||||
|
||||
let fieldZeros =
|
||||
let vt =
|
||||
typeDef.Fields
|
||||
|> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static))
|
||||
|> List.map (fun field ->
|
||||
@@ -342,10 +766,17 @@ module CliType =
|
||||
zeroOfWithVisited currentConcreteTypes assemblies corelib fieldHandle visited
|
||||
|
||||
currentConcreteTypes <- updatedConcreteTypes2
|
||||
(field.Name, fieldZero)
|
||||
)
|
||||
|
||||
CliType.ValueType fieldZeros, currentConcreteTypes
|
||||
{
|
||||
Name = field.Name
|
||||
Contents = fieldZero
|
||||
Offset = field.Offset
|
||||
Type = fieldHandle
|
||||
}
|
||||
)
|
||||
|> CliValueType.OfFields typeDef.Layout
|
||||
|
||||
CliType.ValueType vt, currentConcreteTypes
|
||||
else
|
||||
// It's a reference type
|
||||
CliType.ObjectRef None, concreteTypes
|
||||
@@ -369,31 +800,70 @@ module CliType =
|
||||
}
|
||||
|
||||
// The field type might reference generic parameters of the declaring type
|
||||
let typeGenerics = declaringType.Generics |> ImmutableArray.CreateRange
|
||||
let methodGenerics = ImmutableArray.Empty // Fields don't have method generics
|
||||
|
||||
let loadAssembly
|
||||
(assyName : AssemblyName)
|
||||
(ref : AssemblyReferenceHandle)
|
||||
: (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly)
|
||||
=
|
||||
match assemblies.TryGetValue assyName.FullName with
|
||||
| true, currentAssy ->
|
||||
let targetAssyRef = currentAssy.AssemblyReferences.[ref]
|
||||
let loadAssembly =
|
||||
{ new IAssemblyLoad with
|
||||
member _.LoadAssembly loaded assyName ref =
|
||||
match loaded.TryGetValue assyName.FullName with
|
||||
| true, currentAssy ->
|
||||
let targetAssyRef = currentAssy.AssemblyReferences.[ref]
|
||||
|
||||
match assemblies.TryGetValue targetAssyRef.Name.FullName with
|
||||
| true, targetAssy -> assemblies, targetAssy
|
||||
| false, _ ->
|
||||
failwithf "Assembly %s not loaded when trying to resolve reference" targetAssyRef.Name.FullName
|
||||
| false, _ -> failwithf "Current assembly %s not loaded when trying to resolve reference" assyName.FullName
|
||||
match loaded.TryGetValue targetAssyRef.Name.FullName with
|
||||
| true, targetAssy -> loaded, targetAssy
|
||||
| false, _ ->
|
||||
failwithf
|
||||
"Assembly %s not loaded when trying to resolve reference"
|
||||
targetAssyRef.Name.FullName
|
||||
| false, _ ->
|
||||
failwithf "Current assembly %s not loaded when trying to resolve reference" assyName.FullName
|
||||
}
|
||||
|
||||
let handle, newCtx =
|
||||
TypeConcretization.concretizeType
|
||||
ctx
|
||||
loadAssembly
|
||||
declaringType.Assembly
|
||||
typeGenerics
|
||||
declaringType.Generics
|
||||
methodGenerics
|
||||
fieldType
|
||||
|
||||
handle, newCtx.ConcreteTypes
|
||||
|
||||
let withFieldSet (field : string) (value : CliType) (c : CliType) : CliType =
|
||||
match c with
|
||||
| CliType.Numeric cliNumericType -> failwith "todo"
|
||||
| CliType.Bool b -> failwith "todo"
|
||||
| CliType.Char (high, low) -> failwith "todo"
|
||||
| CliType.ObjectRef managedHeapAddressOption -> failwith "todo"
|
||||
| CliType.RuntimePointer cliRuntimePointer -> failwith "todo"
|
||||
| CliType.ValueType cvt -> CliValueType.WithFieldSet field value cvt |> CliType.ValueType
|
||||
|
||||
let getField (field : string) (value : CliType) : CliType =
|
||||
match value with
|
||||
| CliType.Numeric cliNumericType -> failwith "todo"
|
||||
| CliType.Bool b -> failwith "todo"
|
||||
| CliType.Char (high, low) -> failwith "todo"
|
||||
| CliType.ObjectRef managedHeapAddressOption -> failwith "todo"
|
||||
| CliType.RuntimePointer cliRuntimePointer -> failwith "todo"
|
||||
| CliType.ValueType cvt -> CliValueType.DereferenceField field cvt
|
||||
|
||||
/// Returns the offset and size.
|
||||
let getFieldLayout (field : string) (value : CliType) : int * int =
|
||||
match value with
|
||||
| CliType.Numeric cliNumericType -> failwith "todo"
|
||||
| CliType.Bool b -> failwith "todo"
|
||||
| CliType.Char (high, low) -> failwith "todo"
|
||||
| CliType.ObjectRef managedHeapAddressOption -> failwith "todo"
|
||||
| CliType.RuntimePointer cliRuntimePointer -> failwith "todo"
|
||||
| CliType.ValueType cvt -> CliValueType.GetFieldLayout field cvt
|
||||
|
||||
/// Returns None if there isn't *exactly* one field that starts there. This rules out some valid programs.
|
||||
let getFieldAt (offset : int) (value : CliType) : CliConcreteField option =
|
||||
match value with
|
||||
| CliType.Numeric cliNumericType -> failwith "todo"
|
||||
| CliType.Bool b -> failwith "todo"
|
||||
| CliType.Char (high, low) -> failwith "todo"
|
||||
| CliType.ObjectRef managedHeapAddressOption -> failwith "todo"
|
||||
| CliType.RuntimePointer cliRuntimePointer -> failwith "todo"
|
||||
| CliType.ValueType cvt -> CliValueType.FieldsAt offset cvt |> List.tryExactlyOne
|
||||
|
@@ -4,17 +4,68 @@ namespace WoofWare.PawPrint
|
||||
|
||||
type IArithmeticOperation =
|
||||
abstract Int32Int32 : int32 -> int32 -> int32
|
||||
abstract Int32NativeInt : int32 -> nativeint -> nativeint
|
||||
abstract NativeIntInt32 : nativeint -> int32 -> nativeint
|
||||
abstract Int64Int64 : int64 -> int64 -> int64
|
||||
abstract FloatFloat : float -> float -> float
|
||||
abstract NativeIntNativeInt : nativeint -> nativeint -> nativeint
|
||||
abstract Int32ManagedPtr : IlMachineState -> int32 -> ManagedPointerSource -> Choice<ManagedPointerSource, int>
|
||||
abstract ManagedPtrInt32 : IlMachineState -> ManagedPointerSource -> int32 -> Choice<ManagedPointerSource, int>
|
||||
|
||||
abstract ManagedPtrManagedPtr :
|
||||
IlMachineState -> ManagedPointerSource -> ManagedPointerSource -> Choice<ManagedPointerSource, nativeint>
|
||||
|
||||
abstract Name : string
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module ArithmeticOperation =
|
||||
let private addInt32ManagedPtr state v ptr =
|
||||
match ptr with
|
||||
| LocalVariable (sourceThread, methodFrame, whichVar) -> failwith "refusing to add to a local variable address"
|
||||
| Argument (sourceThread, methodFrame, whichVar) -> failwith "refusing to add to an argument address"
|
||||
| Heap managedHeapAddress -> failwith "refusing to add to a heap address"
|
||||
| ArrayIndex (arr, index) -> failwith "TODO: arrays"
|
||||
| Field (src, fieldName) ->
|
||||
let obj = IlMachineState.dereferencePointer state src
|
||||
let offset, _ = CliType.getFieldLayout fieldName obj
|
||||
|
||||
match CliType.getFieldAt (offset + v) obj with
|
||||
| None -> failwith "TODO: couldn't identify field at offset"
|
||||
| Some field ->
|
||||
ManagedPointerSource.Field (src, CliConcreteField.ToCliField(field).Name)
|
||||
|> Choice1Of2
|
||||
| Null -> Choice2Of2 v
|
||||
| InterpretedAsType (managedPointerSource, concreteType) -> failwith "todo"
|
||||
|
||||
let private mulInt32ManagedPtr (state : IlMachineState) v ptr =
|
||||
if v = 0 then
|
||||
Choice2Of2 0
|
||||
elif v = 1 then
|
||||
Choice1Of2 ptr
|
||||
else
|
||||
|
||||
match ptr with
|
||||
| ManagedPointerSource.Null -> Choice2Of2 0
|
||||
| _ -> failwith "refusing to multiply pointers"
|
||||
|
||||
let add =
|
||||
{ new IArithmeticOperation with
|
||||
member _.Int32Int32 a b = (# "add" a b : int32 #)
|
||||
member _.Int64Int64 a b = (# "add" a b : int64 #)
|
||||
member _.FloatFloat a b = (# "add" a b : float #)
|
||||
member _.NativeIntNativeInt a b = (# "add" a b : nativeint #)
|
||||
member _.Int32NativeInt a b = (# "add" a b : nativeint #)
|
||||
member _.NativeIntInt32 a b = (# "add" a b : nativeint #)
|
||||
|
||||
member _.ManagedPtrManagedPtr _ ptr1 ptr2 =
|
||||
match ptr1, ptr2 with
|
||||
| ManagedPointerSource.Null, _ -> Choice1Of2 ptr2
|
||||
| _, ManagedPointerSource.Null -> Choice1Of2 ptr1
|
||||
| _, _ -> failwith "refusing to add two managed pointers"
|
||||
|
||||
member _.Int32ManagedPtr state val1 ptr2 = addInt32ManagedPtr state val1 ptr2
|
||||
member _.ManagedPtrInt32 state ptr1 val2 = addInt32ManagedPtr state val2 ptr1
|
||||
|
||||
member _.Name = "add"
|
||||
}
|
||||
|
||||
@@ -23,6 +74,42 @@ module ArithmeticOperation =
|
||||
member _.Int32Int32 a b = (# "sub" a b : int32 #)
|
||||
member _.Int64Int64 a b = (# "sub" a b : int64 #)
|
||||
member _.FloatFloat a b = (# "sub" a b : float #)
|
||||
member _.NativeIntNativeInt a b = (# "sub" a b : nativeint #)
|
||||
member _.Int32NativeInt a b = (# "sub" a b : nativeint #)
|
||||
member _.NativeIntInt32 a b = (# "sub" a b : nativeint #)
|
||||
|
||||
member _.ManagedPtrManagedPtr state ptr1 ptr2 =
|
||||
match ptr1, ptr2 with
|
||||
| ptr1, ManagedPointerSource.Null -> Choice1Of2 ptr1
|
||||
| ManagedPointerSource.Null, _ -> failwith "refusing to create negative pointer"
|
||||
| ManagedPointerSource.ArrayIndex (arr1, index1), ManagedPointerSource.ArrayIndex (arr2, index2) ->
|
||||
if arr1 <> arr2 then
|
||||
failwith "refusing to operate on pointers to different arrays"
|
||||
|
||||
(index1 - index2) |> nativeint |> Choice2Of2
|
||||
| ManagedPointerSource.ArrayIndex _, _ -> failwith $"refusing to operate on array index ptr vs %O{ptr2}"
|
||||
| ManagedPointerSource.Argument _, _
|
||||
| _, ManagedPointerSource.Argument _ ->
|
||||
failwith $"refusing to operate on pointers to arguments: %O{ptr1} and %O{ptr2}"
|
||||
| ManagedPointerSource.Field (obj1, fieldName1), ManagedPointerSource.Field (obj2, fieldName2) ->
|
||||
if obj1 <> obj2 then
|
||||
failwith "refusing to operate on field pointers in different objects"
|
||||
|
||||
let obj = IlMachineState.dereferencePointer state obj1
|
||||
|
||||
let offset1, _ = CliType.getFieldLayout fieldName1 obj
|
||||
let offset2, _ = CliType.getFieldLayout fieldName2 obj
|
||||
|
||||
(offset1 - offset2) |> nativeint |> Choice2Of2
|
||||
| _, _ -> failwith "TODO"
|
||||
|
||||
member _.Int32ManagedPtr state val1 ptr2 =
|
||||
match ptr2 with
|
||||
| ManagedPointerSource.Null -> Choice2Of2 val1
|
||||
| _ -> failwith "refusing to subtract a pointer"
|
||||
|
||||
member _.ManagedPtrInt32 state ptr1 val2 = failwith "TODO: subtract from pointer"
|
||||
|
||||
member _.Name = "sub"
|
||||
}
|
||||
|
||||
@@ -31,21 +118,143 @@ module ArithmeticOperation =
|
||||
member _.Int32Int32 a b = (# "mul" a b : int32 #)
|
||||
member _.Int64Int64 a b = (# "mul" a b : int64 #)
|
||||
member _.FloatFloat a b = (# "mul" a b : float #)
|
||||
member _.NativeIntNativeInt a b = (# "mul" a b : nativeint #)
|
||||
member _.Int32NativeInt a b = (# "mul" a b : nativeint #)
|
||||
member _.NativeIntInt32 a b = (# "mul" a b : nativeint #)
|
||||
|
||||
member _.ManagedPtrManagedPtr _ ptr1 ptr2 =
|
||||
match ptr1, ptr2 with
|
||||
| ManagedPointerSource.Null, _ -> Choice2Of2 (nativeint 0)
|
||||
| _, ManagedPointerSource.Null -> Choice2Of2 (nativeint 0)
|
||||
| _, _ -> failwith "refusing to multiply two managed pointers"
|
||||
|
||||
member _.Int32ManagedPtr state a ptr = mulInt32ManagedPtr state a ptr
|
||||
member _.ManagedPtrInt32 state ptr a = mulInt32ManagedPtr state a ptr
|
||||
|
||||
member _.Name = "mul"
|
||||
}
|
||||
|
||||
let rem =
|
||||
{ new IArithmeticOperation with
|
||||
member _.Int32Int32 a b = (# "rem" a b : int32 #)
|
||||
member _.Int64Int64 a b = (# "rem" a b : int64 #)
|
||||
member _.FloatFloat a b = (# "rem" a b : float #)
|
||||
member _.NativeIntNativeInt a b = (# "rem" a b : nativeint #)
|
||||
member _.Int32NativeInt a b = (# "rem" a b : nativeint #)
|
||||
member _.NativeIntInt32 a b = (# "rem" a b : nativeint #)
|
||||
|
||||
member _.ManagedPtrManagedPtr _ ptr1 ptr2 = failwith "refusing to rem pointers"
|
||||
|
||||
member _.Int32ManagedPtr _ a ptr = failwith "refusing to rem pointer"
|
||||
|
||||
member _.ManagedPtrInt32 _ ptr a = failwith "refusing to rem pointer"
|
||||
|
||||
member _.Name = "mul"
|
||||
}
|
||||
|
||||
let mulOvf =
|
||||
{ new IArithmeticOperation with
|
||||
member _.Int32Int32 a b = (# "mul.ovf" a b : int32 #)
|
||||
member _.Int64Int64 a b = (# "mul.ovf" a b : int64 #)
|
||||
member _.FloatFloat a b = (# "mul.ovf" a b : float #)
|
||||
member _.NativeIntNativeInt a b = (# "mul.ovf" a b : nativeint #)
|
||||
member _.Int32NativeInt a b = (# "mul.ovf" a b : nativeint #)
|
||||
member _.NativeIntInt32 a b = (# "mul.ovf" a b : nativeint #)
|
||||
|
||||
member _.ManagedPtrManagedPtr _ ptr1 ptr2 =
|
||||
match ptr1, ptr2 with
|
||||
| ManagedPointerSource.Null, _ -> Choice2Of2 (nativeint 0)
|
||||
| _, ManagedPointerSource.Null -> Choice2Of2 (nativeint 0)
|
||||
| _, _ -> failwith "refusing to multiply two managed pointers"
|
||||
|
||||
member _.Int32ManagedPtr state a ptr = mulInt32ManagedPtr state a ptr
|
||||
member _.ManagedPtrInt32 state a ptr = mulInt32ManagedPtr state ptr a
|
||||
|
||||
member _.Name = "mul_ovf"
|
||||
}
|
||||
|
||||
let div =
|
||||
{ new IArithmeticOperation with
|
||||
member _.Int32Int32 a b = (# "div" a b : int32 #)
|
||||
member _.Int64Int64 a b = (# "div" a b : int64 #)
|
||||
member _.FloatFloat a b = (# "div" a b : float #)
|
||||
member _.NativeIntNativeInt a b = (# "div" a b : nativeint #)
|
||||
member _.Int32NativeInt a b = (# "div" a b : nativeint #)
|
||||
member _.NativeIntInt32 a b = (# "div" a b : nativeint #)
|
||||
|
||||
member _.ManagedPtrManagedPtr _ ptr1 ptr2 =
|
||||
match ptr1, ptr2 with
|
||||
| ManagedPointerSource.Null, _ -> Choice2Of2 (nativeint 0)
|
||||
| _, _ -> failwith "refusing to divide two managed pointers"
|
||||
|
||||
member _.Int32ManagedPtr _ a ptr =
|
||||
if a = 0 then
|
||||
Choice2Of2 0
|
||||
else
|
||||
failwith "refusing to divide pointers"
|
||||
|
||||
member _.ManagedPtrInt32 _ ptr a =
|
||||
if a = 1 then
|
||||
Choice1Of2 ptr
|
||||
else
|
||||
failwith "refusing to divide a pointer"
|
||||
|
||||
member _.Name = "div"
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module BinaryArithmetic =
|
||||
let execute (op : IArithmeticOperation) (val1 : EvalStackValue) (val2 : EvalStackValue) : EvalStackValue =
|
||||
let execute
|
||||
(op : IArithmeticOperation)
|
||||
(state : IlMachineState)
|
||||
(val1 : EvalStackValue)
|
||||
(val2 : EvalStackValue)
|
||||
: EvalStackValue
|
||||
=
|
||||
// see table at https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.add?view=net-9.0
|
||||
match val1, val2 with
|
||||
| EvalStackValue.Int32 val1, EvalStackValue.Int32 val2 -> op.Int32Int32 val1 val2 |> EvalStackValue.Int32
|
||||
| EvalStackValue.Int32 val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.NativeInt
|
||||
| EvalStackValue.Int32 val1, EvalStackValue.ManagedPointer val2 -> failwith "" |> EvalStackValue.ManagedPointer
|
||||
| EvalStackValue.Int32 val1, EvalStackValue.NativeInt val2 ->
|
||||
let val2 =
|
||||
match val2 with
|
||||
| NativeIntSource.Verbatim n -> nativeint<int64> n
|
||||
| v -> failwith $"refusing to operate on non-verbatim native int %O{v}"
|
||||
|
||||
op.Int32NativeInt val1 val2
|
||||
|> int64<nativeint>
|
||||
|> NativeIntSource.Verbatim
|
||||
|> EvalStackValue.NativeInt
|
||||
| EvalStackValue.Int32 val1, EvalStackValue.ManagedPointer val2 ->
|
||||
match op.Int32ManagedPtr state val1 val2 with
|
||||
| Choice1Of2 v -> EvalStackValue.ManagedPointer v
|
||||
| Choice2Of2 i -> EvalStackValue.Int32 i
|
||||
| EvalStackValue.Int32 val1, EvalStackValue.ObjectRef val2 -> failwith "" |> EvalStackValue.ObjectRef
|
||||
| EvalStackValue.Int64 val1, EvalStackValue.Int64 val2 -> op.Int64Int64 val1 val2 |> EvalStackValue.Int64
|
||||
| EvalStackValue.NativeInt val1, EvalStackValue.Int32 val2 -> failwith "" |> EvalStackValue.NativeInt
|
||||
| EvalStackValue.NativeInt val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.NativeInt
|
||||
| EvalStackValue.NativeInt val1, EvalStackValue.Int32 val2 ->
|
||||
let val1 =
|
||||
match val1 with
|
||||
| NativeIntSource.Verbatim n -> nativeint<int64> n
|
||||
| v -> failwith $"refusing to operate on non-verbatim native int %O{v}"
|
||||
|
||||
op.NativeIntInt32 val1 val2
|
||||
|> int64<nativeint>
|
||||
|> NativeIntSource.Verbatim
|
||||
|> EvalStackValue.NativeInt
|
||||
| EvalStackValue.NativeInt val1, EvalStackValue.NativeInt val2 ->
|
||||
let val1 =
|
||||
match val1 with
|
||||
| NativeIntSource.Verbatim n -> nativeint<int64> n
|
||||
| v -> failwith $"refusing to operate on non-verbatim native int %O{v}"
|
||||
|
||||
let val2 =
|
||||
match val2 with
|
||||
| NativeIntSource.Verbatim n -> nativeint<int64> n
|
||||
| v -> failwith $"refusing to operate on non-verbatim native int %O{v}"
|
||||
|
||||
op.NativeIntNativeInt val1 val2
|
||||
|> int64<nativeint>
|
||||
|> NativeIntSource.Verbatim
|
||||
|> EvalStackValue.NativeInt
|
||||
| EvalStackValue.NativeInt val1, EvalStackValue.ManagedPointer val2 ->
|
||||
failwith "" |> EvalStackValue.ManagedPointer
|
||||
| EvalStackValue.NativeInt val1, EvalStackValue.ObjectRef val2 -> failwith "" |> EvalStackValue.ObjectRef
|
||||
@@ -53,6 +262,13 @@ module BinaryArithmetic =
|
||||
| EvalStackValue.ManagedPointer val1, EvalStackValue.NativeInt val2 ->
|
||||
failwith "" |> EvalStackValue.ManagedPointer
|
||||
| EvalStackValue.ObjectRef val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.ObjectRef
|
||||
| EvalStackValue.ManagedPointer val1, EvalStackValue.Int32 val2 -> failwith "" |> EvalStackValue.ManagedPointer
|
||||
| EvalStackValue.ManagedPointer val1, EvalStackValue.Int32 val2 ->
|
||||
match op.ManagedPtrInt32 state val1 val2 with
|
||||
| Choice1Of2 result -> EvalStackValue.ManagedPointer result
|
||||
| Choice2Of2 result -> EvalStackValue.NativeInt (NativeIntSource.Verbatim (int64<int32> result))
|
||||
| EvalStackValue.ObjectRef val1, EvalStackValue.Int32 val2 -> failwith "" |> EvalStackValue.ObjectRef
|
||||
| EvalStackValue.ManagedPointer val1, EvalStackValue.ManagedPointer val2 ->
|
||||
match op.ManagedPtrManagedPtr state val1 val2 with
|
||||
| Choice1Of2 result -> EvalStackValue.ManagedPointer result
|
||||
| Choice2Of2 result -> EvalStackValue.NativeInt (NativeIntSource.Verbatim (int64<nativeint> result))
|
||||
| val1, val2 -> failwith $"invalid %s{op.Name} operation: {val1} and {val2}"
|
||||
|
@@ -1,5 +1,8 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection.Metadata
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Corelib =
|
||||
|
||||
@@ -134,6 +137,21 @@ module Corelib =
|
||||
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "UIntPtr" then Some v else None)
|
||||
|> Seq.exactlyOne
|
||||
|
||||
let runtimeFieldInfoStubType =
|
||||
corelib.TypeDefs
|
||||
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "RuntimeFieldInfoStub" then Some v else None)
|
||||
|> Seq.exactlyOne
|
||||
|
||||
let runtimeFieldHandleInternalType =
|
||||
corelib.TypeDefs
|
||||
|> Seq.choose (fun (KeyValue (_, v)) ->
|
||||
if v.Name = "RuntimeFieldHandleInternal" then
|
||||
Some v
|
||||
else
|
||||
None
|
||||
)
|
||||
|> Seq.exactlyOne
|
||||
|
||||
{
|
||||
Corelib = corelib
|
||||
String = stringType
|
||||
@@ -157,9 +175,89 @@ module Corelib =
|
||||
RuntimeTypeHandle = runtimeTypeHandleType
|
||||
RuntimeMethodHandle = runtimeMethodHandleType
|
||||
RuntimeFieldHandle = runtimeFieldHandleType
|
||||
RuntimeFieldInfoStub = runtimeFieldInfoStubType
|
||||
RuntimeFieldHandleInternal = runtimeFieldHandleInternalType
|
||||
RuntimeType = runtimeTypeType
|
||||
Void = voidType
|
||||
TypedReference = typedReferenceType
|
||||
IntPtr = intPtrType
|
||||
UIntPtr = uintPtrType
|
||||
}
|
||||
|
||||
let concretizeAll
|
||||
(loaded : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(bct : BaseClassTypes<DumpedAssembly>)
|
||||
(t : AllConcreteTypes)
|
||||
: AllConcreteTypes
|
||||
=
|
||||
let ctx =
|
||||
{
|
||||
TypeConcretization.ConcretizationContext.InProgress = ImmutableDictionary.Empty
|
||||
TypeConcretization.ConcretizationContext.ConcreteTypes = t
|
||||
TypeConcretization.ConcretizationContext.LoadedAssemblies = loaded
|
||||
TypeConcretization.ConcretizationContext.BaseTypes = bct
|
||||
}
|
||||
|
||||
let loader =
|
||||
{ new IAssemblyLoad with
|
||||
member _.LoadAssembly _ _ _ =
|
||||
failwith "should have already loaded this assembly"
|
||||
}
|
||||
|
||||
let tys =
|
||||
[
|
||||
bct.String
|
||||
bct.Boolean
|
||||
bct.Char
|
||||
bct.SByte
|
||||
bct.Byte
|
||||
bct.Int16
|
||||
bct.UInt16
|
||||
bct.Int32
|
||||
bct.UInt32
|
||||
bct.Int64
|
||||
bct.UInt64
|
||||
bct.Single
|
||||
bct.Double
|
||||
bct.Array
|
||||
bct.Enum
|
||||
bct.ValueType
|
||||
bct.DelegateType
|
||||
bct.Object
|
||||
bct.RuntimeTypeHandle
|
||||
bct.RuntimeMethodHandle
|
||||
bct.RuntimeFieldHandle
|
||||
bct.RuntimeFieldInfoStub
|
||||
bct.RuntimeFieldHandleInternal
|
||||
bct.RuntimeType
|
||||
bct.Void
|
||||
bct.TypedReference
|
||||
bct.IntPtr
|
||||
bct.UIntPtr
|
||||
]
|
||||
|
||||
(ctx, tys)
|
||||
||> List.fold (fun ctx ty ->
|
||||
let stk =
|
||||
match DumpedAssembly.resolveBaseType ctx.BaseTypes ctx.LoadedAssemblies ty.Assembly ty.BaseType with
|
||||
| ResolvedBaseType.Enum
|
||||
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
||||
| ResolvedBaseType.Object
|
||||
| ResolvedBaseType.Delegate -> SignatureTypeKind.Class
|
||||
|
||||
let _handle, ctx =
|
||||
TypeConcretization.concretizeType
|
||||
ctx
|
||||
loader
|
||||
ty.Assembly
|
||||
ImmutableArray.Empty
|
||||
ImmutableArray.Empty
|
||||
(TypeDefn.FromDefinition (
|
||||
ComparableTypeDefinitionHandle.Make ty.TypeDefHandle,
|
||||
ty.Assembly.FullName,
|
||||
stk
|
||||
))
|
||||
|
||||
ctx
|
||||
)
|
||||
|> _.ConcreteTypes
|
||||
|
@@ -1,5 +1,7 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
#nowarn "42"
|
||||
|
||||
/// See I.12.3.2.1 for definition
|
||||
type EvalStackValue =
|
||||
| Int32 of int32
|
||||
@@ -8,10 +10,8 @@ type EvalStackValue =
|
||||
| Float of float
|
||||
| ManagedPointer of ManagedPointerSource
|
||||
| ObjectRef of ManagedHeapAddress
|
||||
// Fraser thinks this isn't really a thing in CoreCLR
|
||||
// | TransientPointer of TransientPointerSource
|
||||
/// Mapping of field name to value
|
||||
| UserDefinedValueType of (string * EvalStackValue) list
|
||||
/// This doesn't match what the CLR does in reality, but we can work out whatever we need from it.
|
||||
| UserDefinedValueType of CliValueType
|
||||
|
||||
override this.ToString () =
|
||||
match this with
|
||||
@@ -21,13 +21,7 @@ type EvalStackValue =
|
||||
| EvalStackValue.Float f -> $"Float(%f{f})"
|
||||
| EvalStackValue.ManagedPointer managedPointerSource -> $"Pointer(%O{managedPointerSource})"
|
||||
| EvalStackValue.ObjectRef managedHeapAddress -> $"ObjectRef(%O{managedHeapAddress})"
|
||||
| EvalStackValue.UserDefinedValueType evalStackValues ->
|
||||
let desc =
|
||||
evalStackValues
|
||||
|> List.map (snd >> string<EvalStackValue>)
|
||||
|> String.concat " | "
|
||||
|
||||
$"Struct(%s{desc})"
|
||||
| EvalStackValue.UserDefinedValueType evalStackValues -> $"Struct(%O{evalStackValues})"
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module EvalStackValue =
|
||||
@@ -55,6 +49,7 @@ module EvalStackValue =
|
||||
failwith "todo"
|
||||
| NativeIntSource.ManagedPointer _ -> failwith "TODO"
|
||||
| NativeIntSource.FunctionPointer _ -> failwith "TODO"
|
||||
| NativeIntSource.FieldHandlePtr _ -> failwith "TODO"
|
||||
| NativeIntSource.TypeHandlePtr _ -> failwith "TODO"
|
||||
| EvalStackValue.Float f -> failwith "todo"
|
||||
| EvalStackValue.ManagedPointer managedPointerSource ->
|
||||
@@ -83,7 +78,14 @@ module EvalStackValue =
|
||||
match value with
|
||||
| EvalStackValue.Int32 i -> Some (int64<int> i)
|
||||
| EvalStackValue.Int64 i -> Some i
|
||||
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
|
||||
| EvalStackValue.NativeInt src ->
|
||||
match src with
|
||||
| NativeIntSource.Verbatim int64 -> Some int64
|
||||
| NativeIntSource.ManagedPointer ManagedPointerSource.Null -> Some 0L
|
||||
| NativeIntSource.ManagedPointer _
|
||||
| NativeIntSource.FunctionPointer _
|
||||
| NativeIntSource.TypeHandlePtr _
|
||||
| NativeIntSource.FieldHandlePtr _ -> failwith "refusing to convert pointer to int64"
|
||||
| EvalStackValue.Float f -> failwith "todo"
|
||||
| EvalStackValue.ManagedPointer managedPointerSource -> failwith "todo"
|
||||
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
|
||||
@@ -100,6 +102,52 @@ module EvalStackValue =
|
||||
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
|
||||
| EvalStackValue.UserDefinedValueType evalStackValues -> failwith "todo"
|
||||
|
||||
/// Then truncates to int32.
|
||||
let convToUInt8 (value : EvalStackValue) : int32 option =
|
||||
match value with
|
||||
| EvalStackValue.Int32 (i : int32) ->
|
||||
let v = (# "conv.u1" i : uint8 #)
|
||||
Some (int32<uint8> v)
|
||||
| EvalStackValue.Int64 int64 ->
|
||||
let v = (# "conv.u1" int64 : uint8 #)
|
||||
Some (int32<uint8> v)
|
||||
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
|
||||
| EvalStackValue.Float f -> failwith "todo"
|
||||
| EvalStackValue.ManagedPointer managedPointerSource -> failwith "todo"
|
||||
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
|
||||
| EvalStackValue.UserDefinedValueType evalStackValues -> failwith "todo"
|
||||
|
||||
let rec ofCliType (v : CliType) : EvalStackValue =
|
||||
match v with
|
||||
| CliType.Numeric numeric ->
|
||||
match numeric with
|
||||
| CliNumericType.Int32 i -> EvalStackValue.Int32 i
|
||||
| CliNumericType.Int64 i -> EvalStackValue.Int64 i
|
||||
| CliNumericType.NativeInt i -> EvalStackValue.NativeInt i
|
||||
// Sign-extend types int8 and int16
|
||||
// Zero-extend unsigned int8/unsigned int16
|
||||
| CliNumericType.Int8 b -> int32<int8> b |> EvalStackValue.Int32
|
||||
| CliNumericType.UInt8 b -> int32<uint8> b |> EvalStackValue.Int32
|
||||
| CliNumericType.Int16 s -> int32<int16> s |> EvalStackValue.Int32
|
||||
| CliNumericType.UInt16 s -> int32<uint16> s |> EvalStackValue.Int32
|
||||
| CliNumericType.Float32 f -> EvalStackValue.Float (float<float32> f)
|
||||
| CliNumericType.Float64 f -> EvalStackValue.Float f
|
||||
| CliNumericType.NativeFloat f -> EvalStackValue.Float f
|
||||
| CliType.ObjectRef i ->
|
||||
match i with
|
||||
| None -> EvalStackValue.ManagedPointer ManagedPointerSource.Null
|
||||
| Some i -> EvalStackValue.ManagedPointer (ManagedPointerSource.Heap i)
|
||||
// Zero-extend bool/char
|
||||
| CliType.Bool b -> int32 b |> EvalStackValue.Int32
|
||||
| CliType.Char (high, low) -> int32 high * 256 + int32 low |> EvalStackValue.Int32
|
||||
| CliType.RuntimePointer ptr ->
|
||||
match ptr with
|
||||
| CliRuntimePointer.Verbatim ptrInt -> NativeIntSource.Verbatim ptrInt |> EvalStackValue.NativeInt
|
||||
| CliRuntimePointer.FieldRegistryHandle ptrInt ->
|
||||
NativeIntSource.FieldHandlePtr ptrInt |> EvalStackValue.NativeInt
|
||||
| CliRuntimePointer.Managed ptr -> ptr |> EvalStackValue.ManagedPointer
|
||||
| CliType.ValueType fields -> EvalStackValue.UserDefinedValueType fields
|
||||
|
||||
let rec toCliTypeCoerced (target : CliType) (popped : EvalStackValue) : CliType =
|
||||
match target with
|
||||
| CliType.Numeric numeric ->
|
||||
@@ -107,7 +155,13 @@ module EvalStackValue =
|
||||
| CliNumericType.Int32 _ ->
|
||||
match popped with
|
||||
| EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.Int32 i)
|
||||
| EvalStackValue.UserDefinedValueType [ popped ] -> toCliTypeCoerced target (snd popped)
|
||||
| EvalStackValue.UserDefinedValueType popped ->
|
||||
let popped = CliValueType.DereferenceFieldAt 0 4 popped
|
||||
// TODO: when we have a general mechanism to coerce CliTypes to each other,
|
||||
// do that
|
||||
match popped with
|
||||
| CliType.Numeric (CliNumericType.Int32 i) -> CliType.Numeric (CliNumericType.Int32 i)
|
||||
| _ -> failwith "TODO"
|
||||
| i -> failwith $"TODO: %O{i}"
|
||||
| CliNumericType.Int64 _ ->
|
||||
match popped with
|
||||
@@ -117,17 +171,34 @@ module EvalStackValue =
|
||||
| NativeIntSource.Verbatim i -> CliType.Numeric (CliNumericType.Int64 i)
|
||||
| NativeIntSource.ManagedPointer ptr -> failwith "TODO"
|
||||
| NativeIntSource.FunctionPointer f -> failwith $"TODO: {f}"
|
||||
// CliType.Numeric (CliNumericType.ProvenanceTrackedNativeInt64 f)
|
||||
| NativeIntSource.FieldHandlePtr f -> failwith $"TODO: {f}"
|
||||
| NativeIntSource.TypeHandlePtr f -> failwith $"TODO: {f}"
|
||||
// CliType.Numeric (CliNumericType.TypeHandlePtr f)
|
||||
| i -> failwith $"TODO: %O{i}"
|
||||
| CliNumericType.NativeInt _ ->
|
||||
match popped with
|
||||
| EvalStackValue.NativeInt s -> CliNumericType.NativeInt s
|
||||
| EvalStackValue.NativeInt s -> CliNumericType.NativeInt s |> CliType.Numeric
|
||||
| EvalStackValue.ManagedPointer ptrSrc ->
|
||||
CliNumericType.NativeInt (NativeIntSource.ManagedPointer ptrSrc)
|
||||
|> CliType.Numeric
|
||||
| EvalStackValue.UserDefinedValueType vt ->
|
||||
let popped = CliValueType.DereferenceFieldAt 0 NATIVE_INT_SIZE vt
|
||||
// TODO: when we have a general mechanism to coerce CliTypes to each other,
|
||||
// do that
|
||||
match popped with
|
||||
| CliType.Numeric (CliNumericType.NativeInt i) -> CliType.Numeric (CliNumericType.NativeInt i)
|
||||
| CliType.Numeric (CliNumericType.Int64 i) ->
|
||||
CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.Verbatim i))
|
||||
| CliType.RuntimePointer ptr ->
|
||||
match ptr with
|
||||
| CliRuntimePointer.Verbatim i ->
|
||||
CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.Verbatim i))
|
||||
| CliRuntimePointer.FieldRegistryHandle ptr ->
|
||||
CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FieldHandlePtr ptr))
|
||||
| CliRuntimePointer.Managed src ->
|
||||
CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.ManagedPointer src))
|
||||
| _ -> failwith $"TODO: {popped}"
|
||||
| _ -> failwith $"TODO: {popped}"
|
||||
|> CliType.Numeric
|
||||
| CliNumericType.NativeFloat f -> failwith "todo"
|
||||
| CliNumericType.Int8 _ ->
|
||||
match popped with
|
||||
@@ -156,34 +227,29 @@ module EvalStackValue =
|
||||
| CliType.ObjectRef _ ->
|
||||
match popped with
|
||||
| EvalStackValue.ManagedPointer ptrSource ->
|
||||
match ptrSource with
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
||||
CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, whichVar)
|
||||
|> CliRuntimePointer.Managed
|
||||
|> CliType.RuntimePointer
|
||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
|
||||
CliRuntimePointerSource.Argument (sourceThread, methodFrame, whichVar)
|
||||
|> CliRuntimePointer.Managed
|
||||
|> CliType.RuntimePointer
|
||||
| ManagedPointerSource.Heap managedHeapAddress -> CliType.ObjectRef (Some managedHeapAddress)
|
||||
| ManagedPointerSource.Null -> CliType.ObjectRef None
|
||||
| ManagedPointerSource.ArrayIndex (arr, ind) ->
|
||||
CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.ArrayIndex (arr, ind)))
|
||||
ptrSource |> CliRuntimePointer.Managed |> CliType.RuntimePointer
|
||||
| EvalStackValue.ObjectRef ptr ->
|
||||
ManagedPointerSource.Heap ptr
|
||||
|> CliRuntimePointer.Managed
|
||||
|> CliType.RuntimePointer
|
||||
| EvalStackValue.NativeInt nativeIntSource ->
|
||||
match nativeIntSource with
|
||||
| NativeIntSource.Verbatim 0L -> CliType.ObjectRef None
|
||||
| NativeIntSource.Verbatim i -> failwith $"refusing to interpret verbatim native int {i} as a pointer"
|
||||
| NativeIntSource.FunctionPointer _ -> failwith "TODO"
|
||||
| NativeIntSource.TypeHandlePtr _ -> failwith "refusing to interpret type handle ID as an object ref"
|
||||
| NativeIntSource.FieldHandlePtr _ -> failwith "refusing to interpret field handle ID as an object ref"
|
||||
| NativeIntSource.ManagedPointer ptr ->
|
||||
match ptr with
|
||||
| ManagedPointerSource.Null -> CliType.ObjectRef None
|
||||
| ManagedPointerSource.Heap s -> CliType.ObjectRef (Some s)
|
||||
| _ -> failwith "TODO"
|
||||
| EvalStackValue.UserDefinedValueType fields ->
|
||||
match fields with
|
||||
| [ esv ] -> toCliTypeCoerced target (snd esv)
|
||||
| fields -> failwith $"TODO: don't know how to coerce struct of {fields} to a pointer"
|
||||
| EvalStackValue.UserDefinedValueType obj ->
|
||||
let popped = CliValueType.DereferenceFieldAt 0 NATIVE_INT_SIZE obj
|
||||
|
||||
match popped with
|
||||
| CliType.ObjectRef r -> CliType.ObjectRef r
|
||||
| _ -> failwith "TODO"
|
||||
| _ -> failwith $"TODO: {popped}"
|
||||
| CliType.Bool _ ->
|
||||
match popped with
|
||||
@@ -195,41 +261,19 @@ module EvalStackValue =
|
||||
| i -> failwith $"TODO: %O{i}"
|
||||
| CliType.RuntimePointer _ ->
|
||||
match popped with
|
||||
| EvalStackValue.ManagedPointer src ->
|
||||
match src with
|
||||
| ManagedPointerSource.Heap addr -> CliType.OfManagedObject addr
|
||||
| ManagedPointerSource.Null -> CliType.ObjectRef None
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, var) ->
|
||||
CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, var)
|
||||
|> CliRuntimePointer.Managed
|
||||
|> CliType.RuntimePointer
|
||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, var) ->
|
||||
CliRuntimePointerSource.Argument (sourceThread, methodFrame, var)
|
||||
|> CliRuntimePointer.Managed
|
||||
|> CliType.RuntimePointer
|
||||
| ManagedPointerSource.ArrayIndex (arr, index) ->
|
||||
CliRuntimePointerSource.ArrayIndex (arr, index)
|
||||
|> CliRuntimePointer.Managed
|
||||
|> CliType.RuntimePointer
|
||||
| EvalStackValue.ManagedPointer src -> src |> CliRuntimePointer.Managed |> CliType.RuntimePointer
|
||||
| EvalStackValue.NativeInt intSrc ->
|
||||
match intSrc with
|
||||
| NativeIntSource.Verbatim i -> CliType.RuntimePointer (CliRuntimePointer.Unmanaged i)
|
||||
| NativeIntSource.ManagedPointer src ->
|
||||
match src with
|
||||
| ManagedPointerSource.Heap src ->
|
||||
CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Heap src))
|
||||
| ManagedPointerSource.Null ->
|
||||
CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
|
||||
| ManagedPointerSource.LocalVariable (a, b, c) ->
|
||||
CliType.RuntimePointer (
|
||||
CliRuntimePointer.Managed (CliRuntimePointerSource.LocalVariable (a, b, c))
|
||||
)
|
||||
| ManagedPointerSource.Argument (a, b, c) ->
|
||||
CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Argument (a, b, c)))
|
||||
| ManagedPointerSource.ArrayIndex _ -> failwith "TODO"
|
||||
| NativeIntSource.Verbatim i -> CliType.RuntimePointer (CliRuntimePointer.Verbatim i)
|
||||
| NativeIntSource.ManagedPointer src -> src |> CliRuntimePointer.Managed |> CliType.RuntimePointer
|
||||
| NativeIntSource.FunctionPointer methodInfo ->
|
||||
CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer methodInfo))
|
||||
| NativeIntSource.TypeHandlePtr int64 -> failwith "todo"
|
||||
| NativeIntSource.FieldHandlePtr int64 -> failwith "todo"
|
||||
| EvalStackValue.ObjectRef addr ->
|
||||
ManagedPointerSource.Heap addr
|
||||
|> CliRuntimePointer.Managed
|
||||
|> CliType.RuntimePointer
|
||||
| _ -> failwith $"TODO: %O{popped}"
|
||||
| CliType.Char _ ->
|
||||
match popped with
|
||||
@@ -238,71 +282,40 @@ module EvalStackValue =
|
||||
let low = i % 256
|
||||
CliType.Char (byte<int> high, byte<int> low)
|
||||
| popped -> failwith $"Unexpectedly wanted a char from {popped}"
|
||||
| CliType.ValueType fields ->
|
||||
| CliType.ValueType vt ->
|
||||
match popped with
|
||||
| EvalStackValue.UserDefinedValueType popped ->
|
||||
if fields.Length <> popped.Length then
|
||||
failwith
|
||||
$"mismatch: popped value type {popped} (length %i{popped.Length}) into {fields} (length %i{fields.Length})"
|
||||
| EvalStackValue.UserDefinedValueType popped' ->
|
||||
match CliValueType.TrySequentialFields vt, CliValueType.TrySequentialFields popped' with
|
||||
| Some vt, Some popped ->
|
||||
if vt.Length <> popped.Length then
|
||||
failwith
|
||||
$"mismatch: popped value type {popped} (length %i{popped.Length}) into {vt} (length %i{vt.Length})"
|
||||
|
||||
List.map2
|
||||
(fun (name1, v1) (name2, v2) ->
|
||||
if name1 <> name2 then
|
||||
failwith $"TODO: name mismatch, {name1} vs {name2}"
|
||||
(vt, popped)
|
||||
||> List.map2 (fun field1 popped ->
|
||||
if field1.Name <> popped.Name then
|
||||
failwith $"TODO: name mismatch, {field1.Name} vs {popped.Name}"
|
||||
|
||||
name1, toCliTypeCoerced v1 v2
|
||||
if field1.Offset <> popped.Offset then
|
||||
failwith $"TODO: offset mismatch for {field1.Name}, {field1.Offset} vs {popped.Offset}"
|
||||
|
||||
let contents = toCliTypeCoerced field1.Contents (ofCliType popped.Contents)
|
||||
|
||||
{
|
||||
CliField.Name = field1.Name
|
||||
Contents = contents
|
||||
Offset = field1.Offset
|
||||
Type = field1.Type
|
||||
}
|
||||
)
|
||||
fields
|
||||
popped
|
||||
|> CliType.ValueType
|
||||
|> CliValueType.OfFields popped'.Layout
|
||||
|> CliType.ValueType
|
||||
| _, _ -> failwith "TODO: overlapping fields going onto eval stack"
|
||||
| popped ->
|
||||
match fields with
|
||||
| [ _, target ] -> toCliTypeCoerced target popped
|
||||
match CliValueType.TryExactlyOneField vt with
|
||||
| Some field -> toCliTypeCoerced field.Contents popped
|
||||
| _ -> failwith $"TODO: {popped} into value type {target}"
|
||||
|
||||
let rec ofCliType (v : CliType) : EvalStackValue =
|
||||
match v with
|
||||
| CliType.Numeric numeric ->
|
||||
match numeric with
|
||||
| CliNumericType.Int32 i -> EvalStackValue.Int32 i
|
||||
| CliNumericType.Int64 i -> EvalStackValue.Int64 i
|
||||
| CliNumericType.NativeInt i -> EvalStackValue.NativeInt i
|
||||
// Sign-extend types int8 and int16
|
||||
// Zero-extend unsigned int8/unsigned int16
|
||||
| CliNumericType.Int8 b -> int32<int8> b |> EvalStackValue.Int32
|
||||
| CliNumericType.UInt8 b -> int32<uint8> b |> EvalStackValue.Int32
|
||||
| CliNumericType.Int16 s -> int32<int16> s |> EvalStackValue.Int32
|
||||
| CliNumericType.UInt16 s -> int32<uint16> s |> EvalStackValue.Int32
|
||||
| CliNumericType.Float32 f -> EvalStackValue.Float (float<float32> f)
|
||||
| CliNumericType.Float64 f -> EvalStackValue.Float f
|
||||
| CliNumericType.NativeFloat f -> EvalStackValue.Float f
|
||||
| CliType.ObjectRef i ->
|
||||
match i with
|
||||
| None -> EvalStackValue.ManagedPointer ManagedPointerSource.Null
|
||||
| Some i -> EvalStackValue.ManagedPointer (ManagedPointerSource.Heap i)
|
||||
// Zero-extend bool/char
|
||||
| CliType.Bool b -> int32 b |> EvalStackValue.Int32
|
||||
| CliType.Char (high, low) -> int32 high * 256 + int32 low |> EvalStackValue.Int32
|
||||
| CliType.RuntimePointer ptr ->
|
||||
match ptr with
|
||||
| CliRuntimePointer.Unmanaged ptrInt -> NativeIntSource.Verbatim ptrInt |> EvalStackValue.NativeInt
|
||||
| CliRuntimePointer.Managed ptr ->
|
||||
match ptr with
|
||||
| CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, var) ->
|
||||
ManagedPointerSource.LocalVariable (sourceThread, methodFrame, var)
|
||||
|> EvalStackValue.ManagedPointer
|
||||
| CliRuntimePointerSource.ArrayIndex (arr, ind) ->
|
||||
ManagedPointerSource.ArrayIndex (arr, ind) |> EvalStackValue.ManagedPointer
|
||||
| CliRuntimePointerSource.Argument (sourceThread, methodFrame, var) ->
|
||||
ManagedPointerSource.Argument (sourceThread, methodFrame, var)
|
||||
|> EvalStackValue.ManagedPointer
|
||||
| CliRuntimePointerSource.Heap addr -> EvalStackValue.ObjectRef addr
|
||||
| CliRuntimePointerSource.Null -> EvalStackValue.ManagedPointer ManagedPointerSource.Null
|
||||
| CliType.ValueType fields ->
|
||||
fields
|
||||
|> List.map (fun (name, f) -> name, ofCliType f)
|
||||
|> EvalStackValue.UserDefinedValueType
|
||||
|
||||
type EvalStack =
|
||||
{
|
||||
Values : EvalStackValue list
|
||||
@@ -335,3 +348,5 @@ type EvalStack =
|
||||
let v = EvalStackValue.ofCliType v
|
||||
|
||||
EvalStack.Push' v stack
|
||||
|
||||
static member PeekNthFromTop (n : int) (stack : EvalStack) : EvalStackValue option = stack.Values |> List.tryItem n
|
||||
|
@@ -120,9 +120,17 @@ module EvalStackValueComparisons =
|
||||
failwith "TODO"
|
||||
| other1, other2 -> failwith $"Cgt.un instruction invalid for comparing {other1} vs {other2}"
|
||||
|
||||
let ceq (var1 : EvalStackValue) (var2 : EvalStackValue) : bool =
|
||||
let rec ceq (var1 : EvalStackValue) (var2 : EvalStackValue) : bool =
|
||||
// Table III.4
|
||||
match var1, var2 with
|
||||
| EvalStackValue.UserDefinedValueType var1, v ->
|
||||
match CliValueType.TryExactlyOneField var1 with
|
||||
| None -> failwith "TODO"
|
||||
| Some var1 -> ceq (EvalStackValue.ofCliType var1.Contents) v
|
||||
| u, EvalStackValue.UserDefinedValueType var2 ->
|
||||
match CliValueType.TryExactlyOneField var2 with
|
||||
| None -> failwith "TODO"
|
||||
| Some var2 -> ceq u (EvalStackValue.ofCliType var2.Contents)
|
||||
| EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> var1 = var2
|
||||
| EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 -> failwith "TODO: int32 CEQ nativeint"
|
||||
| EvalStackValue.Int32 _, _ -> failwith $"bad ceq: Int32 vs {var2}"
|
||||
@@ -146,9 +154,18 @@ module EvalStackValueComparisons =
|
||||
failwith $"TODO (CEQ): nativeint vs managed pointer"
|
||||
| EvalStackValue.NativeInt _, _ -> failwith $"bad ceq: NativeInt vs {var2}"
|
||||
| EvalStackValue.ObjectRef var1, EvalStackValue.ObjectRef var2 -> var1 = var2
|
||||
| EvalStackValue.ManagedPointer src, EvalStackValue.ObjectRef var1
|
||||
| EvalStackValue.ObjectRef var1, EvalStackValue.ManagedPointer src ->
|
||||
match src with
|
||||
| ManagedPointerSource.Heap src -> src = var1
|
||||
| ManagedPointerSource.Null -> false
|
||||
| ManagedPointerSource.Field _
|
||||
| ManagedPointerSource.LocalVariable _
|
||||
| ManagedPointerSource.Argument _ -> false
|
||||
| ManagedPointerSource.ArrayIndex (arr, index) -> failwith "todo"
|
||||
| ManagedPointerSource.InterpretedAsType (src, ty) -> failwith "todo"
|
||||
| EvalStackValue.ObjectRef _, _ -> failwith $"bad ceq: ObjectRef vs {var2}"
|
||||
| EvalStackValue.ManagedPointer var1, EvalStackValue.ManagedPointer var2 -> var1 = var2
|
||||
| EvalStackValue.ManagedPointer var1, EvalStackValue.NativeInt var2 ->
|
||||
failwith $"TODO (CEQ): managed pointer vs nativeint"
|
||||
| EvalStackValue.ManagedPointer _, _ -> failwith $"bad ceq: ManagedPointer vs {var2}"
|
||||
| EvalStackValue.UserDefinedValueType _, _ -> failwith $"bad ceq: {var1} vs {var2}"
|
||||
|
@@ -34,7 +34,7 @@ module ExceptionHandling =
|
||||
|
||||
/// Check if an exception type matches a catch handler type
|
||||
let private isExceptionAssignableTo
|
||||
(exceptionTypeCrate : TypeInfoCrate)
|
||||
(exceptionType : ConcreteTypeHandle)
|
||||
(catchTypeToken : MetadataToken)
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
: bool
|
||||
@@ -46,7 +46,7 @@ module ExceptionHandling =
|
||||
/// Also returns `isFinally : bool`: whether this is a `finally` block (as opposed to e.g. a `catch`).
|
||||
let findExceptionHandler
|
||||
(currentPC : int)
|
||||
(exceptionTypeCrate : TypeInfoCrate)
|
||||
(exceptionType : ConcreteTypeHandle)
|
||||
(method : WoofWare.PawPrint.MethodInfo<'typeGen, 'methodGeneric, 'methodVar>)
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
: (WoofWare.PawPrint.ExceptionRegion * bool) option // handler, isFinally
|
||||
@@ -62,7 +62,7 @@ module ExceptionHandling =
|
||||
| ExceptionRegion.Catch (typeToken, offset) ->
|
||||
if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then
|
||||
// Check if exception type matches
|
||||
if isExceptionAssignableTo exceptionTypeCrate typeToken assemblies then
|
||||
if isExceptionAssignableTo exceptionType typeToken assemblies then
|
||||
Some (region, false)
|
||||
else
|
||||
None
|
||||
|
@@ -62,6 +62,7 @@ module System_Threading_Monitor =
|
||||
match lockObj with
|
||||
| EvalStackValue.ManagedPointer ManagedPointerSource.Null ->
|
||||
failwith "TODO: throw ArgumentNullException"
|
||||
| EvalStackValue.ObjectRef addr
|
||||
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) ->
|
||||
match IlMachineState.getSyncBlock addr state with
|
||||
| SyncBlock.Free ->
|
||||
@@ -82,11 +83,13 @@ module System_Threading_Monitor =
|
||||
| ManagedPointerSource.Null -> failwith "logic error"
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
||||
state
|
||||
|> IlMachineState.setLocalVariable sourceThread methodFrame whichVar (CliType.OfBool true)
|
||||
|> IlMachineState.setLocalVariable sourceThread methodFrame whichVar (CliType.ofBool true)
|
||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
|
||||
failwith "not really expecting to *edit* an argument..."
|
||||
| ManagedPointerSource.Heap addr -> failwith "todo: managed heap"
|
||||
| ManagedPointerSource.ArrayIndex _ -> failwith "todo: array index"
|
||||
| ManagedPointerSource.Field (managedPointerSource, fieldName) -> failwith "todo"
|
||||
| ManagedPointerSource.InterpretedAsType _ -> failwith "TODO"
|
||||
|
||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||
|
||||
@@ -100,6 +103,7 @@ module System_Threading_Monitor =
|
||||
match lockObj with
|
||||
| EvalStackValue.ManagedPointer ManagedPointerSource.Null ->
|
||||
failwith "TODO: throw ArgumentNullException"
|
||||
| EvalStackValue.ObjectRef addr
|
||||
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) ->
|
||||
match IlMachineState.getSyncBlock addr state with
|
||||
| SyncBlock.Free -> failwith "TODO: throw SynchronizationLockException"
|
||||
|
196
WoofWare.PawPrint/FieldHandleRegistry.fs
Normal file
196
WoofWare.PawPrint/FieldHandleRegistry.fs
Normal file
@@ -0,0 +1,196 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection
|
||||
open System.Reflection.Metadata
|
||||
|
||||
type FieldHandle =
|
||||
private
|
||||
{
|
||||
AssemblyFullName : string
|
||||
DeclaringType : ConcreteTypeHandle
|
||||
FieldHandle : ComparableFieldDefinitionHandle
|
||||
}
|
||||
|
||||
type FieldHandleRegistry =
|
||||
private
|
||||
{
|
||||
FieldHandleToId : Map<FieldHandle, int64>
|
||||
FieldHandleToField : Map<ManagedHeapAddress, FieldHandle>
|
||||
FieldToHandle : Map<FieldHandle, ManagedHeapAddress>
|
||||
NextHandle : int64
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module FieldHandleRegistry =
|
||||
let empty () =
|
||||
{
|
||||
FieldHandleToField = Map.empty
|
||||
FieldToHandle = Map.empty
|
||||
FieldHandleToId = Map.empty
|
||||
NextHandle = 1L
|
||||
}
|
||||
|
||||
/// Returns a (struct) System.RuntimeFieldHandle, with its contents (reference type) freshly allocated if necessary.
|
||||
let getOrAllocate
|
||||
(baseClassTypes : BaseClassTypes<'corelib>)
|
||||
(allConcreteTypes : AllConcreteTypes)
|
||||
(allocState : 'allocState)
|
||||
(allocate : CliValueType -> 'allocState -> ManagedHeapAddress * 'allocState)
|
||||
(declaringAssy : AssemblyName)
|
||||
(declaringType : ConcreteTypeHandle)
|
||||
(handle : FieldDefinitionHandle)
|
||||
(reg : FieldHandleRegistry)
|
||||
: CliType * FieldHandleRegistry * 'allocState
|
||||
=
|
||||
|
||||
let runtimeFieldHandle (runtimeFieldInfoStub : ManagedHeapAddress) =
|
||||
// RuntimeFieldHandle is a struct; it contains one field, an IRuntimeFieldInfo
|
||||
// https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1048
|
||||
// In practice we expect to use RuntimeFieldInfoStub for that IRuntimeFieldInfo:
|
||||
// https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1157
|
||||
let runtimeFieldHandleType = baseClassTypes.RuntimeFieldHandle
|
||||
let field = runtimeFieldHandleType.Fields |> List.exactlyOne
|
||||
|
||||
if field.Name <> "m_ptr" then
|
||||
failwith $"unexpected field name %s{field.Name} for BCL type RuntimeFieldHandle"
|
||||
|
||||
{
|
||||
Name = "m_ptr"
|
||||
Contents = CliType.ofManagedObject runtimeFieldInfoStub
|
||||
Offset = None
|
||||
Type =
|
||||
AllConcreteTypes.findExistingConcreteType
|
||||
allConcreteTypes
|
||||
(baseClassTypes.RuntimeFieldInfoStub.Assembly,
|
||||
baseClassTypes.RuntimeFieldInfoStub.Namespace,
|
||||
baseClassTypes.RuntimeFieldInfoStub.Name,
|
||||
ImmutableArray.Empty)
|
||||
|> Option.get
|
||||
}
|
||||
|> List.singleton
|
||||
|> CliValueType.OfFields Layout.Default
|
||||
|> CliType.ValueType
|
||||
|
||||
let handle =
|
||||
{
|
||||
AssemblyFullName = declaringAssy.FullName
|
||||
FieldHandle = ComparableFieldDefinitionHandle.Make handle
|
||||
DeclaringType = declaringType
|
||||
}
|
||||
|
||||
match Map.tryFind handle reg.FieldToHandle with
|
||||
| Some v -> runtimeFieldHandle v, reg, allocState
|
||||
| None ->
|
||||
|
||||
let newHandle = reg.NextHandle
|
||||
|
||||
let runtimeFieldHandleInternal =
|
||||
let field = baseClassTypes.RuntimeFieldHandleInternal.Fields |> List.exactlyOne
|
||||
|
||||
if field.Name <> "m_handle" then
|
||||
failwith $"unexpected field name %s{field.Name} for BCL type RuntimeFieldHandleInternal"
|
||||
|
||||
match field.Signature with
|
||||
| TypeDefn.PrimitiveType PrimitiveType.IntPtr -> ()
|
||||
| s -> failwith $"bad sig: {s}"
|
||||
|
||||
// https://github.com/dotnet/runtime/blob/2b21c73fa2c32fa0195e4a411a435dda185efd08/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1380
|
||||
{
|
||||
Name = "m_handle"
|
||||
Contents = CliType.RuntimePointer (CliRuntimePointer.FieldRegistryHandle newHandle)
|
||||
Offset = None // no struct layout was specified
|
||||
Type =
|
||||
AllConcreteTypes.findExistingConcreteType
|
||||
allConcreteTypes
|
||||
(baseClassTypes.IntPtr.Assembly,
|
||||
baseClassTypes.IntPtr.Namespace,
|
||||
baseClassTypes.IntPtr.Name,
|
||||
ImmutableArray.Empty)
|
||||
|> Option.get
|
||||
}
|
||||
|> List.singleton
|
||||
|> CliValueType.OfFields Layout.Default
|
||||
|> CliType.ValueType
|
||||
|
||||
// https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1074
|
||||
let runtimeFieldInfoStub =
|
||||
let objType =
|
||||
AllConcreteTypes.findExistingConcreteType
|
||||
allConcreteTypes
|
||||
(baseClassTypes.Object.Assembly,
|
||||
baseClassTypes.Object.Namespace,
|
||||
baseClassTypes.Object.Name,
|
||||
ImmutableArray.Empty)
|
||||
|> Option.get
|
||||
|
||||
let intType =
|
||||
AllConcreteTypes.findExistingConcreteType
|
||||
allConcreteTypes
|
||||
(baseClassTypes.Int32.Assembly,
|
||||
baseClassTypes.Int32.Namespace,
|
||||
baseClassTypes.Int32.Name,
|
||||
ImmutableArray.Empty)
|
||||
|> Option.get
|
||||
|
||||
// LayoutKind.Sequential
|
||||
[
|
||||
// If we ever implement a GC, something should change here
|
||||
{
|
||||
Name = "m_keepalive"
|
||||
Contents = CliType.ObjectRef None
|
||||
Offset = None
|
||||
Type = objType
|
||||
}
|
||||
{
|
||||
Name = "m_c"
|
||||
Contents = CliType.ObjectRef None
|
||||
Offset = None
|
||||
Type = objType
|
||||
}
|
||||
{
|
||||
Name = "m_d"
|
||||
Contents = CliType.ObjectRef None
|
||||
Offset = None
|
||||
Type = objType
|
||||
}
|
||||
{
|
||||
Name = "m_b"
|
||||
Contents = CliType.Numeric (CliNumericType.Int32 0)
|
||||
Offset = None
|
||||
Type = intType
|
||||
}
|
||||
{
|
||||
Name = "m_e"
|
||||
Contents = CliType.ObjectRef None
|
||||
Offset = None
|
||||
Type = objType
|
||||
}
|
||||
// RuntimeFieldHandleInternal: https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1048
|
||||
{
|
||||
Name = "m_fieldHandle"
|
||||
Contents = runtimeFieldHandleInternal
|
||||
Offset = None
|
||||
Type =
|
||||
AllConcreteTypes.findExistingConcreteType
|
||||
allConcreteTypes
|
||||
(baseClassTypes.RuntimeFieldHandleInternal.Assembly,
|
||||
baseClassTypes.RuntimeFieldHandleInternal.Namespace,
|
||||
baseClassTypes.RuntimeFieldHandleInternal.Name,
|
||||
ImmutableArray.Empty)
|
||||
|> Option.get
|
||||
}
|
||||
]
|
||||
|> CliValueType.OfFields Layout.Default // explicitly sequential but no custom packing size
|
||||
|
||||
let alloc, state = allocate runtimeFieldInfoStub allocState
|
||||
|
||||
let reg =
|
||||
{
|
||||
FieldHandleToField = reg.FieldHandleToField |> Map.add alloc handle
|
||||
FieldToHandle = reg.FieldToHandle |> Map.add handle alloc
|
||||
FieldHandleToId = reg.FieldHandleToId |> Map.add handle newHandle
|
||||
NextHandle = reg.NextHandle + 1L
|
||||
}
|
||||
|
||||
runtimeFieldHandle alloc, reg, state
|
File diff suppressed because it is too large
Load Diff
811
WoofWare.PawPrint/IlMachineStateExecution.fs
Normal file
811
WoofWare.PawPrint/IlMachineStateExecution.fs
Normal file
@@ -0,0 +1,811 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection
|
||||
open System.Reflection.Metadata
|
||||
open System.Runtime.CompilerServices
|
||||
open Microsoft.Extensions.Logging
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module IlMachineStateExecution =
|
||||
let getTypeOfObj
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||
(state : IlMachineState)
|
||||
(esv : EvalStackValue)
|
||||
: IlMachineState * ConcreteTypeHandle
|
||||
=
|
||||
match esv with
|
||||
| EvalStackValue.Int32 _ ->
|
||||
DumpedAssembly.typeInfoToTypeDefn' baseClassTypes state._LoadedAssemblies baseClassTypes.Int32
|
||||
|> IlMachineState.concretizeType
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
state
|
||||
baseClassTypes.Corelib.Name
|
||||
ImmutableArray.Empty
|
||||
ImmutableArray.Empty
|
||||
| EvalStackValue.Int64 _ ->
|
||||
DumpedAssembly.typeInfoToTypeDefn' baseClassTypes state._LoadedAssemblies baseClassTypes.Int64
|
||||
|> IlMachineState.concretizeType
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
state
|
||||
baseClassTypes.Corelib.Name
|
||||
ImmutableArray.Empty
|
||||
ImmutableArray.Empty
|
||||
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
|
||||
| EvalStackValue.Float _ ->
|
||||
DumpedAssembly.typeInfoToTypeDefn' baseClassTypes state._LoadedAssemblies baseClassTypes.Double
|
||||
|> IlMachineState.concretizeType
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
state
|
||||
baseClassTypes.Corelib.Name
|
||||
ImmutableArray.Empty
|
||||
ImmutableArray.Empty
|
||||
| EvalStackValue.ManagedPointer src ->
|
||||
match src with
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> failwith "todo"
|
||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> failwith "todo"
|
||||
| ManagedPointerSource.Heap addr ->
|
||||
let o = ManagedHeap.get addr state.ManagedHeap
|
||||
state, o.ConcreteType
|
||||
| ManagedPointerSource.ArrayIndex (arr, index) -> failwith "todo"
|
||||
| ManagedPointerSource.Null -> failwith "todo"
|
||||
| ManagedPointerSource.Field (managedPointerSource, fieldName) -> failwith "todo"
|
||||
| ManagedPointerSource.InterpretedAsType (src, ty) -> failwith "todo"
|
||||
| EvalStackValue.ObjectRef addr ->
|
||||
let o = ManagedHeap.get addr state.ManagedHeap
|
||||
state, o.ConcreteType
|
||||
| EvalStackValue.UserDefinedValueType tuples -> failwith "todo"
|
||||
|
||||
let isAssignableFrom
|
||||
(objToCast : ConcreteTypeHandle)
|
||||
(possibleTargetType : ConcreteTypeHandle)
|
||||
(state : IlMachineState)
|
||||
: bool
|
||||
=
|
||||
if objToCast = possibleTargetType then
|
||||
true
|
||||
else
|
||||
|
||||
let objToCast' = AllConcreteTypes.lookup objToCast state.ConcreteTypes |> Option.get
|
||||
|
||||
let possibleTargetType' =
|
||||
AllConcreteTypes.lookup possibleTargetType state.ConcreteTypes |> Option.get
|
||||
|
||||
// TODO: null can be assigned to any reference type; might not be relevant here?
|
||||
|
||||
match possibleTargetType with
|
||||
| ConcreteObj state.ConcreteTypes -> true
|
||||
| ConcreteValueType state.ConcreteTypes when failwith "check if objToCast inherits ValueType" -> true
|
||||
| _ ->
|
||||
// Claude describes the algorithm here:
|
||||
// https://claude.ai/chat/f15e23f6-a27b-4655-9e69-e4d445dd1249
|
||||
failwith
|
||||
$"TODO: check inheritance chain and interfaces: is {objToCast'} assignable from {possibleTargetType'}?"
|
||||
|
||||
let callMethod
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||
(wasInitialising : ConcreteTypeHandle option)
|
||||
(wasConstructing : ManagedHeapAddress option)
|
||||
(performInterfaceResolution : bool)
|
||||
(wasClassConstructor : bool)
|
||||
(advanceProgramCounterOfCaller : bool)
|
||||
(methodGenerics : ImmutableArray<ConcreteTypeHandle>)
|
||||
(methodToCall : WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>)
|
||||
(thread : ThreadId)
|
||||
(threadState : ThreadState)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState
|
||||
=
|
||||
let logger = loggerFactory.CreateLogger "CallMethod"
|
||||
let activeAssy = state.ActiveAssembly thread
|
||||
|
||||
// Check for intrinsics first
|
||||
let isIntrinsic =
|
||||
MethodInfo.isJITIntrinsic
|
||||
(fun handle ->
|
||||
match activeAssy.Members.[handle].Parent with
|
||||
| MetadataToken.TypeReference r -> activeAssy.TypeRefs.[r]
|
||||
| x -> failwith $"{x}"
|
||||
)
|
||||
activeAssy.Methods
|
||||
methodToCall
|
||||
|
||||
match
|
||||
if isIntrinsic then
|
||||
Intrinsics.call loggerFactory baseClassTypes methodToCall thread state
|
||||
else
|
||||
None
|
||||
with
|
||||
| Some result -> result
|
||||
| None ->
|
||||
|
||||
// Get zero values for all parameters
|
||||
let state, argZeroObjects =
|
||||
((state, []), methodToCall.Signature.ParameterTypes)
|
||||
||> List.fold (fun (state, zeros) tyHandle ->
|
||||
let zero, state = IlMachineState.cliTypeZeroOfHandle state baseClassTypes tyHandle
|
||||
state, zero :: zeros
|
||||
)
|
||||
|
||||
let argZeroObjects = List.rev argZeroObjects
|
||||
|
||||
let activeMethodState = threadState.MethodStates.[threadState.ActiveMethodState]
|
||||
|
||||
let state, methodToCall =
|
||||
match methodToCall.Instructions, performInterfaceResolution, methodToCall.IsStatic with
|
||||
| None, true, false ->
|
||||
logger.LogDebug (
|
||||
"Identifying target of virtual call for {TypeName}.{MethodName}",
|
||||
methodToCall.DeclaringType.Name,
|
||||
methodToCall.Name
|
||||
)
|
||||
// This might be an interface implementation, or implemented by native code.
|
||||
// If native code, we'll deal with that when we actually start implementing.
|
||||
|
||||
// Since we're not static, there's a `this` on the eval stack.
|
||||
// It comes *below* all the arguments.
|
||||
let callingObj =
|
||||
match
|
||||
activeMethodState.EvaluationStack
|
||||
|> EvalStack.PeekNthFromTop methodToCall.Parameters.Length
|
||||
with
|
||||
| None -> failwith "unexpectedly no `this` on the eval stack of instance method"
|
||||
| Some this -> this
|
||||
|
||||
let state, callingObjTyHandle =
|
||||
getTypeOfObj loggerFactory baseClassTypes state callingObj
|
||||
|
||||
let callingObjTy =
|
||||
let ty =
|
||||
AllConcreteTypes.lookup callingObjTyHandle state.ConcreteTypes |> Option.get
|
||||
|
||||
state.LoadedAssembly(ty.Assembly).Value.TypeDefs.[ty.Definition.Get]
|
||||
|
||||
let declaringAssy = state.LoadedAssembly(methodToCall.DeclaringType.Assembly).Value
|
||||
|
||||
let methodDeclaringType =
|
||||
declaringAssy.TypeDefs.[methodToCall.DeclaringType.Definition.Get]
|
||||
|
||||
let interfaceExplicitNamedMethod =
|
||||
if methodDeclaringType.IsInterface then
|
||||
Some
|
||||
$"{TypeInfo.fullName (fun h -> declaringAssy.TypeDefs.[h]) methodDeclaringType}.{methodToCall.Name}"
|
||||
else
|
||||
None
|
||||
|
||||
// Does type `callingObjTy` implement this method? If so, this is probably a JIT intrinsic or
|
||||
// is supplied by the runtime.
|
||||
let selfImplementation, state =
|
||||
(state, callingObjTy.Methods)
|
||||
||> List.mapFold (fun state meth ->
|
||||
if
|
||||
meth.Signature.GenericParameterCount
|
||||
<> methodToCall.Signature.GenericParameterCount
|
||||
|| meth.Signature.RequiredParameterCount
|
||||
<> methodToCall.Signature.RequiredParameterCount
|
||||
then
|
||||
None, state
|
||||
else if
|
||||
|
||||
meth.Name <> methodToCall.Name && Some meth.Name <> interfaceExplicitNamedMethod
|
||||
then
|
||||
None, state
|
||||
else
|
||||
|
||||
// TODO: check if methodToCall's declaringtype is an interface; if so, check the possible prefixed name first
|
||||
|
||||
let state, retType =
|
||||
meth.Signature.ReturnType
|
||||
|> IlMachineState.concretizeType
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
state
|
||||
meth.DeclaringType.Assembly
|
||||
methodToCall.DeclaringType.Generics
|
||||
methodToCall.Generics
|
||||
|
||||
let paramTypes, state =
|
||||
(state, meth.Signature.ParameterTypes)
|
||||
||> Seq.mapFold (fun state ty ->
|
||||
ty
|
||||
|> IlMachineState.concretizeType
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
state
|
||||
meth.DeclaringType.Assembly
|
||||
methodToCall.DeclaringType.Generics
|
||||
methodToCall.Generics
|
||||
|> fun (a, b) -> b, a
|
||||
)
|
||||
|
||||
let paramTypes = List.ofSeq paramTypes
|
||||
|
||||
if
|
||||
isAssignableFrom retType methodToCall.Signature.ReturnType state
|
||||
&& paramTypes = methodToCall.Signature.ParameterTypes
|
||||
then
|
||||
Some (meth, Some meth.Name = interfaceExplicitNamedMethod), state
|
||||
else
|
||||
None, state
|
||||
)
|
||||
|
||||
let selfImplementation =
|
||||
selfImplementation
|
||||
|> List.choose id
|
||||
|> List.sortBy (fun (_, isInterface) -> if isInterface then -1 else 0)
|
||||
|
||||
match selfImplementation with
|
||||
| (impl, true) :: l when (l |> List.forall (fun (_, b) -> not b)) ->
|
||||
logger.LogDebug "Found concrete implementation from an interface"
|
||||
|
||||
let typeGenerics =
|
||||
AllConcreteTypes.lookup callingObjTyHandle state.ConcreteTypes
|
||||
|> Option.get
|
||||
|> _.Generics
|
||||
|
||||
let state, meth, _ =
|
||||
IlMachineState.concretizeMethodWithAllGenerics
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
typeGenerics
|
||||
impl
|
||||
methodGenerics
|
||||
state
|
||||
|
||||
state, meth
|
||||
| [ impl, false ] ->
|
||||
logger.LogDebug "Found concrete implementation"
|
||||
// Yes, callingObjTy implements the method directly. No need to look up interfaces.
|
||||
let typeGenerics =
|
||||
AllConcreteTypes.lookup callingObjTyHandle state.ConcreteTypes
|
||||
|> Option.get
|
||||
|> _.Generics
|
||||
|
||||
let state, meth, _ =
|
||||
IlMachineState.concretizeMethodWithAllGenerics
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
typeGenerics
|
||||
impl
|
||||
methodGenerics
|
||||
state
|
||||
|
||||
state, meth
|
||||
| _ :: _ ->
|
||||
selfImplementation
|
||||
|> List.map (fun (m, _) -> m.Name)
|
||||
|> String.concat ", "
|
||||
|> failwithf "multiple options: %s"
|
||||
| [] ->
|
||||
|
||||
logger.LogDebug "No concrete implementation found; scanning interfaces"
|
||||
|
||||
// If not, what interfaces does it implement, and do any of those implement the method?
|
||||
let possibleInterfaceMethods, state =
|
||||
(state, callingObjTy.ImplementedInterfaces)
|
||||
||> Seq.mapFold (fun state impl ->
|
||||
let assy = state.LoadedAssembly impl.RelativeToAssembly |> Option.get
|
||||
|
||||
let state, defn =
|
||||
match impl.InterfaceHandle with
|
||||
| MetadataToken.TypeDefinition defn ->
|
||||
let state, defn = IlMachineState.lookupTypeDefn baseClassTypes state assy defn
|
||||
|
||||
let state, _, defn =
|
||||
// TODO: generics
|
||||
IlMachineState.resolveTypeFromDefn
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
defn
|
||||
ImmutableArray.Empty
|
||||
ImmutableArray.Empty
|
||||
assy
|
||||
state
|
||||
|
||||
state, defn
|
||||
| MetadataToken.TypeReference ty ->
|
||||
let state, defn, assy =
|
||||
IlMachineState.lookupTypeRef loggerFactory baseClassTypes state assy Seq.empty ty
|
||||
|
||||
state, failwith "TODO"
|
||||
| MetadataToken.TypeSpecification spec ->
|
||||
// TODO: generics
|
||||
let state, assy, defn =
|
||||
IlMachineState.resolveTypeFromSpec
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
spec
|
||||
assy
|
||||
ImmutableArray.Empty
|
||||
ImmutableArray.Empty
|
||||
state
|
||||
|
||||
state, defn
|
||||
| handle -> failwith $"unexpected: {handle}"
|
||||
|
||||
logger.LogDebug (
|
||||
"Interface {InterfaceName} (generics: {InterfaceGenerics})",
|
||||
defn.Name,
|
||||
defn.Generics
|
||||
)
|
||||
|
||||
let s, state =
|
||||
defn.Methods
|
||||
|> Seq.filter (fun mi -> mi.Name = methodToCall.Name
|
||||
// TODO: also the rest of the signature
|
||||
)
|
||||
|> Seq.mapFold
|
||||
(fun state meth ->
|
||||
// TODO: generics
|
||||
let state, mi, _ =
|
||||
IlMachineState.concretizeMethodForExecution
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
thread
|
||||
meth
|
||||
None
|
||||
(if defn.Generics.IsEmpty then None else Some defn.Generics)
|
||||
state
|
||||
|
||||
mi, state
|
||||
)
|
||||
state
|
||||
|
||||
s, state
|
||||
)
|
||||
|
||||
let possibleInterfaceMethods = possibleInterfaceMethods |> Seq.concat |> Seq.toList
|
||||
|
||||
match possibleInterfaceMethods with
|
||||
| [] ->
|
||||
logger.LogDebug "No interface implementation found either"
|
||||
state, methodToCall
|
||||
| [ meth ] ->
|
||||
logger.LogDebug (
|
||||
"Exactly one interface implementation found {DeclaringTypeNamespace}.{DeclaringTypeName}.{MethodName} ({MethodGenerics})",
|
||||
meth.DeclaringType.Namespace,
|
||||
meth.DeclaringType.Name,
|
||||
meth.Name,
|
||||
meth.Generics
|
||||
)
|
||||
|
||||
state, meth
|
||||
| _ -> failwith "TODO: handle overloads"
|
||||
| _, _, true
|
||||
| _, false, _
|
||||
| Some _, _, _ -> state, methodToCall
|
||||
|
||||
// Helper to pop and coerce a single argument
|
||||
let popAndCoerceArg zeroType methodState =
|
||||
let value, newState = MethodState.popFromStack methodState
|
||||
EvalStackValue.toCliTypeCoerced zeroType value, newState
|
||||
|
||||
// Collect arguments based on calling convention
|
||||
let args, afterPop =
|
||||
if methodToCall.IsStatic then
|
||||
// Static method: pop args in reverse order
|
||||
let args = ImmutableArray.CreateBuilder methodToCall.Parameters.Length
|
||||
let mutable currentState = activeMethodState
|
||||
|
||||
for i = methodToCall.Parameters.Length - 1 downto 0 do
|
||||
let arg, newState = popAndCoerceArg argZeroObjects.[i] currentState
|
||||
args.Add arg
|
||||
currentState <- newState
|
||||
|
||||
args.Reverse ()
|
||||
args.ToImmutable (), currentState
|
||||
else
|
||||
// Instance method: handle `this` pointer
|
||||
let argCount = methodToCall.Parameters.Length
|
||||
let args = ImmutableArray.CreateBuilder (argCount + 1)
|
||||
let mutable currentState = activeMethodState
|
||||
|
||||
match wasConstructing with
|
||||
| Some _ ->
|
||||
// Constructor: `this` is on top of stack, by our own odd little calling convention
|
||||
// where Newobj puts the object pointer on top
|
||||
let thisArg, newState =
|
||||
popAndCoerceArg
|
||||
(CliType.RuntimePointer (CliRuntimePointer.Managed ManagedPointerSource.Null))
|
||||
currentState
|
||||
|
||||
currentState <- newState
|
||||
|
||||
// Pop remaining args in reverse
|
||||
for i = argCount - 1 downto 0 do
|
||||
let arg, newState = popAndCoerceArg argZeroObjects.[i] currentState
|
||||
args.Add arg
|
||||
currentState <- newState
|
||||
|
||||
args.Add thisArg
|
||||
args.Reverse ()
|
||||
args.ToImmutable (), currentState
|
||||
| None ->
|
||||
// Regular instance method: args then `this`
|
||||
for i = argCount - 1 downto 0 do
|
||||
let arg, newState = popAndCoerceArg argZeroObjects.[i] currentState
|
||||
args.Add arg
|
||||
currentState <- newState
|
||||
|
||||
let thisArg, newState =
|
||||
popAndCoerceArg
|
||||
(CliType.RuntimePointer (CliRuntimePointer.Managed ManagedPointerSource.Null))
|
||||
currentState
|
||||
|
||||
args.Add thisArg
|
||||
currentState <- newState
|
||||
|
||||
args.Reverse ()
|
||||
args.ToImmutable (), currentState
|
||||
|
||||
// Helper to create new frame with assembly loading
|
||||
let rec createNewFrame state =
|
||||
let returnInfo =
|
||||
Some
|
||||
{
|
||||
JumpTo = threadState.ActiveMethodState
|
||||
WasInitialisingType = wasInitialising
|
||||
WasConstructingObj = wasConstructing
|
||||
}
|
||||
|
||||
match
|
||||
MethodState.Empty
|
||||
state.ConcreteTypes
|
||||
baseClassTypes
|
||||
state._LoadedAssemblies
|
||||
(state.ActiveAssembly thread)
|
||||
methodToCall
|
||||
methodGenerics
|
||||
args
|
||||
returnInfo
|
||||
with
|
||||
| Ok frame -> state, frame
|
||||
| Error toLoad ->
|
||||
let state' =
|
||||
(state, toLoad)
|
||||
||> List.fold (fun s (asmRef : WoofWare.PawPrint.AssemblyReference) ->
|
||||
let s, _, _ =
|
||||
IlMachineState.loadAssembly
|
||||
loggerFactory
|
||||
(state.LoadedAssembly methodToCall.DeclaringType.Assembly |> Option.get)
|
||||
(fst asmRef.Handle)
|
||||
s
|
||||
|
||||
s
|
||||
)
|
||||
|
||||
createNewFrame state'
|
||||
|
||||
let state, newFrame = createNewFrame state
|
||||
|
||||
let oldFrame =
|
||||
if wasClassConstructor || not advanceProgramCounterOfCaller then
|
||||
afterPop
|
||||
else
|
||||
afterPop |> MethodState.advanceProgramCounter
|
||||
|
||||
let newThreadState =
|
||||
{ threadState with
|
||||
MethodStates = threadState.MethodStates.Add(newFrame).SetItem (threadState.ActiveMethodState, oldFrame)
|
||||
ActiveMethodState = threadState.MethodStates.Length
|
||||
}
|
||||
|
||||
{ state with
|
||||
ThreadState = state.ThreadState |> Map.add thread newThreadState
|
||||
}
|
||||
|
||||
let rec loadClass
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||
(ty : ConcreteTypeHandle)
|
||||
(currentThread : ThreadId)
|
||||
(state : IlMachineState)
|
||||
: StateLoadResult
|
||||
=
|
||||
let logger = loggerFactory.CreateLogger "LoadClass"
|
||||
|
||||
match TypeInitTable.tryGet ty state.TypeInitTable with
|
||||
| Some TypeInitState.Initialized ->
|
||||
// Type already initialized; nothing to do
|
||||
StateLoadResult.NothingToDo state
|
||||
| Some (TypeInitState.InProgress tid) when tid = currentThread ->
|
||||
// We're already initializing this type on this thread; just proceed with the initialisation, no extra
|
||||
// class loading required.
|
||||
StateLoadResult.NothingToDo state
|
||||
| Some (TypeInitState.InProgress _) ->
|
||||
// This is usually signalled by WhatWeDid.Blocked
|
||||
failwith
|
||||
"TODO: cross-thread class init synchronization unimplemented - this thread has to wait for the other thread to finish initialisation"
|
||||
| None ->
|
||||
// We have work to do!
|
||||
|
||||
// Look up the concrete type from the handle
|
||||
let concreteType =
|
||||
match AllConcreteTypes.lookup ty state.ConcreteTypes with
|
||||
| Some ct -> ct
|
||||
| None -> failwith $"ConcreteTypeHandle {ty} not found in ConcreteTypes mapping"
|
||||
|
||||
let state, origAssyName =
|
||||
state.WithThreadSwitchedToAssembly concreteType.Assembly currentThread
|
||||
|
||||
let sourceAssembly = state.LoadedAssembly concreteType.Assembly |> Option.get
|
||||
|
||||
let typeDef =
|
||||
match sourceAssembly.TypeDefs.TryGetValue concreteType.Definition.Get with
|
||||
| false, _ ->
|
||||
failwith
|
||||
$"Failed to find type definition {concreteType.Definition.Get} in {concreteType.Assembly.FullName}"
|
||||
| true, v -> v
|
||||
|
||||
logger.LogDebug ("Resolving type {TypeDefNamespace}.{TypeDefName}", typeDef.Namespace, typeDef.Name)
|
||||
|
||||
// First mark as in-progress to detect cycles
|
||||
let state = state.WithTypeBeginInit currentThread ty
|
||||
|
||||
// Check if the type has a base type that needs initialization
|
||||
let firstDoBaseClass =
|
||||
match typeDef.BaseType with
|
||||
| Some baseTypeInfo ->
|
||||
// Determine if base type is in the same or different assembly
|
||||
match baseTypeInfo with
|
||||
| BaseTypeInfo.ForeignAssemblyType _ -> failwith "TODO"
|
||||
//logger.LogDebug (
|
||||
// "Resolved base type of {TypeDefNamespace}.{TypeDefName} to foreign assembly {ForeignAssemblyName}",
|
||||
// typeDef.Namespace,
|
||||
// typeDef.Name,
|
||||
// baseAssemblyName.Name
|
||||
//)
|
||||
|
||||
//match loadClass loggerFactory baseTypeHandle baseAssemblyName currentThread state with
|
||||
//| FirstLoadThis state -> Error state
|
||||
//| NothingToDo state -> Ok state
|
||||
| BaseTypeInfo.TypeDef typeDefinitionHandle ->
|
||||
logger.LogDebug (
|
||||
"Resolved base type of {TypeDefNamespace}.{TypeDefName} to this assembly, typedef",
|
||||
typeDef.Namespace,
|
||||
typeDef.Name
|
||||
)
|
||||
|
||||
let baseTypeDefn =
|
||||
DumpedAssembly.typeInfoToTypeDefn' baseClassTypes state._LoadedAssemblies typeDef
|
||||
|
||||
// Concretize the base type
|
||||
let state, baseTypeHandle =
|
||||
IlMachineState.concretizeType
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
state
|
||||
sourceAssembly.Name
|
||||
concreteType.Generics
|
||||
// TODO: surely we have generics in scope here?
|
||||
ImmutableArray.Empty
|
||||
baseTypeDefn
|
||||
|
||||
// Recursively load the base class
|
||||
match loadClass loggerFactory baseClassTypes baseTypeHandle currentThread state with
|
||||
| FirstLoadThis state -> Error state
|
||||
| NothingToDo state -> Ok state
|
||||
| BaseTypeInfo.TypeRef typeReferenceHandle ->
|
||||
let state, assy, targetType =
|
||||
// TypeRef won't have any generics; it would be a TypeSpec if it did
|
||||
IlMachineState.resolveType
|
||||
loggerFactory
|
||||
typeReferenceHandle
|
||||
ImmutableArray.Empty
|
||||
(state.ActiveAssembly currentThread)
|
||||
state
|
||||
|
||||
logger.LogDebug (
|
||||
"Resolved base type of {TypeDefNamespace}.{TypeDefName} to a typeref in assembly {ResolvedAssemblyName}, {BaseTypeNamespace}.{BaseTypeName}",
|
||||
typeDef.Namespace,
|
||||
typeDef.Name,
|
||||
assy.Name.Name,
|
||||
targetType.Namespace,
|
||||
targetType.Name
|
||||
)
|
||||
|
||||
// Create a TypeDefn from the resolved TypeRef
|
||||
let baseTypeDefn =
|
||||
targetType
|
||||
|> DumpedAssembly.typeInfoToTypeDefn baseClassTypes state._LoadedAssemblies
|
||||
|
||||
// Concretize the base type
|
||||
let state, baseTypeHandle =
|
||||
IlMachineState.concretizeType
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
state
|
||||
sourceAssembly.Name
|
||||
concreteType.Generics
|
||||
// TODO: surely we have generics in scope here?
|
||||
ImmutableArray.Empty
|
||||
baseTypeDefn
|
||||
|
||||
// Recursively load the base class
|
||||
match loadClass loggerFactory baseClassTypes baseTypeHandle currentThread state with
|
||||
| FirstLoadThis state -> Error state
|
||||
| NothingToDo state -> Ok state
|
||||
| BaseTypeInfo.TypeSpec typeSpecificationHandle ->
|
||||
failwith "TODO: TypeSpec base type loading unimplemented"
|
||||
| None -> Ok state // No base type (or it's System.Object)
|
||||
|
||||
match firstDoBaseClass with
|
||||
| Error state -> FirstLoadThis state
|
||||
| Ok state ->
|
||||
|
||||
// TODO: also need to initialise all interfaces implemented by the type
|
||||
|
||||
// Find the class constructor (.cctor) if it exists
|
||||
let cctor =
|
||||
typeDef.Methods
|
||||
|> List.tryFind (fun method -> method.Name = ".cctor" && method.IsStatic && method.Parameters.IsEmpty)
|
||||
|
||||
match cctor with
|
||||
| Some cctorMethod ->
|
||||
// Call the class constructor! Note that we *don't* use `callMethodInActiveAssembly`, because that
|
||||
// performs class loading, but we're already in the middle of loading this class.
|
||||
// TODO: factor out the common bit.
|
||||
let currentThreadState = state.ThreadState.[currentThread]
|
||||
|
||||
// Convert the method's type generics from TypeDefn to ConcreteTypeHandle
|
||||
let cctorMethodWithTypeGenerics =
|
||||
cctorMethod
|
||||
|> MethodInfo.mapTypeGenerics (fun (par, _) -> concreteType.Generics.[par.SequenceNumber])
|
||||
|
||||
// Convert method generics (should be empty for cctor)
|
||||
let cctorMethodWithMethodGenerics =
|
||||
cctorMethodWithTypeGenerics
|
||||
|> MethodInfo.mapMethodGenerics (fun _ -> failwith "cctor cannot be generic")
|
||||
|
||||
// Convert method signature from TypeDefn to ConcreteTypeHandle using concretization
|
||||
let state, convertedSignature =
|
||||
cctorMethodWithMethodGenerics.Signature
|
||||
|> TypeMethodSignature.map
|
||||
state
|
||||
(fun state typeDefn ->
|
||||
IlMachineState.concretizeType
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
state
|
||||
concreteType.Assembly
|
||||
concreteType.Generics
|
||||
// no method generics for cctor
|
||||
ImmutableArray.Empty
|
||||
typeDefn
|
||||
)
|
||||
|
||||
// Convert method instructions (local variables)
|
||||
let state, convertedInstructions =
|
||||
match cctorMethodWithMethodGenerics.Instructions with
|
||||
| None -> state, None
|
||||
| Some methodInstr ->
|
||||
let state, convertedLocalVars =
|
||||
match methodInstr.LocalVars with
|
||||
| None -> state, None
|
||||
| Some localVars ->
|
||||
// Concretize each local variable type
|
||||
let state, convertedVars =
|
||||
((state, []), localVars)
|
||||
||> Seq.fold (fun (state, acc) typeDefn ->
|
||||
let state, handle =
|
||||
IlMachineState.concretizeType
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
state
|
||||
concreteType.Assembly
|
||||
concreteType.Generics
|
||||
ImmutableArray.Empty // no method generics for cctor
|
||||
typeDefn
|
||||
|
||||
state, handle :: acc
|
||||
)
|
||||
|> Tuple.rmap ImmutableArray.CreateRange
|
||||
|
||||
state, Some convertedVars
|
||||
|
||||
state, Some (MethodInstructions.setLocalVars convertedLocalVars methodInstr)
|
||||
|
||||
let fullyConvertedMethod =
|
||||
MethodInfo.setMethodVars convertedInstructions convertedSignature cctorMethodWithMethodGenerics
|
||||
|
||||
callMethod
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
(Some ty)
|
||||
None
|
||||
true
|
||||
true
|
||||
false
|
||||
// constructor is surely not generic
|
||||
ImmutableArray.Empty
|
||||
fullyConvertedMethod
|
||||
currentThread
|
||||
currentThreadState
|
||||
state
|
||||
|> FirstLoadThis
|
||||
| None ->
|
||||
// No constructor, just continue.
|
||||
// Mark the type as initialized.
|
||||
let state = state.WithTypeEndInit currentThread ty
|
||||
|
||||
// Restore original assembly context if needed
|
||||
state.WithThreadSwitchedToAssembly origAssyName currentThread
|
||||
|> fst
|
||||
|> NothingToDo
|
||||
|
||||
let ensureTypeInitialised
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||
(thread : ThreadId)
|
||||
(ty : ConcreteTypeHandle)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState * WhatWeDid
|
||||
=
|
||||
match TypeInitTable.tryGet ty state.TypeInitTable with
|
||||
| None ->
|
||||
match loadClass loggerFactory baseClassTypes ty thread state with
|
||||
| NothingToDo state -> state, WhatWeDid.Executed
|
||||
| FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit
|
||||
| Some TypeInitState.Initialized -> state, WhatWeDid.Executed
|
||||
| Some (InProgress threadId) ->
|
||||
if threadId = thread then
|
||||
// II.10.5.3.2: avoid the deadlock by simply proceeding.
|
||||
state, WhatWeDid.Executed
|
||||
else
|
||||
state, WhatWeDid.BlockedOnClassInit threadId
|
||||
|
||||
/// It may be useful to *not* advance the program counter of the caller, e.g. if you're using `callMethodInActiveAssembly`
|
||||
/// as a convenient way to move to a different method body rather than to genuinely perform a call.
|
||||
/// (Delegates do this, for example: we get a call to invoke the delegate, and then we implement the delegate as
|
||||
/// another call to its function pointer.)
|
||||
let callMethodInActiveAssembly
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||
(thread : ThreadId)
|
||||
(performInterfaceResolution : bool)
|
||||
(advanceProgramCounterOfCaller : bool)
|
||||
(methodGenerics : TypeDefn ImmutableArray option)
|
||||
(methodToCall : WoofWare.PawPrint.MethodInfo<TypeDefn, GenericParamFromMetadata, TypeDefn>)
|
||||
(weAreConstructingObj : ManagedHeapAddress option)
|
||||
(typeArgsFromMetadata : TypeDefn ImmutableArray option)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState * WhatWeDid
|
||||
=
|
||||
let threadState = state.ThreadState.[thread]
|
||||
|
||||
let state, concretizedMethod, declaringTypeHandle =
|
||||
IlMachineState.concretizeMethodForExecution
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
thread
|
||||
methodToCall
|
||||
methodGenerics
|
||||
typeArgsFromMetadata
|
||||
state
|
||||
|
||||
let state, typeInit =
|
||||
ensureTypeInitialised loggerFactory baseClassTypes thread declaringTypeHandle state
|
||||
|
||||
match typeInit with
|
||||
| WhatWeDid.Executed ->
|
||||
callMethod
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
None
|
||||
weAreConstructingObj
|
||||
performInterfaceResolution
|
||||
false
|
||||
advanceProgramCounterOfCaller
|
||||
concretizedMethod.Generics
|
||||
concretizedMethod
|
||||
thread
|
||||
threadState
|
||||
state,
|
||||
WhatWeDid.Executed
|
||||
| _ -> state, typeInit
|
14
WoofWare.PawPrint/ImmutableArray.fs
Normal file
14
WoofWare.PawPrint/ImmutableArray.fs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System.Collections.Immutable
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal ImmutableArray =
|
||||
|
||||
let map (f : 'a -> 'b) (arr : ImmutableArray<'a>) : ImmutableArray<'b> =
|
||||
let b = ImmutableArray.CreateBuilder ()
|
||||
|
||||
for i in arr do
|
||||
b.Add (f i)
|
||||
|
||||
b.ToImmutable ()
|
655
WoofWare.PawPrint/Intrinsics.fs
Normal file
655
WoofWare.PawPrint/Intrinsics.fs
Normal file
@@ -0,0 +1,655 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System
|
||||
open System.Collections.Immutable
|
||||
open Microsoft.Extensions.Logging
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Intrinsics =
|
||||
let private safeIntrinsics =
|
||||
[
|
||||
// The IL implementation is fine: https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs#L677
|
||||
"System.Private.CoreLib", "Unsafe", "AsRef"
|
||||
// https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/String.cs#L739-L750
|
||||
"System.Private.CoreLib", "String", "get_Length"
|
||||
// https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs#L54
|
||||
"System.Private.CoreLib", "ArgumentNullException", "ThrowIfNull"
|
||||
// https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/coreclr/System.Private.CoreLib/src/System/Type.CoreCLR.cs#L82
|
||||
"System.Private.CoreLib", "Type", "GetTypeFromHandle"
|
||||
// https://github.com/dotnet/runtime/blob/108fa7856efcfd39bc991c2d849eabbf7ba5989c/src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.cs#L161
|
||||
"System.Private.CoreLib", "ReadOnlySpan`1", "get_Length"
|
||||
// https://github.com/dotnet/runtime/blob/9e5e6aa7bc36aeb2a154709a9d1192030c30a2ef/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs#L153
|
||||
"System.Private.CoreLib", "RuntimeHelpers", "CreateSpan"
|
||||
// https://github.com/dotnet/runtime/blob/d258af50034c192bf7f0a18856bf83d2903d98ae/src/libraries/System.Private.CoreLib/src/System/Math.cs#L127
|
||||
// https://github.com/dotnet/runtime/blob/d258af50034c192bf7f0a18856bf83d2903d98ae/src/libraries/System.Private.CoreLib/src/System/Math.cs#L137
|
||||
"System.Private.CoreLib", "Math", "Abs"
|
||||
// https://github.com/dotnet/runtime/blob/d258af50034c192bf7f0a18856bf83d2903d98ae/src/libraries/System.Private.CoreLib/src/System/Math.cs#L965C10-L1062C19
|
||||
"System.Private.CoreLib", "Math", "Max"
|
||||
// https://github.com/dotnet/runtime/blob/d258af50034c192bf7f0a18856bf83d2903d98ae/src/libraries/System.Private.CoreLib/src/System/Buffer.cs#L150
|
||||
"System.Private.CoreLib", "Buffer", "Memmove"
|
||||
// https://github.com/dotnet/runtime/blob/1c3221b63340d7f81dfd829f3bcd822e582324f6/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs#L799
|
||||
"System.Private.CoreLib", "Thread", "get_CurrentThread"
|
||||
]
|
||||
|> Set.ofList
|
||||
|
||||
type private RefTypeProcessingStatus =
|
||||
| InProgress
|
||||
| Completed of bool
|
||||
|
||||
let rec private containsRefType
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||
(state : IlMachineState)
|
||||
(seenSoFar : ImmutableDictionary<TypeInfo<TypeDefn, TypeDefn>, RefTypeProcessingStatus>)
|
||||
(td : TypeInfo<TypeDefn, TypeDefn>)
|
||||
: IlMachineState * ImmutableDictionary<_, RefTypeProcessingStatus> * bool
|
||||
=
|
||||
match seenSoFar.TryGetValue td with
|
||||
| true, InProgress ->
|
||||
// We've hit a cycle. Optimistically assume this path does not introduce a reference type.
|
||||
// If another path finds a reference type, its 'true' will override this.
|
||||
state, seenSoFar, false
|
||||
| true, Completed v ->
|
||||
// We've already calculated this; return the memoized result.
|
||||
state, seenSoFar, v
|
||||
| false, _ ->
|
||||
// Check if this type itself is a reference type.
|
||||
let baseType =
|
||||
td.BaseType
|
||||
|> DumpedAssembly.resolveBaseType baseClassTypes state._LoadedAssemblies td.Assembly
|
||||
|
||||
match baseType with
|
||||
| ResolvedBaseType.Delegate
|
||||
| ResolvedBaseType.Object ->
|
||||
// Short-circuit: if the type itself is a reference type, we're done.
|
||||
let seenSoFar = seenSoFar.Add (td, Completed true)
|
||||
state, seenSoFar, true
|
||||
| ResolvedBaseType.Enum
|
||||
| ResolvedBaseType.ValueType ->
|
||||
// It's a value type, so we must check its fields.
|
||||
// Mark as in progress before recursing.
|
||||
let seenSoFarWithInProgress = seenSoFar.Add (td, InProgress)
|
||||
|
||||
let stateAfterFieldResolution, nonStaticFields =
|
||||
((state, []), td.Fields)
|
||||
||> List.fold (fun (currentState, acc) field ->
|
||||
if field.IsStatic then
|
||||
currentState, acc
|
||||
else
|
||||
// TODO: generics
|
||||
let newState, _, info =
|
||||
IlMachineState.resolveTypeFromDefn
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
field.Signature
|
||||
ImmutableArray.Empty
|
||||
ImmutableArray.Empty
|
||||
(currentState.LoadedAssembly (td.Assembly) |> Option.get)
|
||||
currentState
|
||||
|
||||
newState, info :: acc
|
||||
)
|
||||
|
||||
// Recurse through the fields, correctly propagating state.
|
||||
let finalState, finalSeenSoFar, fieldsContainRefType =
|
||||
((stateAfterFieldResolution, seenSoFarWithInProgress, false), nonStaticFields)
|
||||
||> List.fold (fun (currentState, currentSeenSoFar, currentResult) field ->
|
||||
if currentResult then
|
||||
(currentState, currentSeenSoFar, true) // Short-circuit
|
||||
else
|
||||
let newState, newSeenSoFar, fieldResult =
|
||||
containsRefType loggerFactory baseClassTypes currentState currentSeenSoFar field
|
||||
|
||||
(newState, newSeenSoFar, currentResult || fieldResult)
|
||||
)
|
||||
|
||||
// Mark as completed with the final result before returning.
|
||||
let finalSeenSoFar = finalSeenSoFar.SetItem (td, Completed fieldsContainRefType)
|
||||
finalState, finalSeenSoFar, fieldsContainRefType
|
||||
|
||||
let call
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(baseClassTypes : BaseClassTypes<_>)
|
||||
(methodToCall : WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>)
|
||||
(currentThread : ThreadId)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState option
|
||||
=
|
||||
let callerAssy =
|
||||
state.ThreadState.[currentThread].MethodState.ExecutingMethod.DeclaringType.Assembly
|
||||
|
||||
if
|
||||
methodToCall.DeclaringType.Assembly.Name = "System.Private.CoreLib"
|
||||
&& methodToCall.DeclaringType.Name = "Volatile"
|
||||
then
|
||||
// These are all safely implemented in IL, just inefficient.
|
||||
// https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/Threading/Volatile.cs#L13
|
||||
None
|
||||
elif
|
||||
Set.contains
|
||||
(methodToCall.DeclaringType.Assembly.Name, methodToCall.DeclaringType.Name, methodToCall.Name)
|
||||
safeIntrinsics
|
||||
then
|
||||
None
|
||||
else
|
||||
|
||||
// In general, some implementations are in:
|
||||
// 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
|
||||
| "System.Private.CoreLib", "Type", "get_TypeHandle" ->
|
||||
// TODO: check return type is RuntimeTypeHandle
|
||||
match methodToCall.Signature.ParameterTypes with
|
||||
| _ :: _ -> failwith "bad signature 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
|
||||
match methodToCall.Signature.ParameterTypes with
|
||||
| _ :: _ -> failwith "bad signature Type.get_TypeHandle"
|
||||
| _ -> ()
|
||||
|
||||
// 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.
|
||||
let arg, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let arg =
|
||||
let rec go (arg : EvalStackValue) =
|
||||
match arg with
|
||||
| EvalStackValue.UserDefinedValueType vt ->
|
||||
match CliValueType.TryExactlyOneField vt with
|
||||
| None -> failwith "TODO"
|
||||
| Some field -> go (EvalStackValue.ofCliType field.Contents)
|
||||
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE"
|
||||
| EvalStackValue.ObjectRef addr
|
||||
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> Some addr
|
||||
| s -> failwith $"TODO: called with unrecognised arg %O{s}"
|
||||
|
||||
go arg
|
||||
|
||||
let state =
|
||||
let vt =
|
||||
// https://github.com/dotnet/runtime/blob/2b21c73fa2c32fa0195e4a411a435dda185efd08/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L92
|
||||
{
|
||||
Name = "m_type"
|
||||
Contents = CliType.ObjectRef arg
|
||||
Offset = None
|
||||
Type =
|
||||
AllConcreteTypes.findExistingConcreteType
|
||||
state.ConcreteTypes
|
||||
(baseClassTypes.RuntimeType.Assembly,
|
||||
baseClassTypes.RuntimeType.Namespace,
|
||||
baseClassTypes.RuntimeType.Name,
|
||||
ImmutableArray.Empty)
|
||||
|> Option.get
|
||||
}
|
||||
|> List.singleton
|
||||
|> CliValueType.OfFields Layout.Default
|
||||
|
||||
IlMachineState.pushToEvalStack (CliType.ValueType vt) currentThread state
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
Some state
|
||||
| "System.Private.CoreLib", "Type", "get_IsValueType" ->
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [], ConcreteBool state.ConcreteTypes -> ()
|
||||
| _ -> failwith "bad signature Type.get_IsValueType"
|
||||
|
||||
let this, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let this =
|
||||
match this with
|
||||
| EvalStackValue.ObjectRef ptr ->
|
||||
IlMachineState.dereferencePointer state (ManagedPointerSource.Heap ptr)
|
||||
| EvalStackValue.ManagedPointer ptr -> IlMachineState.dereferencePointer state ptr
|
||||
| EvalStackValue.Float _
|
||||
| EvalStackValue.Int32 _
|
||||
| EvalStackValue.Int64 _ -> failwith "refusing to dereference literal"
|
||||
| _ -> failwith "TODO"
|
||||
// `this` should be of type Type
|
||||
let ty =
|
||||
match this with
|
||||
| CliType.ValueType cvt ->
|
||||
match CliValueType.DereferenceField "m_handle" cvt with
|
||||
| CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.TypeHandlePtr cth)) -> cth
|
||||
| _ -> failwith ""
|
||||
| _ -> failwith "expected a Type"
|
||||
|
||||
let ty = AllConcreteTypes.lookup ty state.ConcreteTypes |> Option.get
|
||||
let ty = state.LoadedAssembly(ty.Assembly).Value.TypeDefs.[ty.Definition.Get]
|
||||
|
||||
let isValueType =
|
||||
match DumpedAssembly.resolveBaseType baseClassTypes state._LoadedAssemblies ty.Assembly ty.BaseType with
|
||||
| ResolvedBaseType.Enum
|
||||
| ResolvedBaseType.ValueType -> true
|
||||
| ResolvedBaseType.Object
|
||||
| ResolvedBaseType.Delegate -> false
|
||||
|
||||
IlMachineState.pushToEvalStack (CliType.ofBool isValueType) currentThread state
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Some
|
||||
| "System.Private.CoreLib", "Unsafe", "AsPointer" ->
|
||||
// Method signature: 1 generic parameter, we take a Byref of that parameter, and return a TypeDefn.Pointer(Void)
|
||||
let arg, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let toPush =
|
||||
match arg with
|
||||
| EvalStackValue.ManagedPointer ptr -> CliRuntimePointer.Managed ptr
|
||||
| x -> failwith $"TODO: Unsafe.AsPointer(%O{x})"
|
||||
|
||||
IlMachineState.pushToEvalStack (CliType.RuntimePointer toPush) currentThread state
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Some
|
||||
| "System.Private.CoreLib", "BitConverter", "SingleToInt32Bits" ->
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [ ConcreteSingle state.ConcreteTypes ], ConcreteInt32 state.ConcreteTypes -> ()
|
||||
| _ -> failwith "bad signature BitConverter.SingleToInt32Bits"
|
||||
|
||||
let arg, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let result =
|
||||
match arg with
|
||||
| EvalStackValue.Float f -> BitConverter.SingleToInt32Bits (float32<float> f) |> EvalStackValue.Int32
|
||||
| _ -> failwith "TODO"
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' result currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Some
|
||||
| "System.Private.CoreLib", "BitConverter", "Int32BitsToSingle" ->
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [ ConcreteInt32 state.ConcreteTypes ], ConcreteSingle state.ConcreteTypes -> ()
|
||||
| _ -> failwith "bad signature BitConverter.Int64BitsToSingle"
|
||||
|
||||
let arg, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let arg =
|
||||
match arg with
|
||||
| EvalStackValue.Int32 i -> i
|
||||
| _ -> failwith "$TODO: {arr}"
|
||||
|
||||
let result =
|
||||
BitConverter.Int32BitsToSingle arg |> CliNumericType.Float32 |> CliType.Numeric
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack result currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Some
|
||||
| "System.Private.CoreLib", "BitConverter", "DoubleToUInt64Bits" ->
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [ ConcreteDouble state.ConcreteTypes ], ConcreteUInt64 state.ConcreteTypes -> ()
|
||||
| _ -> failwith "bad signature BitConverter.DoubleToUInt64Bits"
|
||||
|
||||
let arg, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let arg =
|
||||
match arg with
|
||||
| EvalStackValue.Float i -> i
|
||||
| _ -> failwith "$TODO: {arr}"
|
||||
|
||||
let result =
|
||||
BitConverter.DoubleToUInt64Bits arg
|
||||
|> int64<uint64>
|
||||
|> CliNumericType.Int64
|
||||
|> CliType.Numeric
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack result currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Some
|
||||
| "System.Private.CoreLib", "BitConverter", "UInt64BitsToDouble" ->
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [ ConcreteUInt64 state.ConcreteTypes ], ConcreteDouble state.ConcreteTypes -> ()
|
||||
| _ -> failwith "bad signature BitConverter.DoubleToUInt64Bits"
|
||||
|
||||
let arg, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let arg =
|
||||
match arg with
|
||||
| EvalStackValue.Int64 i -> uint64 i
|
||||
| _ -> failwith "$TODO: {arr}"
|
||||
|
||||
let result =
|
||||
BitConverter.UInt64BitsToDouble arg |> CliNumericType.Float64 |> CliType.Numeric
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack result currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Some
|
||||
| "System.Private.CoreLib", "BitConverter", "Int64BitsToDouble" ->
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [ ConcreteInt64 state.ConcreteTypes ], ConcreteDouble state.ConcreteTypes -> ()
|
||||
| _ -> failwith "bad signature BitConverter.Int64BitsToDouble"
|
||||
|
||||
let arg, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let arg =
|
||||
match arg with
|
||||
| EvalStackValue.Int64 i -> i
|
||||
| _ -> failwith "$TODO: {arr}"
|
||||
|
||||
let result =
|
||||
BitConverter.Int64BitsToDouble arg |> CliNumericType.Float64 |> CliType.Numeric
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack result currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Some
|
||||
| "System.Private.CoreLib", "BitConverter", "DoubleToInt64Bits" ->
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [ ConcreteDouble state.ConcreteTypes ], ConcreteInt64 state.ConcreteTypes -> ()
|
||||
| _ -> failwith "bad signature BitConverter.DoubleToInt64Bits"
|
||||
|
||||
let arg, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let result =
|
||||
match arg with
|
||||
| EvalStackValue.Float f -> BitConverter.DoubleToInt64Bits f |> EvalStackValue.Int64
|
||||
| _ -> failwith "TODO"
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' result currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Some
|
||||
| "System.Private.CoreLib", "BitConverter", "SingleToUInt32Bits" ->
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [ ConcreteSingle state.ConcreteTypes ], ConcreteUInt32 state.ConcreteTypes -> ()
|
||||
| _ -> failwith "bad signature BitConverter.SingleToUInt32Bits"
|
||||
|
||||
let arg, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let result =
|
||||
match arg with
|
||||
| EvalStackValue.Float f ->
|
||||
BitConverter.SingleToUInt32Bits (float32<float> f)
|
||||
|> int<uint32>
|
||||
|> EvalStackValue.Int32
|
||||
| _ -> failwith "TODO"
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' result currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Some
|
||||
| "System.Private.CoreLib", "BitConverter", "UInt32BitsToSingle" ->
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [ ConcreteUInt32 state.ConcreteTypes ], ConcreteSingle state.ConcreteTypes -> ()
|
||||
| _ -> failwith "bad signature BitConverter.UInt32BitsToSingle"
|
||||
|
||||
let arg, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let result =
|
||||
match arg with
|
||||
| EvalStackValue.Int32 f ->
|
||||
BitConverter.UInt32BitsToSingle (uint32<int> f)
|
||||
|> float<float32>
|
||||
|> EvalStackValue.Float
|
||||
| _ -> failwith "TODO"
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' result currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Some
|
||||
| "System.Private.CoreLib", "String", "Equals" ->
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [ ConcreteString state.ConcreteTypes ; ConcreteString state.ConcreteTypes ],
|
||||
ConcreteBool state.ConcreteTypes ->
|
||||
let arg1, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let arg1 =
|
||||
match arg1 with
|
||||
| EvalStackValue.ObjectRef h
|
||||
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap h) -> h
|
||||
| EvalStackValue.Int32 _
|
||||
| EvalStackValue.Int64 _
|
||||
| EvalStackValue.Float _ -> failwith $"this isn't a string! {arg1}"
|
||||
| _ -> failwith $"TODO: %O{arg1}"
|
||||
|
||||
let arg2, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let arg2 =
|
||||
match arg2 with
|
||||
| EvalStackValue.ObjectRef h
|
||||
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap h) -> h
|
||||
| EvalStackValue.Int32 _
|
||||
| EvalStackValue.Int64 _
|
||||
| EvalStackValue.Float _ -> failwith $"this isn't a string! {arg2}"
|
||||
| _ -> failwith $"TODO: %O{arg2}"
|
||||
|
||||
if arg1 = arg2 then
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.ofBool true) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Some
|
||||
else
|
||||
|
||||
let arg1 = ManagedHeap.get arg1 state.ManagedHeap
|
||||
let arg2 = ManagedHeap.get arg2 state.ManagedHeap
|
||||
|
||||
if
|
||||
AllocatedNonArrayObject.DereferenceField "_firstChar" arg1
|
||||
<> AllocatedNonArrayObject.DereferenceField "_firstChar" arg2
|
||||
then
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.ofBool false) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Some
|
||||
else
|
||||
failwith "TODO"
|
||||
| _ -> None
|
||||
| "System.Private.CoreLib", "Unsafe", "ReadUnaligned" ->
|
||||
let ptr, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let v : CliType =
|
||||
let rec go ptr =
|
||||
match ptr with
|
||||
| EvalStackValue.ManagedPointer src -> IlMachineState.dereferencePointer state src
|
||||
| EvalStackValue.NativeInt src -> failwith "TODO"
|
||||
| EvalStackValue.ObjectRef ptr -> failwith "TODO"
|
||||
| EvalStackValue.UserDefinedValueType vt ->
|
||||
match CliValueType.TryExactlyOneField vt with
|
||||
| None -> failwith "TODO"
|
||||
| Some field -> go (EvalStackValue.ofCliType field.Contents)
|
||||
| EvalStackValue.Int32 _
|
||||
| EvalStackValue.Int64 _
|
||||
| EvalStackValue.Float _ -> failwith $"this isn't a pointer! {ptr}"
|
||||
|
||||
go ptr
|
||||
|
||||
let state =
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack v currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
Some state
|
||||
| "System.Private.CoreLib", "String", "op_Implicit" ->
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [ par ], ret ->
|
||||
let par = state.ConcreteTypes |> AllConcreteTypes.lookup par |> Option.get
|
||||
let ret = state.ConcreteTypes |> AllConcreteTypes.lookup ret |> Option.get
|
||||
|
||||
if
|
||||
par.Namespace = "System"
|
||||
&& par.Name = "String"
|
||||
&& ret.Namespace = "System"
|
||||
&& ret.Name = "ReadOnlySpan`1"
|
||||
then
|
||||
match ret.Generics |> Seq.toList with
|
||||
| [ gen ] ->
|
||||
let gen = state.ConcreteTypes |> AllConcreteTypes.lookup gen |> Option.get
|
||||
|
||||
if gen.Namespace = "System" && gen.Name = "Char" then
|
||||
// This is just an optimisation
|
||||
// https://github.com/dotnet/runtime/blob/ab105b51f8b50ec5567d7cfe9001ca54dd6f64c3/src/libraries/System.Private.CoreLib/src/System/String.cs#L363-L366
|
||||
None
|
||||
else
|
||||
failwith "TODO: unexpected params to String.op_Implicit"
|
||||
| _ -> failwith "TODO: unexpected params to String.op_Implicit"
|
||||
else
|
||||
failwith "TODO: unexpected params to String.op_Implicit"
|
||||
| _ -> failwith "TODO: unexpected params to String.op_Implicit"
|
||||
| "System.Private.CoreLib", "RuntimeHelpers", "IsReferenceOrContainsReferences" ->
|
||||
// https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs#L207
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [], ConcreteBool state.ConcreteTypes -> ()
|
||||
| _ -> failwith "bad signature for System.Private.CoreLib.RuntimeHelpers.IsReferenceOrContainsReference"
|
||||
|
||||
let arg = Seq.exactlyOne methodToCall.Generics
|
||||
|
||||
let state, result =
|
||||
// Some types appear circular, because they're hardcoded in the runtime. We have to special-case them.
|
||||
match arg with
|
||||
| ConcreteChar state.ConcreteTypes -> state, false
|
||||
| _ ->
|
||||
|
||||
let generic = AllConcreteTypes.lookup arg state.ConcreteTypes
|
||||
|
||||
let generic =
|
||||
match generic with
|
||||
| None -> failwith "somehow have not already concretised type in IsReferenceOrContainsReferences"
|
||||
| Some generic -> generic
|
||||
|
||||
let td =
|
||||
state.LoadedAssembly generic.Assembly
|
||||
|> Option.get
|
||||
|> fun a -> a.TypeDefs.[generic.Definition.Get]
|
||||
|
||||
let baseType =
|
||||
td.BaseType
|
||||
|> DumpedAssembly.resolveBaseType baseClassTypes state._LoadedAssemblies generic.Assembly
|
||||
|
||||
match baseType with
|
||||
| ResolvedBaseType.Enum
|
||||
| ResolvedBaseType.ValueType ->
|
||||
td
|
||||
|> TypeInfo.mapGeneric (fun (par, _) -> TypeDefn.GenericTypeParameter par.SequenceNumber)
|
||||
|> containsRefType loggerFactory baseClassTypes state ImmutableDictionary.Empty
|
||||
|> fun (state, _, result) -> state, result
|
||||
| ResolvedBaseType.Object
|
||||
| ResolvedBaseType.Delegate -> state, true
|
||||
|
||||
let state =
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.ofBool result) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
Some state
|
||||
| "System.Private.CoreLib", "RuntimeHelpers", "InitializeArray" ->
|
||||
// https://github.com/dotnet/runtime/blob/9e5e6aa7bc36aeb2a154709a9d1192030c30a2ef/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs#L18
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [ ConcreteNonGenericArray state.ConcreteTypes ; ConcreteRuntimeFieldHandle state.ConcreteTypes ],
|
||||
ConcreteVoid state.ConcreteTypes -> ()
|
||||
| _ -> failwith "bad signature for System.Private.CoreLib.RuntimeHelpers.InitializeArray"
|
||||
|
||||
failwith "TODO: if arg0 is null, throw NRE"
|
||||
failwith "TODO: if arg1 contains null handle, throw ArgumentException"
|
||||
|
||||
failwith "TODO: array initialization"
|
||||
| "System.Private.CoreLib", "Unsafe", "As" ->
|
||||
// https://github.com/dotnet/runtime/blob/721fdf6dcb032da1f883d30884e222e35e3d3c99/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs#L64
|
||||
let inputType, retType =
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [ input ], ret -> input, ret
|
||||
| _ -> failwith "bad signature Unsafe.As"
|
||||
|
||||
let from, to_ =
|
||||
match Seq.toList methodToCall.Generics with
|
||||
| [ from ; to_ ] -> from, to_
|
||||
| _ -> failwith "bad generics"
|
||||
|
||||
if ConcreteTypeHandle.Byref to_ <> retType then
|
||||
failwith "bad return type"
|
||||
|
||||
if ConcreteTypeHandle.Byref from <> inputType then
|
||||
failwith "bad input type"
|
||||
|
||||
let from =
|
||||
match AllConcreteTypes.lookup from state.ConcreteTypes with
|
||||
| None -> failwith "somehow have not concretised input type"
|
||||
| Some t -> t
|
||||
|
||||
let to_ =
|
||||
match AllConcreteTypes.lookup to_ state.ConcreteTypes with
|
||||
| None -> failwith "somehow have not concretised ret type"
|
||||
| Some t -> t
|
||||
|
||||
let inputAddr, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let ptr =
|
||||
match inputAddr with
|
||||
| EvalStackValue.Int32 _
|
||||
| EvalStackValue.Int64 _
|
||||
| EvalStackValue.Float _ -> failwith "expected pointer type"
|
||||
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
|
||||
| EvalStackValue.ManagedPointer src ->
|
||||
ManagedPointerSource.InterpretedAsType (src, to_)
|
||||
|> EvalStackValue.ManagedPointer
|
||||
| EvalStackValue.ObjectRef addr ->
|
||||
ManagedPointerSource.InterpretedAsType (ManagedPointerSource.Heap addr, to_)
|
||||
|> EvalStackValue.ManagedPointer
|
||||
| EvalStackValue.UserDefinedValueType evalStackValueUserType -> failwith "todo"
|
||||
|
||||
let state =
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' ptr currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
Some state
|
||||
| "System.Private.CoreLib", "Unsafe", "SizeOf" ->
|
||||
// https://github.com/dotnet/runtime/blob/721fdf6dcb032da1f883d30884e222e35e3d3c99/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs#L51
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [], ConcreteInt32 state.ConcreteTypes -> ()
|
||||
| _ -> failwith "bad signature Unsafe.SizeOf"
|
||||
|
||||
let ty =
|
||||
match Seq.toList methodToCall.Generics with
|
||||
| [ ty ] -> ty
|
||||
| _ -> failwith "bad generics"
|
||||
|
||||
let zero, state = IlMachineState.cliTypeZeroOfHandle state baseClassTypes ty
|
||||
|
||||
let size = CliType.sizeOf zero
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 size)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Some
|
||||
| "System.Private.CoreLib", "RuntimeHelpers", "CreateSpan" ->
|
||||
// https://github.com/dotnet/runtime/blob/9e5e6aa7bc36aeb2a154709a9d1192030c30a2ef/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs#L153
|
||||
None
|
||||
| "System.Private.CoreLib", "Type", "op_Equality" ->
|
||||
// https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/Type.cs#L703
|
||||
None
|
||||
| "System.Private.CoreLib", "MemoryMarshal", "GetArrayDataReference" ->
|
||||
// https://github.com/dotnet/runtime/blob/d258af50034c192bf7f0a18856bf83d2903d98ae/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.CoreCLR.cs#L20
|
||||
let generic = Seq.exactlyOne methodToCall.Generics
|
||||
|
||||
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
|
||||
| [ ConcreteGenericArray state.ConcreteTypes generic ], ConcreteByref t when t = generic -> ()
|
||||
| _ -> failwith "bad signature MemoryMarshal.GetArrayDataReference"
|
||||
|
||||
let arr, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let toPush =
|
||||
match arr with
|
||||
| EvalStackValue.Int32 _
|
||||
| EvalStackValue.Int64 _
|
||||
| EvalStackValue.Float _ -> failwith "expected reference"
|
||||
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
|
||||
| EvalStackValue.ObjectRef addr
|
||||
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) ->
|
||||
if not (state.ManagedHeap.Arrays.ContainsKey addr) then
|
||||
failwith "array not found"
|
||||
|
||||
EvalStackValue.ManagedPointer (ManagedPointerSource.ArrayIndex (addr, 0))
|
||||
| EvalStackValue.UserDefinedValueType evalStackValueUserType -> failwith "todo"
|
||||
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: raise NRE"
|
||||
| EvalStackValue.ManagedPointer _ -> failwith "todo"
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' toPush currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Some
|
||||
| a, b, c -> failwith $"TODO: implement JIT intrinsic {a}.{b}.{c}"
|
||||
|> Option.map (fun s -> s.WithThreadSwitchedToAssembly callerAssy currentThread |> fst)
|
12
WoofWare.PawPrint/List.fs
Normal file
12
WoofWare.PawPrint/List.fs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module List =
|
||||
let replaceWhere (f : 'a -> 'a option) (l : 'a list) : 'a list =
|
||||
([], l)
|
||||
||> List.fold (fun acc x ->
|
||||
match f x with
|
||||
| None -> x :: acc
|
||||
| Some y -> y :: acc
|
||||
)
|
||||
|> List.rev
|
@@ -8,11 +8,20 @@ type SyncBlock =
|
||||
|
||||
type AllocatedNonArrayObject =
|
||||
{
|
||||
Fields : Map<string, CliType>
|
||||
Type : WoofWare.PawPrint.TypeInfoCrate
|
||||
// TODO: this is a slightly odd domain; the same type for value types as class types!
|
||||
Contents : CliValueType
|
||||
ConcreteType : ConcreteTypeHandle
|
||||
SyncBlock : SyncBlock
|
||||
}
|
||||
|
||||
static member DereferenceField (name : string) (f : AllocatedNonArrayObject) : CliType =
|
||||
CliValueType.DereferenceField name f.Contents
|
||||
|
||||
static member SetField (name : string) (v : CliType) (f : AllocatedNonArrayObject) : AllocatedNonArrayObject =
|
||||
{ f with
|
||||
Contents = CliValueType.WithFieldSet name v f.Contents
|
||||
}
|
||||
|
||||
type AllocatedArray =
|
||||
{
|
||||
Length : int
|
||||
@@ -29,7 +38,9 @@ type ManagedHeap =
|
||||
StringArrayData : ImmutableArray<char>
|
||||
}
|
||||
|
||||
static member Empty : ManagedHeap =
|
||||
[<RequireQualifiedAccess>]
|
||||
module ManagedHeap =
|
||||
let empty : ManagedHeap =
|
||||
{
|
||||
NonArrayObjects = Map.empty
|
||||
FirstAvailableAddress = 1
|
||||
@@ -37,12 +48,12 @@ type ManagedHeap =
|
||||
StringArrayData = ImmutableArray.Empty
|
||||
}
|
||||
|
||||
static member GetSyncBlock (addr : ManagedHeapAddress) (heap : ManagedHeap) : SyncBlock =
|
||||
let getSyncBlock (addr : ManagedHeapAddress) (heap : ManagedHeap) : SyncBlock =
|
||||
match heap.NonArrayObjects.TryGetValue addr with
|
||||
| false, _ -> failwith "TODO: getting sync block of array"
|
||||
| true, v -> v.SyncBlock
|
||||
|
||||
static member SetSyncBlock (addr : ManagedHeapAddress) (syncValue : SyncBlock) (heap : ManagedHeap) : ManagedHeap =
|
||||
let setSyncBlock (addr : ManagedHeapAddress) (syncValue : SyncBlock) (heap : ManagedHeap) : ManagedHeap =
|
||||
match heap.NonArrayObjects.TryGetValue addr with
|
||||
| false, _ -> failwith "TODO: locked on an array object"
|
||||
| true, v ->
|
||||
@@ -55,7 +66,7 @@ type ManagedHeap =
|
||||
NonArrayObjects = heap.NonArrayObjects |> Map.add addr newV
|
||||
}
|
||||
|
||||
static member AllocateArray (ty : AllocatedArray) (heap : ManagedHeap) : ManagedHeapAddress * ManagedHeap =
|
||||
let allocateArray (ty : AllocatedArray) (heap : ManagedHeap) : ManagedHeapAddress * ManagedHeap =
|
||||
let addr = heap.FirstAvailableAddress
|
||||
|
||||
let heap =
|
||||
@@ -68,7 +79,7 @@ type ManagedHeap =
|
||||
|
||||
ManagedHeapAddress addr, heap
|
||||
|
||||
static member AllocateString (len : int) (heap : ManagedHeap) : int * ManagedHeap =
|
||||
let allocateString (len : int) (heap : ManagedHeap) : int * ManagedHeap =
|
||||
let addr = heap.StringArrayData.Length
|
||||
|
||||
let heap =
|
||||
@@ -80,7 +91,7 @@ type ManagedHeap =
|
||||
|
||||
addr, heap
|
||||
|
||||
static member SetStringData (addr : int) (contents : string) (heap : ManagedHeap) : ManagedHeap =
|
||||
let setStringData (addr : int) (contents : string) (heap : ManagedHeap) : ManagedHeap =
|
||||
let newArr =
|
||||
(heap.StringArrayData, seq { 0 .. contents.Length - 1 })
|
||||
||> Seq.fold (fun data count -> data.SetItem (addr + count, contents.[count]))
|
||||
@@ -92,11 +103,7 @@ type ManagedHeap =
|
||||
|
||||
heap
|
||||
|
||||
static member AllocateNonArray
|
||||
(ty : AllocatedNonArrayObject)
|
||||
(heap : ManagedHeap)
|
||||
: ManagedHeapAddress * ManagedHeap
|
||||
=
|
||||
let allocateNonArray (ty : AllocatedNonArrayObject) (heap : ManagedHeap) : ManagedHeapAddress * ManagedHeap =
|
||||
let addr = heap.FirstAvailableAddress
|
||||
|
||||
let heap =
|
||||
@@ -109,7 +116,7 @@ type ManagedHeap =
|
||||
|
||||
ManagedHeapAddress addr, heap
|
||||
|
||||
static member GetArrayValue (alloc : ManagedHeapAddress) (offset : int) (heap : ManagedHeap) : CliType =
|
||||
let getArrayValue (alloc : ManagedHeapAddress) (offset : int) (heap : ManagedHeap) : CliType =
|
||||
match heap.Arrays.TryGetValue alloc with
|
||||
| false, _ -> failwith "TODO: array not on heap"
|
||||
| true, arr ->
|
||||
@@ -119,13 +126,17 @@ type ManagedHeap =
|
||||
|
||||
arr.Elements.[offset]
|
||||
|
||||
static member SetArrayValue
|
||||
(alloc : ManagedHeapAddress)
|
||||
(offset : int)
|
||||
(v : CliType)
|
||||
(heap : ManagedHeap)
|
||||
: ManagedHeap
|
||||
=
|
||||
let get (alloc : ManagedHeapAddress) (heap : ManagedHeap) : AllocatedNonArrayObject =
|
||||
// TODO: arrays too
|
||||
heap.NonArrayObjects.[alloc]
|
||||
|
||||
let set (alloc : ManagedHeapAddress) (v : AllocatedNonArrayObject) (heap : ManagedHeap) : ManagedHeap =
|
||||
// TODO: arrays too
|
||||
{ heap with
|
||||
NonArrayObjects = heap.NonArrayObjects |> Map.add alloc v
|
||||
}
|
||||
|
||||
let setArrayValue (alloc : ManagedHeapAddress) (offset : int) (v : CliType) (heap : ManagedHeap) : ManagedHeap =
|
||||
let newArrs =
|
||||
heap.Arrays
|
||||
|> Map.change
|
||||
|
@@ -137,7 +137,7 @@ and MethodState =
|
||||
/// If `method` is static, `args` must be of length numParams.
|
||||
static member Empty
|
||||
(concreteTypes : AllConcreteTypes)
|
||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||
(loadedAssemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(containingAssembly : DumpedAssembly)
|
||||
(method : WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>)
|
||||
@@ -172,7 +172,7 @@ and MethodState =
|
||||
// Note: This assumes all types have already been concretized
|
||||
// If this fails with "ConcreteTypeHandle not found", it means
|
||||
// we need to ensure types are concretized before creating the MethodState
|
||||
let zero, _ = CliType.zeroOf concreteTypes loadedAssemblies corelib var
|
||||
let zero, _ = CliType.zeroOf concreteTypes loadedAssemblies baseClassTypes var
|
||||
result.Add zero
|
||||
|
||||
result.ToImmutable ()
|
||||
|
@@ -1,5 +1,6 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System
|
||||
open Microsoft.Extensions.Logging
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -37,17 +38,6 @@ module NullaryIlOp =
|
||||
| LdindR4 -> CliType.Numeric (CliNumericType.Float32 0.0f)
|
||||
| LdindR8 -> CliType.Numeric (CliNumericType.Float64 0.0)
|
||||
|
||||
/// Retrieve a value from a pointer
|
||||
let private loadFromPointerSource (state : IlMachineState) (src : ManagedPointerSource) : CliType =
|
||||
match src with
|
||||
| ManagedPointerSource.Null -> failwith "unexpected null pointer in Ldind operation"
|
||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
|
||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables.[int<uint16> whichVar]
|
||||
| ManagedPointerSource.Heap managedHeapAddress -> failwith "TODO: Heap pointer dereferencing not implemented"
|
||||
| ManagedPointerSource.ArrayIndex _ -> failwith "TODO: array index pointer dereferencing not implemented"
|
||||
|
||||
// Unified Ldind implementation
|
||||
let private executeLdind
|
||||
(targetType : LdindTargetType)
|
||||
@@ -59,11 +49,11 @@ module NullaryIlOp =
|
||||
|
||||
let loadedValue =
|
||||
match popped with
|
||||
| EvalStackValue.ManagedPointer src -> loadFromPointerSource state src
|
||||
| EvalStackValue.ManagedPointer src -> IlMachineState.dereferencePointer state src
|
||||
| EvalStackValue.NativeInt nativeIntSource ->
|
||||
failwith $"TODO: Native int pointer dereferencing not implemented for {targetType}"
|
||||
| EvalStackValue.ObjectRef managedHeapAddress ->
|
||||
failwith "TODO: Object reference dereferencing not implemented"
|
||||
IlMachineState.dereferencePointer state (ManagedPointerSource.Heap managedHeapAddress)
|
||||
| other -> failwith $"Unexpected eval stack value for Ldind operation: {other}"
|
||||
|
||||
let loadedValue = loadedValue |> EvalStackValue.ofCliType
|
||||
@@ -92,6 +82,7 @@ module NullaryIlOp =
|
||||
| EvalStackValue.ManagedPointer src ->
|
||||
match src with
|
||||
| ManagedPointerSource.Null -> failwith "TODO: throw NullReferenceException"
|
||||
| ManagedPointerSource.InterpretedAsType (src, ty) -> failwith "TODO"
|
||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
|
||||
failwith "unexpected - can we really write to an argument?"
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
||||
@@ -123,10 +114,10 @@ module NullaryIlOp =
|
||||
}
|
||||
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
|
||||
| ManagedPointerSource.ArrayIndex _ -> failwith "todo"
|
||||
| ManagedPointerSource.Field (managedPointerSource, fieldName) -> failwith "todo"
|
||||
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
|
||||
|
||||
let internal ldElem
|
||||
(targetCliTypeZero : CliType)
|
||||
(index : EvalStackValue)
|
||||
(arr : EvalStackValue)
|
||||
(currentThread : ThreadId)
|
||||
@@ -138,6 +129,7 @@ module NullaryIlOp =
|
||||
| EvalStackValue.NativeInt src ->
|
||||
match src with
|
||||
| NativeIntSource.FunctionPointer _
|
||||
| NativeIntSource.FieldHandlePtr _
|
||||
| NativeIntSource.TypeHandlePtr _
|
||||
| NativeIntSource.ManagedPointer _ -> failwith "Refusing to treat a pointer as an array index"
|
||||
| NativeIntSource.Verbatim i -> i |> int32
|
||||
@@ -174,6 +166,7 @@ module NullaryIlOp =
|
||||
| EvalStackValue.NativeInt src ->
|
||||
match src with
|
||||
| NativeIntSource.FunctionPointer _
|
||||
| NativeIntSource.FieldHandlePtr _
|
||||
| NativeIntSource.TypeHandlePtr _
|
||||
| NativeIntSource.ManagedPointer _ -> failwith "Refusing to treat a pointer as an array index"
|
||||
| NativeIntSource.Verbatim i -> i |> int32
|
||||
@@ -440,7 +433,7 @@ module NullaryIlOp =
|
||||
| Sub ->
|
||||
let val2, state = IlMachineState.popEvalStack currentThread state
|
||||
let val1, state = IlMachineState.popEvalStack currentThread state
|
||||
let result = BinaryArithmetic.execute ArithmeticOperation.sub val1 val2
|
||||
let result = BinaryArithmetic.execute ArithmeticOperation.sub state val1 val2
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' result currentThread
|
||||
@@ -450,9 +443,9 @@ module NullaryIlOp =
|
||||
| Sub_ovf -> failwith "TODO: Sub_ovf unimplemented"
|
||||
| Sub_ovf_un -> failwith "TODO: Sub_ovf_un unimplemented"
|
||||
| Add ->
|
||||
let val1, state = IlMachineState.popEvalStack currentThread state
|
||||
let val2, state = IlMachineState.popEvalStack currentThread state
|
||||
let result = BinaryArithmetic.execute ArithmeticOperation.add val1 val2
|
||||
let val1, state = IlMachineState.popEvalStack currentThread state
|
||||
let result = BinaryArithmetic.execute ArithmeticOperation.add state val1 val2
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' result currentThread
|
||||
@@ -462,18 +455,54 @@ module NullaryIlOp =
|
||||
| Add_ovf -> failwith "TODO: Add_ovf unimplemented"
|
||||
| Add_ovf_un -> failwith "TODO: Add_ovf_un unimplemented"
|
||||
| Mul ->
|
||||
let val1, state = IlMachineState.popEvalStack currentThread state
|
||||
let val2, state = IlMachineState.popEvalStack currentThread state
|
||||
let result = BinaryArithmetic.execute ArithmeticOperation.mul val1 val2
|
||||
let val1, state = IlMachineState.popEvalStack currentThread state
|
||||
let result = BinaryArithmetic.execute ArithmeticOperation.mul state val1 val2
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' result currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Mul_ovf -> failwith "TODO: Mul_ovf unimplemented"
|
||||
| Mul_ovf ->
|
||||
let val2, state = IlMachineState.popEvalStack currentThread state
|
||||
let val1, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let result =
|
||||
try
|
||||
BinaryArithmetic.execute ArithmeticOperation.mulOvf state val1 val2 |> Ok
|
||||
with :? OverflowException as e ->
|
||||
Error e
|
||||
|
||||
let state =
|
||||
match result with
|
||||
| Ok result -> state |> IlMachineState.pushToEvalStack' result currentThread
|
||||
| Error excToThrow -> failwith "TODO: throw OverflowException"
|
||||
|
||||
state
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Mul_ovf_un -> failwith "TODO: Mul_ovf_un unimplemented"
|
||||
| Div -> failwith "TODO: Div unimplemented"
|
||||
| Div ->
|
||||
let val2, state = IlMachineState.popEvalStack currentThread state
|
||||
let val1, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let result =
|
||||
try
|
||||
BinaryArithmetic.execute ArithmeticOperation.div state val1 val2 |> Ok
|
||||
with :? OverflowException as e ->
|
||||
Error e
|
||||
|
||||
let state =
|
||||
match result with
|
||||
| Ok result -> state |> IlMachineState.pushToEvalStack' result currentThread
|
||||
| Error excToThrow -> failwith "TODO: throw OverflowException"
|
||||
|
||||
state
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Div_un -> failwith "TODO: Div_un unimplemented"
|
||||
| Shr ->
|
||||
let shift, state = IlMachineState.popEvalStack currentThread state
|
||||
@@ -659,7 +688,20 @@ module NullaryIlOp =
|
||||
let state = state |> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||
| Conv_U1 -> failwith "TODO: Conv_U1 unimplemented"
|
||||
| Conv_U1 ->
|
||||
let popped, state = IlMachineState.popEvalStack currentThread state
|
||||
let converted = EvalStackValue.convToUInt8 popped
|
||||
|
||||
let state =
|
||||
match converted with
|
||||
| None -> failwith "TODO: Conv_U8 conversion failure unimplemented"
|
||||
| Some conv ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 conv) currentThread
|
||||
|
||||
let state = state |> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||
| Conv_U2 -> failwith "TODO: Conv_U2 unimplemented"
|
||||
| Conv_U4 -> failwith "TODO: Conv_U4 unimplemented"
|
||||
| Conv_U8 ->
|
||||
@@ -682,6 +724,7 @@ module NullaryIlOp =
|
||||
let popped =
|
||||
match popped with
|
||||
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE"
|
||||
| EvalStackValue.ObjectRef addr
|
||||
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> addr
|
||||
| _ -> failwith $"can't get len of {popped}"
|
||||
|
||||
@@ -775,7 +818,7 @@ module NullaryIlOp =
|
||||
match
|
||||
ExceptionHandling.findExceptionHandler
|
||||
currentMethodState.IlOpIndex
|
||||
heapObject.Type
|
||||
heapObject.ConcreteType
|
||||
currentMethodState.ExecutingMethod
|
||||
state._LoadedAssemblies
|
||||
with
|
||||
@@ -866,7 +909,16 @@ module NullaryIlOp =
|
||||
| Ldind_u8 -> failwith "TODO: Ldind_u8 unimplemented"
|
||||
| Ldind_r4 -> executeLdind LdindTargetType.LdindR4 currentThread state
|
||||
| Ldind_r8 -> executeLdind LdindTargetType.LdindR8 currentThread state
|
||||
| Rem -> failwith "TODO: Rem unimplemented"
|
||||
| Rem ->
|
||||
let val2, state = IlMachineState.popEvalStack currentThread state
|
||||
let val1, state = IlMachineState.popEvalStack currentThread state
|
||||
let result = BinaryArithmetic.execute ArithmeticOperation.rem state val1 val2
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' result currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Rem_un -> failwith "TODO: Rem_un unimplemented"
|
||||
| Volatile -> failwith "TODO: Volatile unimplemented"
|
||||
| Tail -> failwith "TODO: Tail unimplemented"
|
||||
@@ -889,20 +941,12 @@ module NullaryIlOp =
|
||||
|
||||
let referenced =
|
||||
match addr with
|
||||
| EvalStackValue.ManagedPointer src ->
|
||||
match src with
|
||||
| ManagedPointerSource.Null -> failwith "TODO: throw NRE"
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables
|
||||
.[int<uint16> whichVar]
|
||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
|
||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
|
||||
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
|
||||
| ManagedPointerSource.ArrayIndex _ -> failwith "todo"
|
||||
| EvalStackValue.ManagedPointer src -> IlMachineState.dereferencePointer state src
|
||||
| a -> failwith $"TODO: {a}"
|
||||
|
||||
let state =
|
||||
match referenced with
|
||||
| CliType.RuntimePointer (CliRuntimePointer.Managed _)
|
||||
| CliType.ObjectRef _ -> IlMachineState.pushToEvalStack referenced currentThread state
|
||||
| _ -> failwith $"Unexpected non-reference {referenced}"
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
@@ -926,6 +970,8 @@ module NullaryIlOp =
|
||||
arr
|
||||
(EvalStackValue.toCliTypeCoerced (CliType.ObjectRef None) value)
|
||||
index
|
||||
| ManagedPointerSource.Field _ -> failwith "TODO"
|
||||
| ManagedPointerSource.InterpretedAsType (src, ty) -> failwith "TODO"
|
||||
| addr -> failwith $"TODO: {addr}"
|
||||
|
||||
let state = state |> IlMachineState.advanceProgramCounter currentThread
|
||||
@@ -946,7 +992,7 @@ module NullaryIlOp =
|
||||
let index, state = IlMachineState.popEvalStack currentThread state
|
||||
let arr, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
ldElem (CliType.ObjectRef None) index arr currentThread state
|
||||
ldElem index arr currentThread state
|
||||
| Stelem_i ->
|
||||
let value, state = IlMachineState.popEvalStack currentThread state
|
||||
let index, state = IlMachineState.popEvalStack currentThread state
|
||||
|
@@ -3,21 +3,33 @@ namespace WoofWare.PawPrint
|
||||
open System
|
||||
open System.Collections.Immutable
|
||||
open System.IO
|
||||
open System.Reflection.Metadata
|
||||
open Microsoft.Extensions.Logging
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Program =
|
||||
/// Returns the pointer to the resulting array on the heap.
|
||||
let allocateArgs
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(args : string list)
|
||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||
(state : IlMachineState)
|
||||
: ManagedHeapAddress * IlMachineState
|
||||
=
|
||||
let state, stringType =
|
||||
DumpedAssembly.typeInfoToTypeDefn' corelib state._LoadedAssemblies corelib.String
|
||||
|> IlMachineState.concretizeType
|
||||
loggerFactory
|
||||
corelib
|
||||
state
|
||||
corelib.Corelib.Name
|
||||
ImmutableArray.Empty
|
||||
ImmutableArray.Empty
|
||||
|
||||
let argsAllocations, state =
|
||||
(state, args)
|
||||
||> Seq.mapFold (fun state arg ->
|
||||
IlMachineState.allocateManagedObject corelib.String (failwith "TODO: assert fields and populate") state
|
||||
IlMachineState.allocateManagedObject stringType (failwith "TODO: assert fields and populate") state
|
||||
// TODO: set the char values in memory
|
||||
)
|
||||
|
||||
@@ -28,7 +40,7 @@ module Program =
|
||||
((state, 0), argsAllocations)
|
||||
||> Seq.fold (fun (state, i) arg ->
|
||||
let state =
|
||||
IlMachineState.setArrayValue arrayAllocation (CliType.OfManagedObject arg) i state
|
||||
IlMachineState.setArrayValue arrayAllocation (CliType.ofManagedObject arg) i state
|
||||
|
||||
state, i + 1
|
||||
)
|
||||
@@ -94,7 +106,7 @@ module Program =
|
||||
(currentAssembly : DumpedAssembly)
|
||||
(continueWithGeneric :
|
||||
IlMachineState
|
||||
-> TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
-> TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||
-> DumpedAssembly
|
||||
-> IlMachineState * BaseClassTypes<DumpedAssembly> option)
|
||||
(continueWithResolved :
|
||||
@@ -112,7 +124,7 @@ module Program =
|
||||
let rec go state =
|
||||
// Resolve the type reference to find which assembly it's in
|
||||
match
|
||||
Assembly.resolveTypeRef state._LoadedAssemblies currentAssembly typeRef ImmutableArray.Empty
|
||||
Assembly.resolveTypeRef state._LoadedAssemblies currentAssembly ImmutableArray.Empty typeRef
|
||||
with
|
||||
| TypeResolutionResult.FirstLoadAssy assyRef ->
|
||||
// Need to load this assembly first
|
||||
@@ -145,7 +157,7 @@ module Program =
|
||||
|
||||
let rec findCoreLibraryAssemblyFromGeneric
|
||||
(state : IlMachineState)
|
||||
(currentType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>)
|
||||
(currentType : TypeInfo<GenericParamFromMetadata, TypeDefn>)
|
||||
(currentAssembly : DumpedAssembly)
|
||||
=
|
||||
match currentType.BaseType with
|
||||
@@ -186,7 +198,7 @@ module Program =
|
||||
// Use the original method from metadata, but convert FakeUnit to TypeDefn
|
||||
let rawMainMethod =
|
||||
mainMethodFromMetadata
|
||||
|> MethodInfo.mapTypeGenerics (fun i _ -> TypeDefn.GenericTypeParameter i)
|
||||
|> MethodInfo.mapTypeGenerics (fun (i, _) -> TypeDefn.GenericTypeParameter i.SequenceNumber)
|
||||
|
||||
let state, concretizedMainMethod, _ =
|
||||
IlMachineState.concretizeMethodWithTypeGenerics
|
||||
@@ -233,7 +245,7 @@ module Program =
|
||||
| Some baseTypes ->
|
||||
let rawMainMethod =
|
||||
mainMethodFromMetadata
|
||||
|> MethodInfo.mapTypeGenerics (fun i _ -> TypeDefn.GenericTypeParameter i)
|
||||
|> MethodInfo.mapTypeGenerics (fun (i, _) -> TypeDefn.GenericTypeParameter i.SequenceNumber)
|
||||
|
||||
IlMachineState.concretizeMethodWithTypeGenerics
|
||||
loggerFactory
|
||||
@@ -249,7 +261,11 @@ module Program =
|
||||
let rec loadInitialState (state : IlMachineState) =
|
||||
match
|
||||
state
|
||||
|> IlMachineState.loadClass loggerFactory (Option.toObj baseClassTypes) mainTypeHandle mainThread
|
||||
|> IlMachineStateExecution.loadClass
|
||||
loggerFactory
|
||||
(Option.toObj baseClassTypes)
|
||||
mainTypeHandle
|
||||
mainThread
|
||||
with
|
||||
| StateLoadResult.NothingToDo ilMachineState -> ilMachineState
|
||||
| StateLoadResult.FirstLoadThis ilMachineState -> loadInitialState ilMachineState
|
||||
@@ -271,7 +287,7 @@ module Program =
|
||||
let arrayAllocation, state =
|
||||
match mainMethodFromMetadata.Signature.ParameterTypes |> Seq.toList with
|
||||
| [ TypeDefn.OneDimensionalArrayLowerBoundZero (TypeDefn.PrimitiveType PrimitiveType.String) ] ->
|
||||
allocateArgs argv baseClassTypes state
|
||||
allocateArgs loggerFactory argv baseClassTypes state
|
||||
| _ -> failwith "Main method must take an array of strings; other signatures not yet implemented"
|
||||
|
||||
match mainMethodFromMetadata.Signature.ReturnType with
|
||||
@@ -286,6 +302,11 @@ module Program =
|
||||
|
||||
logger.LogInformation "Main method class now initialised"
|
||||
|
||||
let state =
|
||||
{ state with
|
||||
ConcreteTypes = Corelib.concretizeAll state._LoadedAssemblies baseClassTypes state.ConcreteTypes
|
||||
}
|
||||
|
||||
// Now that BCL initialisation has taken place and the user-code classes are constructed,
|
||||
// overwrite the main thread completely using the already-concretized method.
|
||||
let methodState =
|
||||
@@ -297,7 +318,7 @@ module Program =
|
||||
dumped
|
||||
concretizedMainMethod
|
||||
ImmutableArray.Empty
|
||||
(ImmutableArray.Create (CliType.OfManagedObject arrayAllocation))
|
||||
(ImmutableArray.Create (CliType.ofManagedObject arrayAllocation))
|
||||
None
|
||||
with
|
||||
| Ok s -> s
|
||||
@@ -313,7 +334,7 @@ module Program =
|
||||
{ state with
|
||||
ThreadState = state.ThreadState |> Map.add mainThread threadState
|
||||
}
|
||||
|> IlMachineState.ensureTypeInitialised loggerFactory baseClassTypes mainThread mainTypeHandle
|
||||
|> IlMachineStateExecution.ensureTypeInitialised loggerFactory baseClassTypes mainThread mainTypeHandle
|
||||
|
||||
match init with
|
||||
| WhatWeDid.SuspendedForClassInit -> failwith "TODO: suspended for class init"
|
||||
|
@@ -1,18 +1,12 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
type CanonicalTypeIdentity =
|
||||
{
|
||||
AssemblyFullName : string
|
||||
FullyQualifiedTypeName : string
|
||||
Generics : CanonicalTypeIdentity list
|
||||
}
|
||||
open System.Collections.Immutable
|
||||
|
||||
type TypeHandleRegistry =
|
||||
private
|
||||
{
|
||||
TypeHandleToType : Map<int64<typeHandle>, CanonicalTypeIdentity>
|
||||
TypeToHandle : Map<CanonicalTypeIdentity, int64<typeHandle> * ManagedHeapAddress>
|
||||
NextHandle : int64<typeHandle>
|
||||
TypeHandleToType : Map<ManagedHeapAddress, ConcreteTypeHandle>
|
||||
TypeToHandle : Map<ConcreteTypeHandle, ManagedHeapAddress>
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -21,44 +15,89 @@ module TypeHandleRegistry =
|
||||
{
|
||||
TypeHandleToType = Map.empty
|
||||
TypeToHandle = Map.empty
|
||||
NextHandle = 1L<typeHandle>
|
||||
}
|
||||
|
||||
/// Returns an allocated System.RuntimeType as well.
|
||||
let getOrAllocate
|
||||
(allConcreteTypes : AllConcreteTypes)
|
||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||
(allocState : 'allocState)
|
||||
(allocate : (string * CliType) list -> 'allocState -> ManagedHeapAddress * 'allocState)
|
||||
(def : CanonicalTypeIdentity)
|
||||
(allocate : CliValueType -> 'allocState -> ManagedHeapAddress * 'allocState)
|
||||
(def : ConcreteTypeHandle)
|
||||
(reg : TypeHandleRegistry)
|
||||
: (int64<typeHandle> * ManagedHeapAddress) * TypeHandleRegistry * 'allocState
|
||||
: ManagedHeapAddress * TypeHandleRegistry * 'allocState
|
||||
=
|
||||
match Map.tryFind def reg.TypeToHandle with
|
||||
| Some v -> v, reg, allocState
|
||||
| None ->
|
||||
|
||||
let handle = reg.NextHandle
|
||||
|
||||
// Here follows the class System.RuntimeType, which is an internal class type with a constructor
|
||||
// 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 =
|
||||
[
|
||||
// for the GC, I think?
|
||||
"m_keepalive", CliType.ObjectRef 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
|
||||
"m_cache", CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.Verbatim 0L))
|
||||
"m_handle", CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.TypeHandlePtr handle))
|
||||
{
|
||||
Name = "m_keepalive"
|
||||
Contents = CliType.ObjectRef None
|
||||
Offset = None
|
||||
Type =
|
||||
AllConcreteTypes.findExistingConcreteType
|
||||
allConcreteTypes
|
||||
(corelib.Object.Assembly,
|
||||
corelib.Object.Namespace,
|
||||
corelib.Object.Name,
|
||||
ImmutableArray.Empty)
|
||||
|> Option.get
|
||||
}
|
||||
{
|
||||
Name = "m_cache"
|
||||
Contents = CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.Verbatim 0L))
|
||||
Offset = None
|
||||
Type =
|
||||
AllConcreteTypes.findExistingConcreteType
|
||||
allConcreteTypes
|
||||
(corelib.IntPtr.Assembly,
|
||||
corelib.IntPtr.Namespace,
|
||||
corelib.IntPtr.Name,
|
||||
ImmutableArray.Empty)
|
||||
|> Option.get
|
||||
}
|
||||
{
|
||||
Name = "m_handle"
|
||||
Contents = CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.TypeHandlePtr def))
|
||||
Offset = None
|
||||
Type =
|
||||
AllConcreteTypes.findExistingConcreteType
|
||||
allConcreteTypes
|
||||
(corelib.IntPtr.Assembly,
|
||||
corelib.IntPtr.Namespace,
|
||||
corelib.IntPtr.Name,
|
||||
ImmutableArray.Empty)
|
||||
|> Option.get
|
||||
}
|
||||
// This is the const -1, apparently?!
|
||||
// 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
|
||||
Type =
|
||||
AllConcreteTypes.findExistingConcreteType
|
||||
allConcreteTypes
|
||||
(corelib.Int32.Assembly, corelib.Int32.Namespace, corelib.Int32.Name, ImmutableArray.Empty)
|
||||
|> Option.get
|
||||
}
|
||||
]
|
||||
|> CliValueType.OfFields Layout.Default
|
||||
|
||||
let alloc, state = allocate fields allocState
|
||||
|
||||
let reg =
|
||||
{
|
||||
NextHandle = handle + 1L<typeHandle>
|
||||
TypeHandleToType = reg.TypeHandleToType |> Map.add handle def
|
||||
TypeToHandle = reg.TypeToHandle |> Map.add def (handle, alloc)
|
||||
TypeHandleToType = reg.TypeHandleToType |> Map.add alloc def
|
||||
TypeToHandle = reg.TypeToHandle |> Map.add def alloc
|
||||
}
|
||||
|
||||
(handle, alloc), reg, state
|
||||
alloc, reg, state
|
||||
|
@@ -107,8 +107,8 @@ module internal UnaryConstIlOp =
|
||||
| EvalStackValue.NativeInt i -> not (NativeIntSource.isZero i)
|
||||
| EvalStackValue.Float f -> failwith "TODO: Brfalse_s float semantics undocumented"
|
||||
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> false
|
||||
| EvalStackValue.ObjectRef _
|
||||
| EvalStackValue.ManagedPointer _ -> true
|
||||
| EvalStackValue.ObjectRef _ -> failwith "TODO: Brfalse_s ObjectRef comparison unimplemented"
|
||||
| EvalStackValue.UserDefinedValueType _ ->
|
||||
failwith "TODO: Brfalse_s UserDefinedValueType comparison unimplemented"
|
||||
|
||||
@@ -129,8 +129,8 @@ module internal UnaryConstIlOp =
|
||||
| EvalStackValue.NativeInt i -> not (NativeIntSource.isZero i)
|
||||
| EvalStackValue.Float f -> failwith "TODO: Brtrue_s float semantics undocumented"
|
||||
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> false
|
||||
| EvalStackValue.ObjectRef _
|
||||
| EvalStackValue.ManagedPointer _ -> true
|
||||
| EvalStackValue.ObjectRef _ -> failwith "TODO: Brtrue_s ObjectRef comparison unimplemented"
|
||||
| EvalStackValue.UserDefinedValueType _ ->
|
||||
failwith "TODO: Brtrue_s UserDefinedValueType comparison unimplemented"
|
||||
|
||||
@@ -212,7 +212,33 @@ module internal UnaryConstIlOp =
|
||||
else
|
||||
id
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Blt_s b -> failwith "TODO: Blt_s unimplemented"
|
||||
| Blt_s b ->
|
||||
let value2, state = IlMachineState.popEvalStack currentThread state
|
||||
let value1, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let isLessThan =
|
||||
match value1, value2 with
|
||||
| EvalStackValue.Int32 v1, EvalStackValue.Int32 v2 -> v1 < v2
|
||||
| EvalStackValue.Int32 i, EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
|
||||
| EvalStackValue.Int32 i, _ -> failwith $"invalid comparison, {i} with {value2}"
|
||||
| EvalStackValue.Int64 v1, EvalStackValue.Int64 v2 -> v1 < v2
|
||||
| EvalStackValue.Int64 i, _ -> failwith $"invalid comparison, {i} with {value2}"
|
||||
| EvalStackValue.NativeInt nativeIntSource, _ -> failwith "todo"
|
||||
| EvalStackValue.Float v1, EvalStackValue.Float v2 -> failwith "todo"
|
||||
| EvalStackValue.Float f, _ -> failwith $"invalid comparison, {f} with {value2}"
|
||||
| EvalStackValue.ManagedPointer v1, EvalStackValue.ManagedPointer v2 -> failwith "todo"
|
||||
| EvalStackValue.ManagedPointer v1, _ -> failwith $"invalid comparison, {v1} with {value2}"
|
||||
| EvalStackValue.ObjectRef _, _ -> failwith "todo"
|
||||
| EvalStackValue.UserDefinedValueType _, _ ->
|
||||
failwith "unexpectedly tried to compare user-defined value type"
|
||||
|
||||
state
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> if isLessThan then
|
||||
IlMachineState.jumpProgramCounter currentThread (int<int8> b)
|
||||
else
|
||||
id
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Ble_s b ->
|
||||
let value2, state = IlMachineState.popEvalStack currentThread state
|
||||
let value1, state = IlMachineState.popEvalStack currentThread state
|
||||
@@ -240,7 +266,33 @@ module internal UnaryConstIlOp =
|
||||
else
|
||||
id
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Bgt_s b -> failwith "TODO: Bgt_s unimplemented"
|
||||
| Bgt_s b ->
|
||||
let value2, state = IlMachineState.popEvalStack currentThread state
|
||||
let value1, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let isGreaterThan =
|
||||
match value1, value2 with
|
||||
| EvalStackValue.Int32 v1, EvalStackValue.Int32 v2 -> v1 > v2
|
||||
| EvalStackValue.Int32 i, EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
|
||||
| EvalStackValue.Int32 i, _ -> failwith $"invalid comparison, {i} with {value2}"
|
||||
| EvalStackValue.Int64 v1, EvalStackValue.Int64 v2 -> v1 > v2
|
||||
| EvalStackValue.Int64 i, _ -> failwith $"invalid comparison, {i} with {value2}"
|
||||
| EvalStackValue.NativeInt nativeIntSource, _ -> failwith "todo"
|
||||
| EvalStackValue.Float v1, EvalStackValue.Float v2 -> failwith "todo"
|
||||
| EvalStackValue.Float f, _ -> failwith $"invalid comparison, {f} with {value2}"
|
||||
| EvalStackValue.ManagedPointer v1, EvalStackValue.ManagedPointer v2 -> failwith "todo"
|
||||
| EvalStackValue.ManagedPointer v1, _ -> failwith $"invalid comparison, {v1} with {value2}"
|
||||
| EvalStackValue.ObjectRef _, _ -> failwith "todo"
|
||||
| EvalStackValue.UserDefinedValueType _, _ ->
|
||||
failwith "unexpectedly tried to compare user-defined value type"
|
||||
|
||||
state
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> if isGreaterThan then
|
||||
IlMachineState.jumpProgramCounter currentThread (int<int8> b)
|
||||
else
|
||||
id
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Bge_s b ->
|
||||
let value2, state = IlMachineState.popEvalStack currentThread state
|
||||
let value1, state = IlMachineState.popEvalStack currentThread state
|
||||
@@ -351,7 +403,35 @@ module internal UnaryConstIlOp =
|
||||
else
|
||||
id
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Bne_un_s b -> failwith "TODO: Bne_un_s unimplemented"
|
||||
| Bne_un_s b ->
|
||||
// Table III.4
|
||||
let value2, state = IlMachineState.popEvalStack currentThread state
|
||||
let value1, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let isNotEqual =
|
||||
match value1, value2 with
|
||||
| EvalStackValue.Int32 v1, EvalStackValue.Int32 v2 -> v1 <> v2
|
||||
| EvalStackValue.Int32 v1, EvalStackValue.NativeInt v2 -> failwith "TODO"
|
||||
| EvalStackValue.Int32 v1, _ -> failwith $"invalid comparison, {v1} with {value2}"
|
||||
| _, EvalStackValue.Int32 v2 -> failwith $"invalid comparison, {value1} with {v2}"
|
||||
| EvalStackValue.Int64 v1, EvalStackValue.Int64 v2 -> v1 <> v2
|
||||
| EvalStackValue.Int64 v1, _ -> failwith $"invalid comparison, {v1} with {value2}"
|
||||
| _, EvalStackValue.Int64 v2 -> failwith $"invalid comparison, {value1} with {v2}"
|
||||
| EvalStackValue.Float v1, EvalStackValue.Float v2 -> v1 <> v2
|
||||
| _, EvalStackValue.Float v2 -> failwith $"invalid comparison, {value1} with {v2}"
|
||||
| EvalStackValue.Float v1, _ -> failwith $"invalid comparison, {v1} with {value2}"
|
||||
| EvalStackValue.NativeInt v1, EvalStackValue.NativeInt v2 -> v1 <> v2
|
||||
| EvalStackValue.ManagedPointer ptr1, EvalStackValue.ManagedPointer ptr2 -> ptr1 <> ptr2
|
||||
| EvalStackValue.ObjectRef ptr1, EvalStackValue.ObjectRef ptr2 -> ptr1 <> ptr2
|
||||
| _, _ -> failwith $"TODO {value1} {value2} (see table III.4)"
|
||||
|
||||
state
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> if isNotEqual then
|
||||
IlMachineState.jumpProgramCounter currentThread (int b)
|
||||
else
|
||||
id
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Bge_un_s b ->
|
||||
let value2, state = IlMachineState.popEvalStack currentThread state
|
||||
let value1, state = IlMachineState.popEvalStack currentThread state
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,15 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection
|
||||
open Microsoft.Extensions.Logging
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module internal UnaryStringTokenIlOp =
|
||||
let execute
|
||||
(baseClassTypes : BaseClassTypes<'a>)
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||
(op : UnaryStringTokenIlOp)
|
||||
(sh : StringToken)
|
||||
(state : IlMachineState)
|
||||
@@ -25,6 +28,8 @@ module internal UnaryStringTokenIlOp =
|
||||
|
||||
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 =
|
||||
baseClassTypes.String.Fields
|
||||
|> List.choose (fun field ->
|
||||
@@ -47,12 +52,46 @@ module internal UnaryStringTokenIlOp =
|
||||
|
||||
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
|
||||
Type =
|
||||
AllConcreteTypes.findExistingConcreteType
|
||||
state.ConcreteTypes
|
||||
(baseClassTypes.Char.Assembly,
|
||||
baseClassTypes.Char.Namespace,
|
||||
baseClassTypes.Char.Name,
|
||||
ImmutableArray.Empty)
|
||||
|> Option.get
|
||||
}
|
||||
{
|
||||
Name = "_stringLength"
|
||||
Contents = CliType.Numeric (CliNumericType.Int32 stringToAllocate.Length)
|
||||
Offset = None
|
||||
Type =
|
||||
AllConcreteTypes.findExistingConcreteType
|
||||
state.ConcreteTypes
|
||||
(baseClassTypes.Int32.Assembly,
|
||||
baseClassTypes.Int32.Namespace,
|
||||
baseClassTypes.Int32.Name,
|
||||
ImmutableArray.Empty)
|
||||
|> Option.get
|
||||
}
|
||||
]
|
||||
|> CliValueType.OfFields Layout.Default
|
||||
|
||||
let addr, state =
|
||||
IlMachineState.allocateManagedObject baseClassTypes.String fields state
|
||||
let state, stringType =
|
||||
DumpedAssembly.typeInfoToTypeDefn' baseClassTypes state._LoadedAssemblies baseClassTypes.String
|
||||
|> IlMachineState.concretizeType
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
state
|
||||
baseClassTypes.Corelib.Name
|
||||
ImmutableArray.Empty
|
||||
ImmutableArray.Empty
|
||||
|
||||
let addr, state = IlMachineState.allocateManagedObject stringType fields state
|
||||
|
||||
addr,
|
||||
{ state with
|
||||
|
@@ -7,20 +7,25 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Tuple.fs" />
|
||||
<Compile Include="List.fs" />
|
||||
<Compile Include="ImmutableArray.fs" />
|
||||
<Compile Include="Result.fs" />
|
||||
<Compile Include="Corelib.fs" />
|
||||
<Compile Include="AbstractMachineDomain.fs" />
|
||||
<Compile Include="BasicCliType.fs" />
|
||||
<Compile Include="TypeHandleRegistry.fs" />
|
||||
<Compile Include="FieldHandleRegistry.fs" />
|
||||
<Compile Include="ManagedHeap.fs" />
|
||||
<Compile Include="TypeInitialisation.fs" />
|
||||
<Compile Include="Exceptions.fs" />
|
||||
<Compile Include="EvalStack.fs" />
|
||||
<Compile Include="EvalStackValueComparisons.fs" />
|
||||
<Compile Include="BinaryArithmetic.fs" />
|
||||
<Compile Include="MethodState.fs" />
|
||||
<Compile Include="ThreadState.fs" />
|
||||
<Compile Include="IlMachineState.fs" />
|
||||
<Compile Include="BinaryArithmetic.fs" />
|
||||
<Compile Include="Intrinsics.fs" />
|
||||
<Compile Include="IlMachineStateExecution.fs" />
|
||||
<Compile Include="NullaryIlOp.fs" />
|
||||
<Compile Include="UnaryMetadataIlOp.fs" />
|
||||
<Compile Include="UnaryStringTokenIlOp.fs" />
|
||||
|
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1750836778,
|
||||
"narHash": "sha256-sRLyRiC7TezRbbjGJwUFOgb2xMbSr3wQ0oJKfYlQ6s0=",
|
||||
"lastModified": 1756381814,
|
||||
"narHash": "sha256-tzo7YvAsGlzo4WiIHT0ooR59VHu+aKRQdHk7sIyoia4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d7bb1922f0bb3d0c990f56f9cdb767fdb20a5f22",
|
||||
"rev": "aca2499b79170038df0dbaec8bf2f689b506ad32",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
101
nix/deps.json
101
nix/deps.json
@@ -21,8 +21,8 @@
|
||||
},
|
||||
{
|
||||
"pname": "FSharp.Core",
|
||||
"version": "9.0.202",
|
||||
"hash": "sha256-64Gub0qemmCoMa1tDus6TeTuB1+5sHfE6KD2j4o84mA="
|
||||
"version": "9.0.303",
|
||||
"hash": "sha256-AxR6wqodeU23KOTgkUfIgbavgbcSuzD4UBP+tiFydgA="
|
||||
},
|
||||
{
|
||||
"pname": "FsUnit",
|
||||
@@ -31,33 +31,33 @@
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.ApplicationInsights",
|
||||
"version": "2.22.0",
|
||||
"hash": "sha256-mUQ63atpT00r49ca50uZu2YCiLg3yd6r3HzTryqcuEA="
|
||||
"version": "2.23.0",
|
||||
"hash": "sha256-5sf3bg7CZZjHseK+F3foOchEhmVeioePxMZVvS6Rjb0="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Ref",
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-NNGXfUV5RVt1VqLI99NlHoBkt2Vv/Hg3TAHzm8nGM8M="
|
||||
"version": "8.0.19",
|
||||
"hash": "sha256-QySX2bih1UvwmLcn9cy1j+RuvZZwbcFKggL5Y/WcTnw="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Runtime.linux-arm64",
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-Eunz3nZF5r8a9nqwdeorQPgqd5G+Z4ddofMeAk6VmnA="
|
||||
"version": "8.0.19",
|
||||
"hash": "sha256-69S+Ywyc5U8PDsVkkCVvZdHOgWb6ZZ3+f4UA0MLOLFI="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Runtime.linux-x64",
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-SWdah72tC5i2CQL4mRUYfHC0Kh8+C2jiskIIeC74smY="
|
||||
"version": "8.0.19",
|
||||
"hash": "sha256-u50rdLuoADSDCthx2Fg+AnT192TalHhFrzFCfMgmTn4="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Runtime.osx-arm64",
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-y55EGfQ2FzrY2X5+Ne5N3dqi5WNHkFTGVW1hEMrh6OI="
|
||||
"version": "8.0.19",
|
||||
"hash": "sha256-QAKu2xD4UQ4+gX79ynNQ0aA07D+EW6Ke0jRiTZne8CY="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Runtime.osx-x64",
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-uRCCNPevPemvKIuUxy/VtQlgskChbiAauMWVK/xhoc0="
|
||||
"version": "8.0.19",
|
||||
"hash": "sha256-v5lzESMpodrH2grgk8ojA6BLDUfyxX5r6YY5Pgq61tA="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.CodeAnalysis.Analyzers",
|
||||
@@ -106,48 +106,48 @@
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Host.linux-arm64",
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-pzOqFCd+UrIXmWGDfds5GxkI+Asjx30yFtLIuHFu/h4="
|
||||
"version": "8.0.19",
|
||||
"hash": "sha256-R86Kqzi3FUuPZlgj3zNOObLAvXtnGrS2mxsBAxWIZrY="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Host.linux-x64",
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-AGnEGHcO2hfvChG3xEGOTA6dX4MiYPB7FoBkmWz3dc8="
|
||||
"version": "8.0.19",
|
||||
"hash": "sha256-a9t/bX+WIKOu9q2R52b/hPGwOpkAgpYuP42SW2QXTak="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Host.osx-arm64",
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-fpMzkOWaA3OFNtHsqOk9s9xKVrcrqOyKHxE7jk8hebg="
|
||||
"version": "8.0.19",
|
||||
"hash": "sha256-VaeGPR+6ApGNtQpEaky2rdUKd4X/Pp3xFGaSgUfGNiE="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Host.osx-x64",
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-Hrn01x+S+gnGEEHhr6mN6bPyqVAhp5u3CqgWwQbh4To="
|
||||
"version": "8.0.19",
|
||||
"hash": "sha256-hw7WMpTq7o544uSNvWUCIr6IRt5xZOo+eERMnwAbYyk="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Ref",
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-tKawpjkMjV0ysNIWWrgHTiLxncZJDRNiDkQBwl255l4="
|
||||
"version": "8.0.19",
|
||||
"hash": "sha256-4ymel0R1c0HrX0plAWubJPzev52y0Fsx1esyQ1R7bXc="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Runtime.linux-arm64",
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-FutphE4bEjd8s6ZqpFXrD1zuCDkNCJ7Vnl0pBm86HBA="
|
||||
"version": "8.0.19",
|
||||
"hash": "sha256-hqhpd8yT8bv05DhFTuMhfsaSISpLs3t4oM+R/ZkJH80="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Runtime.linux-x64",
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-6YVEXiJ3b2gZAYri8iSRBdi/J+0DEl7FcwBX6h1Unkg="
|
||||
"version": "8.0.19",
|
||||
"hash": "sha256-Ou51zUFTPESAAzP/z0+sLDAAXC54+oDlESBBT12M2lM="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Runtime.osx-arm64",
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-J3dfDial8GHyKQMFuBNFtOMD/mOK58vjrK2ZtrYObZg="
|
||||
"version": "8.0.19",
|
||||
"hash": "sha256-IC/e8AmT9twcXwzFmXAelf4ctMbg4ancKPGrPLFMNn8="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Runtime.osx-x64",
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-WnkJyhSBHMw/VtLHWy0AFwzzkbIC1YQugFuj3Adg+Ks="
|
||||
"version": "8.0.19",
|
||||
"hash": "sha256-Rb0z0PT/FHyk/Fgjj9W3WDpkDMKJoXR9DgHB1cJeZSA="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.Platforms",
|
||||
@@ -171,33 +171,38 @@
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Testing.Extensions.Telemetry",
|
||||
"version": "1.5.3",
|
||||
"hash": "sha256-bIXwPSa3jkr2b6xINOqMUs6/uj/r4oVFM7xq3uVIZDU="
|
||||
"version": "1.7.3",
|
||||
"hash": "sha256-Z6WsY2FCUbNnT5HJd7IOrfOvqknVXp6PWzTVeb0idVg="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Testing.Extensions.TrxReport.Abstractions",
|
||||
"version": "1.5.3",
|
||||
"hash": "sha256-IfMRfcyaIKEMRtx326ICKtinDBEfGw/Sv8ZHawJ96Yc="
|
||||
"version": "1.7.3",
|
||||
"hash": "sha256-PTee04FHyTHx/gF5NLckXuVje807G51MzkPrZ1gkgCw="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Testing.Extensions.VSTestBridge",
|
||||
"version": "1.5.3",
|
||||
"hash": "sha256-XpM/yFjhLSsuzyDV+xKubs4V1zVVYiV05E0+N4S1h0g="
|
||||
"version": "1.7.3",
|
||||
"hash": "sha256-8d+wZmucfSO7PsviHjVxYB4q6NcjgxvnCUpLePq35sM="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Testing.Platform",
|
||||
"version": "1.5.3",
|
||||
"hash": "sha256-y61Iih6w5D79dmrj2V675mcaeIiHoj1HSa1FRit2BLM="
|
||||
"version": "1.7.3",
|
||||
"hash": "sha256-cavX11P5o9rooqC3ZHw5h002OKRg2ZNR/VaRwpNTQYA="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Testing.Platform.MSBuild",
|
||||
"version": "1.5.3",
|
||||
"hash": "sha256-YspvjE5Jfi587TAfsvfDVJXNrFOkx1B3y1CKV6m7YLY="
|
||||
"version": "1.7.3",
|
||||
"hash": "sha256-cREl529UQ/c5atT8KimMgrgNdy6MrAd0sBGT8sXRRPM="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.TestPlatform.AdapterUtilities",
|
||||
"version": "17.13.0",
|
||||
"hash": "sha256-Vr+3Tad/h/nk7f/5HMExn3HvCGFCarehFAzJSfCBaOc="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.TestPlatform.ObjectModel",
|
||||
"version": "17.12.0",
|
||||
"hash": "sha256-3XBHBSuCxggAIlHXmKNQNlPqMqwFlM952Av6RrLw1/w="
|
||||
"version": "17.13.0",
|
||||
"hash": "sha256-6S0fjfj8vA+h6dJVNwLi6oZhYDO/I/6hBZaq2VTW+Uk="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.TestPlatform.ObjectModel",
|
||||
@@ -231,13 +236,13 @@
|
||||
},
|
||||
{
|
||||
"pname": "NUnit",
|
||||
"version": "4.3.2",
|
||||
"hash": "sha256-0RWe8uFoxYp6qhPlDDEghOMcKJgyw2ybvEoAqBLebeE="
|
||||
"version": "4.4.0",
|
||||
"hash": "sha256-5geF5QOF+X/WkuCEgkPVKH4AdKx4U0olpU07S8+G3nU="
|
||||
},
|
||||
{
|
||||
"pname": "NUnit3TestAdapter",
|
||||
"version": "5.0.0",
|
||||
"hash": "sha256-7jZM4qAbIzne3AcdFfMbvbgogqpxvVe6q2S7Ls8xQy0="
|
||||
"version": "5.1.0",
|
||||
"hash": "sha256-5z470sFjV67wGHaw8KfmSloIAYe81Dokp0f8I6zXsDc="
|
||||
},
|
||||
{
|
||||
"pname": "runtime.any.System.Runtime",
|
||||
|
Reference in New Issue
Block a user