59 Commits

Author SHA1 Message Date
Smaug123
a9b2598525 Progress 2025-08-24 23:47:05 +01:00
Smaug123
e05abbb724 Rejig assembly load 2025-08-24 23:20:14 +01:00
Smaug123
55677a507b Revert 2025-08-24 20:43:06 +01:00
Smaug123
3930cc5fbb More 2025-08-24 20:40:52 +01:00
Smaug123
735449d4df Revert 2025-08-24 20:40:32 +01:00
Smaug123
6c73f14a4e Merge branch 'main' into generic-edge-cases 2025-08-24 20:39:27 +01:00
Patrick Stevens
4de0dbd816 Add another test and put real-version first (#116) 2025-08-24 19:38:43 +00:00
Smaug123
5bd0f779c5 Merge branch 'main' into generic-edge-cases 2025-08-24 20:27:48 +01:00
Smaug123
4e8c852b56 Merge branch 'main' into generic-edge-cases 2025-08-24 20:27:39 +01:00
Patrick Stevens
91aff34d1e Fix a TODO (#115) 2025-08-24 19:27:38 +00:00
Patrick Stevens
f9e186ba8f Initobj (#114) 2025-08-24 19:23:50 +00:00
Smaug123
1b1b00b4dd Merge branch 'main' into generic-edge-cases 2025-08-24 10:54:23 +01:00
Patrick Stevens
622d0782ae Add field pointer handle (#113) 2025-08-24 09:44:57 +00:00
Smaug123
6e98652691 Revert 2025-08-24 10:07:21 +01:00
Smaug123
ec5f6ed752 Merge branch 'main' into generic-edge-cases 2025-08-24 10:06:32 +01:00
Patrick Stevens
3e4b0a7b7e Plumb through field offset info (#112) 2025-08-24 09:05:31 +00:00
Patrick Stevens
5f35c7a7cd ConcreteChar matcher (#111) 2025-08-24 08:13:06 +00:00
Smaug123
99e46b3756 more 2025-08-24 09:08:52 +01:00
Smaug123
35128afab4 Merge commit '9afc7efea128e46f9cd1a00adcce8e7e50aa7418' into generic-edge-cases 2025-08-24 09:06:52 +01:00
Smaug123
57b0e545c9 Merge commit '2190f148e13ed6aab310c2e867f178c7a551ec1e' into generic-edge-cases 2025-08-23 22:53:34 +01:00
Smaug123
f1db433711 Merge commit 'e2e3d5c3bf7c07fcbf89c0c69d04273aa20b0d2f' into generic-edge-cases 2025-08-23 22:53:30 +01:00
Smaug123
64339bf4ee Merge commit '92f22cff42e73c533f0d9c28e37991046a8008d7' into generic-edge-cases 2025-08-23 22:52:32 +01:00
Smaug123
2e8e6f3919 Merge commit '3bdfeaf8a1cb802ce4dce2cd12d6abd639edc75e' into generic-edge-cases 2025-08-23 22:51:41 +01:00
Smaug123
02ae05893b Merge commit '174e415c70843d905a6d40790cbdfdcf0f21cbdb' into generic-edge-cases 2025-08-23 22:51:31 +01:00
Smaug123
4b7e2ac1e6 Merge commit 'cfd67166162b7c28a6e044e5904cac53da2a194d' into generic-edge-cases 2025-08-23 22:50:11 +01:00
Smaug123
027a49c51a Merge commit 'd711d6fff5c59378a601a6366e01354cd2a43d88' into generic-edge-cases 2025-08-23 22:48:56 +01:00
Patrick Stevens
9afc7efea1 Interface dispatch (#100) 2025-08-23 21:43:57 +00:00
Patrick Stevens
2190f148e1 Test for interface dispatch (#109) 2025-08-23 17:50:07 +00:00
Patrick Stevens
e2e3d5c3bf Tidy up a bit (#108) 2025-08-23 17:41:12 +00:00
Patrick Stevens
92f22cff42 Plumb generic metadata through (#107) 2025-08-23 15:18:05 +01:00
Patrick Stevens
3bdfeaf8a1 Delete spare Program.fs (#106) 2025-08-22 19:20:54 +00:00
Patrick Stevens
5c14baec9f Store interface implementations (#105) 2025-08-22 19:17:39 +00:00
Patrick Stevens
174e415c70 Add constraints to generics (#104) 2025-08-22 19:14:19 +00:00
Patrick Stevens
a531531aef More concrete active patterns (#103) 2025-08-22 19:11:02 +00:00
Patrick Stevens
4c64dd7eb5 Implement Bne_un_s (#102) 2025-08-22 13:37:56 +00:00
Patrick Stevens
cfd6716616 Switch back to NUnit (#101) 2025-08-22 13:34:36 +00:00
Smaug123
f020d560a6 WIP 2025-08-18 23:08:15 +01:00
Smaug123
2e8245d341 WIP 2025-08-15 14:11:48 +01:00
Smaug123
fca9a6dc47 WIP 2025-08-14 08:31:32 +01:00
Smaug123
cc14fb0edd Start initobj 2025-08-12 08:39:25 +01:00
Smaug123
1dbd4b008b resolveTypeFromDefnConcrete 2025-08-12 08:15:38 +01:00
Smaug123
064deee8d5 WIP 2025-08-11 22:12:37 +01:00
Smaug123
407c37a5fb WIP 2025-08-11 21:21:12 +01:00
Smaug123
59fd8a23b7 WIP 2025-08-11 08:18:13 +01:00
Patrick Stevens
d711d6fff5 Check types of JIT calls (#97) 2025-08-10 22:32:23 +00:00
Smaug123
bdedea098a Merge branch 'main' into generic-edge-cases 2025-08-10 23:29:28 +01:00
Patrick Stevens
6dbe6614d5 Remove local variable checks in tests (#98) 2025-08-10 22:29:02 +00:00
Smaug123
6f48c89ef3 More 2025-08-10 23:22:04 +01:00
Smaug123
07c5e931e4 Feature parity 2025-08-10 23:03:23 +01:00
Smaug123
e0e954b131 Remove another spare test 2025-08-10 22:52:45 +01:00
Smaug123
95f422efa9 Revert example 2025-08-10 22:50:44 +01:00
Smaug123
e89fac2780 Fix a couple of TODOs 2025-08-10 22:50:26 +01:00
Smaug123
9bafd0f4b0 WIP 2025-08-02 20:57:53 +01:00
Patrick Stevens
d8b7e84f6c Implement bgt_s (#95) 2025-07-05 23:32:12 +01:00
Patrick Stevens
3af1f2cc97 Implement Ldtoken for fields (#94) 2025-07-05 22:21:58 +01:00
Patrick Stevens
56d1cf63d6 Remove weird duplicate ConcreteType thing (#93) 2025-07-04 18:43:45 +00:00
Patrick Stevens
de1eefb436 Implement Sizeof (#92) 2025-07-04 18:08:56 +00:00
Patrick Stevens
30b8ec990f Split up IlMachineState (#91) 2025-07-03 08:51:48 +01:00
Patrick Stevens
2efe9803da Switch to Expecto (#89) 2025-07-02 23:14:29 +00:00
75 changed files with 6204 additions and 2821 deletions

View File

@@ -42,8 +42,13 @@ dotnet fantomas .
``` ```
### Running the Application ### 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 ```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 ## Architecture
@@ -62,10 +67,13 @@ dotnet publish --self-contained --runtime-id osx-arm64 CSharpExample/ && dotnet
- `Corelib.fs`: Core library type definitions (String, Array, etc.) - `Corelib.fs`: Core library type definitions (String, Array, etc.)
**WoofWare.PawPrint.Test** **WoofWare.PawPrint.Test**
- Uses NUnit as the test framework - Uses Expecto as the test framework
- Test cases are defined in `TestCases.fs` - Test cases are defined in `TestPureCases.fs` and `TestImpureCases.fs`
- C# source files in `sources/` are compiled and executed by the runtime as test cases - C# source files in `sources{Pure,Impure}/` are compiled and executed by the runtime as test cases
- `TestHarness.fs` provides infrastructure for running test assemblies through the interpreter - `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`
- Pending test definitions must be moved into the non-pending test case list before they can be run.
**WoofWare.PawPrint.App** **WoofWare.PawPrint.App**
- Entry point application for running the interpreter - Entry point application for running the interpreter
@@ -90,8 +98,8 @@ dotnet publish --self-contained --runtime-id osx-arm64 CSharpExample/ && dotnet
When adding new IL instruction support: When adding new IL instruction support:
1. Add the instruction to `IlOp.fs` 1. Add the instruction to `IlOp.fs`
2. Implement execution logic in `AbstractMachine.fs` 2. Implement execution logic in `AbstractMachine.fs`
3. Add a test case in `sources/` (C# file) that exercises the instruction 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 `TestCases.fs` 4. Add the test case to `TestPureCases.fs` or `TestImpureCases.fs`
5. Run tests to verify implementation 5. Run tests to verify implementation
The project uses deterministic builds and treats warnings as errors to maintain code quality. The project uses deterministic builds and treats warnings as errors to maintain code quality.
@@ -174,3 +182,47 @@ When encountering errors:
4. Add logging to see generic contexts: `failwithf "Failed to concretize: %A" typeDefn` 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 5. Check if you're in a generic method calling another generic method
6. Verify TypeRefs are being resolved in the correct assembly 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))
```

View File

@@ -6,6 +6,11 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<WarningsAsErrors>false</WarningsAsErrors> <WarningsAsErrors>false</WarningsAsErrors>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors> <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Include="Class1.cs" />
</ItemGroup>
</Project> </Project>

View File

@@ -45,10 +45,7 @@ type DumpedAssembly =
/// Dictionary of all type definitions in this assembly, keyed by their handle. /// Dictionary of all type definitions in this assembly, keyed by their handle.
/// </summary> /// </summary>
TypeDefs : TypeDefs :
IReadOnlyDictionary< IReadOnlyDictionary<TypeDefinitionHandle, WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn>>
TypeDefinitionHandle,
WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
>
/// <summary> /// <summary>
/// Dictionary of all type references in this assembly, keyed by their handle. /// Dictionary of all type references in this assembly, keyed by their handle.
@@ -67,7 +64,7 @@ type DumpedAssembly =
Methods : Methods :
IReadOnlyDictionary< IReadOnlyDictionary<
MethodDefinitionHandle, MethodDefinitionHandle,
WoofWare.PawPrint.MethodInfo<FakeUnit, WoofWare.PawPrint.GenericParameter, TypeDefn> WoofWare.PawPrint.MethodInfo<GenericParamFromMetadata, GenericParamFromMetadata, TypeDefn>
> >
/// <summary> /// <summary>
@@ -78,7 +75,8 @@ type DumpedAssembly =
/// <summary> /// <summary>
/// Dictionary of all field definitions in this assembly, keyed by their handle. /// Dictionary of all field definitions in this assembly, keyed by their handle.
/// </summary> /// </summary>
Fields : IReadOnlyDictionary<FieldDefinitionHandle, WoofWare.PawPrint.FieldInfo<FakeUnit, TypeDefn>> Fields :
IReadOnlyDictionary<FieldDefinitionHandle, WoofWare.PawPrint.FieldInfo<GenericParamFromMetadata, TypeDefn>>
/// <summary> /// <summary>
/// The entry point method of the assembly, if one exists. /// 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. /// Internal lookup for type definitions by namespace and name.
/// </summary> /// </summary>
_TypeDefsLookup : _TypeDefsLookup :
ImmutableDictionary< ImmutableDictionary<string * string, WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn>>
string * string,
WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
>
} }
static member internal BuildExportedTypesLookup static member internal BuildExportedTypesLookup
@@ -205,7 +200,7 @@ type DumpedAssembly =
static member internal BuildTypeDefsLookup static member internal BuildTypeDefsLookup
(logger : ILogger) (logger : ILogger)
(name : AssemblyName) (name : AssemblyName)
(typeDefs : WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> seq) (typeDefs : WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn> seq)
= =
let result = ImmutableDictionary.CreateBuilder () let result = ImmutableDictionary.CreateBuilder ()
let keys = HashSet () let keys = HashSet ()
@@ -236,7 +231,7 @@ type DumpedAssembly =
member this.TypeDef member this.TypeDef
(``namespace`` : string) (``namespace`` : string)
(name : string) (name : string)
: WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> option : WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn> option
= =
match this._TypeDefsLookup.TryGetValue ((``namespace``, name)) with match this._TypeDefsLookup.TryGetValue ((``namespace``, name)) with
| false, _ -> None | false, _ -> None
@@ -428,8 +423,8 @@ module Assembly =
let rec resolveTypeRef let rec resolveTypeRef
(assemblies : ImmutableDictionary<string, DumpedAssembly>) (assemblies : ImmutableDictionary<string, DumpedAssembly>)
(referencedInAssembly : DumpedAssembly) (referencedInAssembly : DumpedAssembly)
(target : TypeRef)
(genericArgs : ImmutableArray<TypeDefn>) (genericArgs : ImmutableArray<TypeDefn>)
(target : TypeRef)
: TypeResolutionResult : TypeResolutionResult
= =
match target.ResolutionScope with match target.ResolutionScope with
@@ -467,7 +462,8 @@ module Assembly =
match targetType with match targetType with
| [ t ] -> | [ 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) TypeResolutionResult.Resolved (assy, t)
| _ :: _ :: _ -> failwith $"Multiple matching type definitions! {nsPath} {target.Name}" | _ :: _ :: _ -> failwith $"Multiple matching type definitions! {nsPath} {target.Name}"
@@ -493,13 +489,13 @@ module Assembly =
| Some typeDef -> | Some typeDef ->
let typeDef = let typeDef =
typeDef typeDef
|> TypeInfo.mapGeneric (fun _ param -> genericArgs.[param.SequenceNumber]) |> TypeInfo.mapGeneric (fun (param, md) -> genericArgs.[param.SequenceNumber])
TypeResolutionResult.Resolved (assy, typeDef) TypeResolutionResult.Resolved (assy, typeDef)
| None -> | None ->
match assy.TypeRef ns name with match assy.TypeRef ns name with
| Some typeRef -> resolveTypeRef assemblies assy typeRef genericArgs | Some typeRef -> resolveTypeRef assemblies assy genericArgs typeRef
| None -> | None ->
match assy.ExportedType (Some ns) name with match assy.ExportedType (Some ns) name with
@@ -536,7 +532,7 @@ module DumpedAssembly =
| Some (BaseTypeInfo.TypeRef r) -> | Some (BaseTypeInfo.TypeRef r) ->
let assy = loadedAssemblies.[source.FullName] let assy = loadedAssemblies.[source.FullName]
// TODO: generics // 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 _ -> | TypeResolutionResult.FirstLoadAssy _ ->
failwith failwith
"seems pretty unlikely that we could have constructed this object without loading its base type" "seems pretty unlikely that we could have constructed this object without loading its base type"
@@ -564,3 +560,35 @@ module DumpedAssembly =
| None -> ResolvedBaseType.Object | None -> ResolvedBaseType.Object
go source baseTypeInfo go source baseTypeInfo
let typeInfoToTypeDefn
(bct : BaseClassTypes<DumpedAssembly>)
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
(ti : TypeInfo<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

View 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

View File

@@ -11,7 +11,7 @@ type ComparableTypeDefinitionHandle =
_Inner : TypeDefinitionHandle _Inner : TypeDefinitionHandle
} }
override this.Equals (other) = override this.Equals other =
match other with match other with
| :? ComparableTypeDefinitionHandle as other -> this._Inner.GetHashCode () = other._Inner.GetHashCode () | :? ComparableTypeDefinitionHandle as other -> this._Inner.GetHashCode () = other._Inner.GetHashCode ()
| _ -> false | _ -> false

View File

@@ -1,6 +1,6 @@
namespace WoofWare.PawPrint namespace WoofWare.PawPrint
open System open System.Collections.Immutable
open System.Reflection open System.Reflection
open System.Reflection.Metadata open System.Reflection.Metadata
@@ -16,8 +16,8 @@ module FakeUnit =
/// A type which has been concretised, runtime-representable, etc. /// A type which has been concretised, runtime-representable, etc.
[<CustomEquality>] [<CustomEquality>]
[<CustomComparison>] [<NoComparison>]
type ConcreteType<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :> IComparable<'typeGeneric>> = type ConcreteType<'typeGeneric> =
private private
{ {
/// Do not use this, because it's intended to be private; use the accessor `.Assembly : AssemblyName` /// Do not use this, because it's intended to be private; use the accessor `.Assembly : AssemblyName`
@@ -30,12 +30,12 @@ type ConcreteType<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :
/// Do not use this, because it's intended to be private; use the accessor `.Namespace` instead. /// Do not use this, because it's intended to be private; use the accessor `.Namespace` instead.
_Namespace : string _Namespace : string
/// Do not use this, because it's intended to be private; use the accessor `.Generics` instead. /// Do not use this, because it's intended to be private; use the accessor `.Generics` instead.
_Generics : 'typeGeneric list _Generics : ImmutableArray<'typeGeneric>
} }
member this.Assembly : AssemblyName = this._AssemblyName member this.Assembly : AssemblyName = this._AssemblyName
member this.Definition : ComparableTypeDefinitionHandle = this._Definition 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.Name = this._Name
member this.Namespace = this._Namespace member this.Namespace = this._Namespace
@@ -50,72 +50,26 @@ type ConcreteType<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :
override this.GetHashCode () : int = override this.GetHashCode () : int =
hash (this._AssemblyName.FullName, this._Definition, this._Generics) 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>] [<RequireQualifiedAccess>]
module ConcreteType = module ConcreteType =
let make let make
(assemblyName : AssemblyName) (assemblyName : AssemblyName)
(defn : TypeDefinitionHandle)
(ns : string) (ns : string)
(name : string) (name : string)
(defn : TypeDefinitionHandle) (genericParam : ImmutableArray<GenericParamFromMetadata>)
(generics : TypeDefn list) : ConcreteType<GenericParamFromMetadata>
: ConcreteType<TypeDefn>
= =
{ {
_AssemblyName = assemblyName _AssemblyName = assemblyName
_Definition = ComparableTypeDefinitionHandle.Make defn _Definition = ComparableTypeDefinitionHandle.Make defn
_Name = name _Name = name
_Namespace = ns _Namespace = ns
_Generics = generics _Generics = genericParam
} }
let make' let mapGeneric<'a, 'b> (f : int -> 'a -> 'b) (x : ConcreteType<'a>) : ConcreteType<'b> =
(assemblyName : AssemblyName) let generics = x._Generics |> Seq.mapi f |> ImmutableArray.CreateRange
(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
{ {
_AssemblyName = x._AssemblyName _AssemblyName = x._AssemblyName

View File

@@ -1,6 +1,5 @@
namespace WoofWare.PawPrint namespace WoofWare.PawPrint
open System
open System.Reflection open System.Reflection
open System.Reflection.Metadata open System.Reflection.Metadata
@@ -8,8 +7,7 @@ open System.Reflection.Metadata
/// Represents detailed information about a field in a .NET assembly. /// Represents detailed information about a field in a .NET assembly.
/// This is a strongly-typed representation of FieldDefinition from System.Reflection.Metadata. /// This is a strongly-typed representation of FieldDefinition from System.Reflection.Metadata.
/// </summary> /// </summary>
type FieldInfo<'typeGeneric, 'fieldGeneric when 'typeGeneric : comparison and 'typeGeneric :> IComparable<'typeGeneric>> type FieldInfo<'typeGeneric, 'fieldGeneric> =
=
{ {
/// <summary> /// <summary>
/// The metadata token handle that uniquely identifies this field in the assembly. /// 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. /// literal, and other characteristics.
/// </summary> /// </summary>
Attributes : FieldAttributes Attributes : FieldAttributes
/// Static fields don't have an offset at all; also, instance fields which don't have an explicit offset (but
/// which of course do have one implicitly, which is most fields) are None here.
Offset : int option
} }
member this.HasFieldRVA = this.Attributes.HasFlag FieldAttributes.HasFieldRVA
member this.IsStatic = this.Attributes.HasFlag FieldAttributes.Static
override this.ToString () : string = override this.ToString () : string =
$"%s{this.DeclaringType.Assembly.Name}.{this.DeclaringType.Name}.%s{this.Name}" $"%s{this.DeclaringType.Assembly.Name}.{this.DeclaringType.Name}.%s{this.Name}"
@@ -46,18 +51,27 @@ module FieldInfo =
(assembly : AssemblyName) (assembly : AssemblyName)
(handle : FieldDefinitionHandle) (handle : FieldDefinitionHandle)
(def : FieldDefinition) (def : FieldDefinition)
: FieldInfo<FakeUnit, TypeDefn> : FieldInfo<GenericParamFromMetadata, TypeDefn>
= =
let name = mr.GetString def.Name let name = mr.GetString def.Name
let fieldSig = def.DecodeSignature (TypeDefn.typeProvider assembly, ()) let fieldSig = def.DecodeSignature (TypeDefn.typeProvider assembly, ())
let declaringType = def.GetDeclaringType () let declaringType = def.GetDeclaringType ()
let typeGenerics = mr.GetTypeDefinition(declaringType).GetGenericParameters().Count
let decType = mr.GetTypeDefinition (declaringType) let typeGenerics =
mr.GetTypeDefinition(declaringType).GetGenericParameters ()
|> GenericParameter.readAll mr
let decType = mr.GetTypeDefinition declaringType
let declaringTypeNamespace = mr.GetString decType.Namespace let declaringTypeNamespace = mr.GetString decType.Namespace
let declaringTypeName = mr.GetString decType.Name let declaringTypeName = mr.GetString decType.Name
let declaringType = let declaringType =
ConcreteType.make' assembly declaringType declaringTypeNamespace declaringTypeName typeGenerics ConcreteType.make assembly declaringType declaringTypeNamespace declaringTypeName typeGenerics
let offset =
match def.GetOffset () with
| -1 -> None
| s -> Some s
{ {
Name = name Name = name
@@ -65,14 +79,10 @@ module FieldInfo =
DeclaringType = declaringType DeclaringType = declaringType
Handle = handle Handle = handle
Attributes = def.Attributes Attributes = def.Attributes
Offset = offset
} }
let mapTypeGenerics<'a, 'b, 'field let mapTypeGenerics<'a, 'b, 'field> (f : int -> 'a -> 'b) (input : FieldInfo<'a, 'field>) : FieldInfo<'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 declaringType = input.DeclaringType |> ConcreteType.mapGeneric f let declaringType = input.DeclaringType |> ConcreteType.mapGeneric f
{ {
@@ -81,5 +91,5 @@ module FieldInfo =
DeclaringType = declaringType DeclaringType = declaringType
Signature = input.Signature Signature = input.Signature
Attributes = input.Attributes Attributes = input.Attributes
Offset = input.Offset
} }

View 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&lt;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

View 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 ()

View File

@@ -52,40 +52,6 @@ module Parameter =
result.ToImmutable () 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&lt;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 = type ExceptionOffset =
{ {
TryLength : int TryLength : int
@@ -168,8 +134,7 @@ module MethodInstructions =
/// Represents detailed information about a method in a .NET assembly. /// Represents detailed information about a method in a .NET assembly.
/// This is a strongly-typed representation of MethodDefinition from System.Reflection.Metadata. /// This is a strongly-typed representation of MethodDefinition from System.Reflection.Metadata.
/// </summary> /// </summary>
type MethodInfo<'typeGenerics, 'methodGenerics, 'methodVars type MethodInfo<'typeGenerics, 'methodGenerics, 'methodVars> =
when 'typeGenerics :> IComparable<'typeGenerics> and 'typeGenerics : comparison> =
{ {
/// <summary> /// <summary>
/// The type that declares this method, along with its assembly information. /// The type that declares this method, along with its assembly information.
@@ -267,14 +232,13 @@ module MethodInfo =
| con -> failwith $"TODO: {con}" | con -> failwith $"TODO: {con}"
) )
let mapTypeGenerics<'a, 'b, 'methodGen, 'vars let mapTypeGenerics<'a, 'b, 'methodGen, 'vars>
when 'a :> IComparable<'a> and 'a : comparison and 'b : comparison and 'b :> IComparable<'b>> (f : 'a -> 'b)
(f : int -> 'a -> 'b)
(m : MethodInfo<'a, 'methodGen, 'vars>) (m : MethodInfo<'a, 'methodGen, 'vars>)
: MethodInfo<'b, 'methodGen, 'vars> : MethodInfo<'b, 'methodGen, 'vars>
= =
{ {
DeclaringType = m.DeclaringType |> ConcreteType.mapGeneric f DeclaringType = m.DeclaringType |> ConcreteType.mapGeneric (fun _ -> f)
Handle = m.Handle Handle = m.Handle
Name = m.Name Name = m.Name
Instructions = m.Instructions Instructions = m.Instructions
@@ -288,18 +252,20 @@ module MethodInfo =
IsStatic = m.IsStatic 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) (f : int -> 'a -> 'b)
(m : MethodInfo<'typeGen, 'a, 'vars>) (m : MethodInfo<'typeGen, 'a, 'vars>)
: MethodInfo<'typeGen, 'b, 'vars> : MethodInfo<'typeGen, 'b, 'vars>
= =
let generics = m.Generics |> Seq.mapi f |> ImmutableArray.CreateRange
{ {
DeclaringType = m.DeclaringType DeclaringType = m.DeclaringType
Handle = m.Handle Handle = m.Handle
Name = m.Name Name = m.Name
Instructions = m.Instructions Instructions = m.Instructions
Parameters = m.Parameters Parameters = m.Parameters
Generics = m.Generics |> Seq.mapi f |> ImmutableArray.CreateRange Generics = generics
Signature = m.Signature Signature = m.Signature
RawSignature = m.RawSignature RawSignature = m.RawSignature
CustomAttributes = m.CustomAttributes CustomAttributes = m.CustomAttributes
@@ -676,7 +642,7 @@ module MethodInfo =
(peReader : PEReader) (peReader : PEReader)
(metadataReader : MetadataReader) (metadataReader : MetadataReader)
(methodHandle : MethodDefinitionHandle) (methodHandle : MethodDefinitionHandle)
: MethodInfo<FakeUnit, GenericParameter, TypeDefn> option : MethodInfo<GenericParamFromMetadata, GenericParamFromMetadata, TypeDefn> option
= =
let logger = loggerFactory.CreateLogger "MethodInfo" let logger = loggerFactory.CreateLogger "MethodInfo"
let assemblyName = metadataReader.GetAssemblyDefinition().GetAssemblyName () let assemblyName = metadataReader.GetAssemblyDefinition().GetAssemblyName ()
@@ -717,7 +683,8 @@ module MethodInfo =
let declaringTypeName = metadataReader.GetString declaringDefn.Name let declaringTypeName = metadataReader.GetString declaringDefn.Name
let declaringTypeGenericParams = let declaringTypeGenericParams =
metadataReader.GetTypeDefinition(declaringType).GetGenericParameters().Count metadataReader.GetTypeDefinition(declaringType).GetGenericParameters ()
|> GenericParameter.readAll metadataReader
let attrs = let attrs =
let result = ImmutableArray.CreateBuilder () let result = ImmutableArray.CreateBuilder ()
@@ -738,7 +705,7 @@ module MethodInfo =
GenericParameter.readAll metadataReader (methodDef.GetGenericParameters ()) GenericParameter.readAll metadataReader (methodDef.GetGenericParameters ())
let declaringType = let declaringType =
ConcreteType.make' ConcreteType.make
assemblyName assemblyName
declaringType declaringType
declaringTypeNamespace declaringTypeNamespace

View File

@@ -1,5 +1,6 @@
namespace WoofWare.PawPrint namespace WoofWare.PawPrint
open System
open System.Collections.Immutable open System.Collections.Immutable
open System.Reflection open System.Reflection
open System.Reflection.Metadata open System.Reflection.Metadata
@@ -9,6 +10,12 @@ type ConcreteTypeHandle =
| Byref of ConcreteTypeHandle | Byref of ConcreteTypeHandle
| Pointer 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 = type AllConcreteTypes =
{ {
Mapping : Map<int, ConcreteType<ConcreteTypeHandle>> Mapping : Map<int, ConcreteType<ConcreteTypeHandle>>
@@ -46,7 +53,7 @@ module AllConcreteTypes =
let findExistingConcreteType let findExistingConcreteType
(concreteTypes : AllConcreteTypes) (concreteTypes : AllConcreteTypes)
(asm : AssemblyName, ns : string, name : string, generics : ConcreteTypeHandle list as key) (asm : AssemblyName, ns : string, name : string, generics : ConcreteTypeHandle ImmutableArray)
: ConcreteTypeHandle option : ConcreteTypeHandle option
= =
concreteTypes.Mapping concreteTypes.Mapping
@@ -75,10 +82,266 @@ module AllConcreteTypes =
toRet, newState 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 (|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 (|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>] [<RequireQualifiedAccess>]
module TypeConcretization = module TypeConcretization =
type ConcretizationContext = type ConcretizationContext<'corelib> =
{ {
/// Types currently being processed (to detect cycles) /// Types currently being processed (to detect cycles)
InProgress : ImmutableDictionary<AssemblyName * TypeDefn, ConcreteTypeHandle> InProgress : ImmutableDictionary<AssemblyName * TypeDefn, ConcreteTypeHandle>
@@ -86,7 +349,7 @@ module TypeConcretization =
ConcreteTypes : AllConcreteTypes ConcreteTypes : AllConcreteTypes
/// For resolving type references /// For resolving type references
LoadedAssemblies : ImmutableDictionary<string, DumpedAssembly> LoadedAssemblies : ImmutableDictionary<string, DumpedAssembly>
BaseTypes : BaseClassTypes<DumpedAssembly> BaseTypes : BaseClassTypes<'corelib>
} }
// Helper function to find existing types by assembly, namespace, name, and generics // Helper function to find existing types by assembly, namespace, name, and generics
@@ -95,7 +358,7 @@ module TypeConcretization =
(assembly : AssemblyName) (assembly : AssemblyName)
(ns : string) (ns : string)
(name : string) (name : string)
(generics : ConcreteTypeHandle list) (generics : ConcreteTypeHandle ImmutableArray)
: ConcreteTypeHandle option : ConcreteTypeHandle option
= =
concreteTypes.Mapping concreteTypes.Mapping
@@ -117,18 +380,18 @@ module TypeConcretization =
(key : AssemblyName * string * string) (key : AssemblyName * string * string)
: ConcreteTypeHandle option : ConcreteTypeHandle option
= =
let (asm, ns, name) = key let asm, ns, name = key
findExistingType concreteTypes asm ns name [] findExistingType concreteTypes asm ns name ImmutableArray.Empty
// Helper function to create and add a ConcreteType to the context // Helper function to create and add a ConcreteType to the context
let private createAndAddConcreteType let private createAndAddConcreteType
(ctx : ConcretizationContext) (ctx : ConcretizationContext<'corelib>)
(assembly : AssemblyName) (assembly : AssemblyName)
(definition : ComparableTypeDefinitionHandle) (definition : ComparableTypeDefinitionHandle)
(ns : string) (ns : string)
(name : string) (name : string)
(generics : ConcreteTypeHandle list) (generics : ConcreteTypeHandle ImmutableArray)
: ConcreteTypeHandle * ConcretizationContext : ConcreteTypeHandle * ConcretizationContext<'corelib>
= =
let concreteType = let concreteType =
{ {
@@ -150,12 +413,11 @@ module TypeConcretization =
// Helper function for assembly loading with retry pattern // Helper function for assembly loading with retry pattern
let private loadAssemblyAndResolveTypeRef let private loadAssemblyAndResolveTypeRef
(loadAssembly : (loadAssembly : IAssemblyLoad)
AssemblyName -> AssemblyReferenceHandle -> ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly) (ctx : ConcretizationContext<'corelib>)
(ctx : ConcretizationContext)
(currentAssembly : AssemblyName) (currentAssembly : AssemblyName)
(typeRef : TypeRef) (typeRef : TypeRef)
: (DumpedAssembly * WoofWare.PawPrint.TypeInfo<_, _>) * ConcretizationContext : (DumpedAssembly * WoofWare.PawPrint.TypeInfo<_, _>) * ConcretizationContext<'corelib>
= =
let currentAssy = let currentAssy =
match ctx.LoadedAssemblies.TryGetValue currentAssembly.FullName with match ctx.LoadedAssemblies.TryGetValue currentAssembly.FullName with
@@ -164,7 +426,7 @@ module TypeConcretization =
// First try to resolve without loading new assemblies // First try to resolve without loading new assemblies
let resolutionResult = let resolutionResult =
Assembly.resolveTypeRef ctx.LoadedAssemblies currentAssy typeRef ImmutableArray.Empty Assembly.resolveTypeRef ctx.LoadedAssemblies currentAssy ImmutableArray.Empty typeRef
match resolutionResult with match resolutionResult with
| TypeResolutionResult.Resolved (targetAssy, typeInfo) -> (targetAssy, typeInfo), ctx | TypeResolutionResult.Resolved (targetAssy, typeInfo) -> (targetAssy, typeInfo), ctx
@@ -172,7 +434,8 @@ module TypeConcretization =
// Need to load the assembly // Need to load the assembly
match typeRef.ResolutionScope with match typeRef.ResolutionScope with
| TypeRefResolutionScope.Assembly assyRef -> | TypeRefResolutionScope.Assembly assyRef ->
let newAssemblies, loadedAssy = loadAssembly currentAssembly assyRef let newAssemblies, _ =
loadAssembly.LoadAssembly ctx.LoadedAssemblies currentAssembly assyRef
let newCtx = let newCtx =
{ ctx with { ctx with
@@ -181,7 +444,7 @@ module TypeConcretization =
// Now try to resolve again with the loaded assembly // Now try to resolve again with the loaded assembly
let resolutionResult2 = let resolutionResult2 =
Assembly.resolveTypeRef newCtx.LoadedAssemblies currentAssy typeRef ImmutableArray.Empty Assembly.resolveTypeRef newCtx.LoadedAssemblies currentAssy ImmutableArray.Empty typeRef
match resolutionResult2 with match resolutionResult2 with
| TypeResolutionResult.Resolved (targetAssy, typeInfo) -> (targetAssy, typeInfo), newCtx | TypeResolutionResult.Resolved (targetAssy, typeInfo) -> (targetAssy, typeInfo), newCtx
@@ -190,9 +453,9 @@ module TypeConcretization =
| _ -> failwith "Unexpected resolution scope" | _ -> failwith "Unexpected resolution scope"
let private concretizePrimitive let private concretizePrimitive
(ctx : ConcretizationContext) (ctx : ConcretizationContext<'corelib>)
(prim : PrimitiveType) (prim : PrimitiveType)
: ConcreteTypeHandle * ConcretizationContext : ConcreteTypeHandle * ConcretizationContext<'corelib>
= =
// Get the TypeInfo for this primitive from BaseClassTypes // Get the TypeInfo for this primitive from BaseClassTypes
@@ -229,13 +492,13 @@ module TypeConcretization =
(ComparableTypeDefinitionHandle.Make typeInfo.TypeDefHandle) (ComparableTypeDefinitionHandle.Make typeInfo.TypeDefHandle)
typeInfo.Namespace typeInfo.Namespace
typeInfo.Name typeInfo.Name
[] // Primitives have no generic parameters ImmutableArray.Empty // Primitives have no generic parameters
let private concretizeArray let private concretizeArray
(ctx : ConcretizationContext) (ctx : ConcretizationContext<'corelib>)
(elementHandle : ConcreteTypeHandle) (elementHandle : ConcreteTypeHandle)
(shape : 'a) (shape : 'a)
: ConcreteTypeHandle * ConcretizationContext : ConcreteTypeHandle * ConcretizationContext<'corelib>
= =
// Arrays are System.Array<T> where T is the element type // Arrays are System.Array<T> where T is the element type
@@ -248,7 +511,7 @@ module TypeConcretization =
arrayTypeInfo.Assembly arrayTypeInfo.Assembly
arrayTypeInfo.Namespace arrayTypeInfo.Namespace
arrayTypeInfo.Name arrayTypeInfo.Name
[ elementHandle ] (ImmutableArray.Create elementHandle)
with with
| Some handle -> handle, ctx | Some handle -> handle, ctx
| None -> | None ->
@@ -259,12 +522,12 @@ module TypeConcretization =
(ComparableTypeDefinitionHandle.Make arrayTypeInfo.TypeDefHandle) (ComparableTypeDefinitionHandle.Make arrayTypeInfo.TypeDefHandle)
arrayTypeInfo.Namespace arrayTypeInfo.Namespace
arrayTypeInfo.Name arrayTypeInfo.Name
[ elementHandle ] // Array<T> has one generic parameter (ImmutableArray.Create elementHandle) // Array<T> has one generic parameter
let private concretizeOneDimArray let private concretizeOneDimArray
(ctx : ConcretizationContext) (ctx : ConcretizationContext<'corelib>)
(elementHandle : ConcreteTypeHandle) (elementHandle : ConcreteTypeHandle)
: ConcreteTypeHandle * ConcretizationContext : ConcreteTypeHandle * ConcretizationContext<'corelib>
= =
// One-dimensional arrays with lower bound 0 are also System.Array<T> // One-dimensional arrays with lower bound 0 are also System.Array<T>
@@ -278,7 +541,7 @@ module TypeConcretization =
arrayTypeInfo.Assembly arrayTypeInfo.Assembly
arrayTypeInfo.Namespace arrayTypeInfo.Namespace
arrayTypeInfo.Name arrayTypeInfo.Name
[ elementHandle ] (ImmutableArray.Create elementHandle)
with with
| Some handle -> handle, ctx | Some handle -> handle, ctx
| None -> | None ->
@@ -289,13 +552,13 @@ module TypeConcretization =
(ComparableTypeDefinitionHandle.Make arrayTypeInfo.TypeDefHandle) (ComparableTypeDefinitionHandle.Make arrayTypeInfo.TypeDefHandle)
arrayTypeInfo.Namespace arrayTypeInfo.Namespace
arrayTypeInfo.Name arrayTypeInfo.Name
[ elementHandle ] // Array<T> has one generic parameter (ImmutableArray.Create elementHandle) // Array<T> has one generic parameter
let concretizeTypeDefinition let concretizeTypeDefinition
(ctx : ConcretizationContext) (ctx : ConcretizationContext<'corelib>)
(assemblyName : AssemblyName) (assemblyName : AssemblyName)
(typeDefHandle : ComparableTypeDefinitionHandle) (typeDefHandle : ComparableTypeDefinitionHandle)
: ConcreteTypeHandle * ConcretizationContext : ConcreteTypeHandle * ConcretizationContext<'corelib>
= =
// Look up the type definition in the assembly // Look up the type definition in the assembly
@@ -315,19 +578,24 @@ module TypeConcretization =
typeInfo.Generics.Length typeInfo.Generics.Length
// Check if we've already concretized this type // 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 | Some handle -> handle, ctx
| None -> | None ->
// Create and add the concrete type (no generic arguments since it's not generic) // 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 let private concretizeTypeReference
(loadAssembly : (loadAssembly : IAssemblyLoad)
AssemblyName -> AssemblyReferenceHandle -> ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly) (ctx : ConcretizationContext<'corelib>)
(ctx : ConcretizationContext)
(currentAssembly : AssemblyName) (currentAssembly : AssemblyName)
(typeRef : TypeRef) (typeRef : TypeRef)
: ConcreteTypeHandle * ConcretizationContext : ConcreteTypeHandle * ConcretizationContext<'corelib>
= =
// Use the helper to load assembly and resolve the type reference // Use the helper to load assembly and resolve the type reference
let (targetAssy, typeInfo), ctx = let (targetAssy, typeInfo), ctx =
@@ -346,14 +614,13 @@ module TypeConcretization =
/// Concretize a type in a specific generic context /// Concretize a type in a specific generic context
let rec concretizeType let rec concretizeType
(ctx : ConcretizationContext) (ctx : ConcretizationContext<DumpedAssembly>)
(loadAssembly : (loadAssembly : IAssemblyLoad)
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
(assembly : AssemblyName) (assembly : AssemblyName)
(typeGenerics : ConcreteTypeHandle ImmutableArray) (typeGenerics : ImmutableArray<ConcreteTypeHandle>)
(methodGenerics : ConcreteTypeHandle ImmutableArray) (methodGenerics : ImmutableArray<ConcreteTypeHandle>)
(typeDefn : TypeDefn) (typeDefn : TypeDefn)
: ConcreteTypeHandle * ConcretizationContext : ConcreteTypeHandle * ConcretizationContext<DumpedAssembly>
= =
let key = (assembly, typeDefn) let key = (assembly, typeDefn)
@@ -382,13 +649,13 @@ module TypeConcretization =
if index < typeGenerics.Length then if index < typeGenerics.Length then
typeGenerics.[index], ctx typeGenerics.[index], ctx
else else
failwithf "Generic type parameter %d out of range" index raise (IndexOutOfRangeException $"Generic type parameter %i{index}")
| TypeDefn.GenericMethodParameter index -> | TypeDefn.GenericMethodParameter index ->
if index < methodGenerics.Length then if index < methodGenerics.Length then
methodGenerics.[index], ctx methodGenerics.[index], ctx
else else
failwithf "Generic method parameter %d out of range" index raise (IndexOutOfRangeException $"Generic method parameter %i{index}")
| TypeDefn.GenericInstantiation (genericDef, args) -> | TypeDefn.GenericInstantiation (genericDef, args) ->
concretizeGenericInstantiation ctx loadAssembly assembly typeGenerics methodGenerics genericDef args concretizeGenericInstantiation ctx loadAssembly assembly typeGenerics methodGenerics genericDef args
@@ -422,7 +689,12 @@ module TypeConcretization =
let voidTypeInfo = ctx.BaseTypes.Void let voidTypeInfo = ctx.BaseTypes.Void
match match
findExistingType ctx.ConcreteTypes voidTypeInfo.Assembly voidTypeInfo.Namespace voidTypeInfo.Name [] findExistingType
ctx.ConcreteTypes
voidTypeInfo.Assembly
voidTypeInfo.Namespace
voidTypeInfo.Name
ImmutableArray.Empty
with with
| Some handle -> handle, ctx | Some handle -> handle, ctx
| None -> | None ->
@@ -433,20 +705,19 @@ module TypeConcretization =
(ComparableTypeDefinitionHandle.Make voidTypeInfo.TypeDefHandle) (ComparableTypeDefinitionHandle.Make voidTypeInfo.TypeDefHandle)
voidTypeInfo.Namespace voidTypeInfo.Namespace
voidTypeInfo.Name voidTypeInfo.Name
[] // Void has no generic parameters ImmutableArray.Empty // Void has no generic parameters
| _ -> failwithf "TODO: Concretization of %A not implemented" typeDefn | _ -> failwithf "TODO: Concretization of %A not implemented" typeDefn
and private concretizeGenericInstantiation and private concretizeGenericInstantiation
(ctx : ConcretizationContext) (ctx : ConcretizationContext<DumpedAssembly>)
(loadAssembly : (loadAssembly : IAssemblyLoad)
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
(assembly : AssemblyName) (assembly : AssemblyName)
(typeGenerics : ConcreteTypeHandle ImmutableArray) (typeGenerics : ImmutableArray<ConcreteTypeHandle>)
(methodGenerics : ConcreteTypeHandle ImmutableArray) (methodGenerics : ImmutableArray<ConcreteTypeHandle>)
(genericDef : TypeDefn) (genericDef : TypeDefn)
(args : ImmutableArray<TypeDefn>) (args : ImmutableArray<TypeDefn>)
: ConcreteTypeHandle * ConcretizationContext : ConcreteTypeHandle * ConcretizationContext<DumpedAssembly>
= =
// First, concretize all type arguments // First, concretize all type arguments
let argHandles, ctxAfterArgs = let argHandles, ctxAfterArgs =
@@ -460,7 +731,7 @@ module TypeConcretization =
) )
([], ctx) ([], ctx)
let argHandles = argHandles |> List.rev let argHandles = argHandles |> Seq.rev |> ImmutableArray.CreateRange
// Get the base type definition // Get the base type definition
let baseAssembly, baseTypeDefHandle, baseNamespace, baseName, ctxAfterArgs = let baseAssembly, baseTypeDefHandle, baseNamespace, baseName, ctxAfterArgs =
@@ -519,7 +790,8 @@ module TypeConcretization =
| false, _ -> | false, _ ->
// Need to load the assembly // Need to load the assembly
let newAssemblies, loadedAssy = loadAssembly assembly assyRef let newAssemblies, loadedAssy =
loadAssembly.LoadAssembly ctx.LoadedAssemblies assembly assyRef
let ctxWithNewAssy = let ctxWithNewAssy =
{ ctxAfterArgs with { ctxAfterArgs with
@@ -597,17 +869,16 @@ module Concretization =
/// Helper to concretize an array of types /// Helper to concretize an array of types
let private concretizeTypeArray let private concretizeTypeArray
(ctx : TypeConcretization.ConcretizationContext) (ctx : TypeConcretization.ConcretizationContext<DumpedAssembly>)
(loadAssembly : (loadAssembly : IAssemblyLoad)
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
(assembly : AssemblyName) (assembly : AssemblyName)
(typeArgs : ConcreteTypeHandle ImmutableArray) (typeArgs : ImmutableArray<ConcreteTypeHandle>)
(methodArgs : ConcreteTypeHandle ImmutableArray) (methodArgs : ImmutableArray<ConcreteTypeHandle>)
(types : ImmutableArray<TypeDefn>) (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 let mutable ctx = ctx
for i = 0 to types.Length - 1 do for i = 0 to types.Length - 1 do
@@ -621,14 +892,13 @@ module Concretization =
/// Helper to concretize a method signature /// Helper to concretize a method signature
let private concretizeMethodSignature let private concretizeMethodSignature
(ctx : TypeConcretization.ConcretizationContext) (ctx : TypeConcretization.ConcretizationContext<DumpedAssembly>)
(loadAssembly : (loadAssembly : IAssemblyLoad)
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
(assembly : AssemblyName) (assembly : AssemblyName)
(typeArgs : ConcreteTypeHandle ImmutableArray) (typeArgs : ImmutableArray<ConcreteTypeHandle>)
(methodArgs : ConcreteTypeHandle ImmutableArray) (methodArgs : ImmutableArray<ConcreteTypeHandle>)
(signature : TypeMethodSignature<TypeDefn>) (signature : TypeMethodSignature<TypeDefn>)
: TypeMethodSignature<ConcreteTypeHandle> * TypeConcretization.ConcretizationContext : TypeMethodSignature<ConcreteTypeHandle> * TypeConcretization.ConcretizationContext<DumpedAssembly>
= =
// Concretize return type // Concretize return type
@@ -643,7 +913,7 @@ module Concretization =
let handle, newCtx = let handle, newCtx =
TypeConcretization.concretizeType ctx loadAssembly assembly typeArgs methodArgs paramType TypeConcretization.concretizeType ctx loadAssembly assembly typeArgs methodArgs paramType
paramHandles.Add (handle) paramHandles.Add handle
ctx <- newCtx ctx <- newCtx
let newSignature = let newSignature =
@@ -659,8 +929,7 @@ module Concretization =
/// Helper to ensure base type assembly is loaded /// Helper to ensure base type assembly is loaded
let rec private ensureBaseTypeAssembliesLoaded let rec private ensureBaseTypeAssembliesLoaded
(loadAssembly : (loadAssembly : IAssemblyLoad)
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
(assemblies : ImmutableDictionary<string, DumpedAssembly>) (assemblies : ImmutableDictionary<string, DumpedAssembly>)
(assyName : AssemblyName) (assyName : AssemblyName)
(baseTypeInfo : BaseTypeInfo option) (baseTypeInfo : BaseTypeInfo option)
@@ -680,7 +949,7 @@ module Concretization =
| true, _ -> assemblies | true, _ -> assemblies
| false, _ -> | false, _ ->
// Need to load the assembly - pass the assembly that contains the reference // 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 newAssemblies
| _ -> assemblies | _ -> assemblies
| Some (BaseTypeInfo.TypeDef _) | Some (BaseTypeInfo.TypeDef _)
@@ -690,13 +959,12 @@ module Concretization =
/// Concretize a method's signature and body /// Concretize a method's signature and body
let concretizeMethod let concretizeMethod
(ctx : AllConcreteTypes) (ctx : AllConcreteTypes)
(loadAssembly : (loadAssembly : IAssemblyLoad)
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
(assemblies : ImmutableDictionary<string, DumpedAssembly>) (assemblies : ImmutableDictionary<string, DumpedAssembly>)
(baseTypes : BaseClassTypes<DumpedAssembly>) (baseTypes : BaseClassTypes<DumpedAssembly>)
(method : WoofWare.PawPrint.MethodInfo<TypeDefn, WoofWare.PawPrint.GenericParameter, TypeDefn>) (method : WoofWare.PawPrint.MethodInfo<'ty, GenericParamFromMetadata, TypeDefn>)
(typeArgs : ConcreteTypeHandle ImmutableArray) (typeArgs : ImmutableArray<ConcreteTypeHandle>)
(methodArgs : ConcreteTypeHandle ImmutableArray) (methodArgs : ImmutableArray<ConcreteTypeHandle>)
: WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle> * : WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle> *
AllConcreteTypes * AllConcreteTypes *
ImmutableDictionary<string, DumpedAssembly> ImmutableDictionary<string, DumpedAssembly>
@@ -826,10 +1094,9 @@ module Concretization =
// Map generics to handles // Map generics to handles
let genericHandles = let genericHandles =
method.Generics method.Generics
|> Seq.mapi (fun i _ -> methodArgs.[i]) |> ImmutableArray.map (fun (gp, md) -> methodArgs.[gp.SequenceNumber])
|> ImmutableArray.CreateRange
let concretizedMethod : MethodInfo<_, _, ConcreteTypeHandle> = let concretizedMethod : MethodInfo<_, _, _> =
{ {
DeclaringType = concretizedDeclaringType DeclaringType = concretizedDeclaringType
Handle = method.Handle Handle = method.Handle
@@ -891,8 +1158,7 @@ module Concretization =
// Recursively convert generic arguments // Recursively convert generic arguments
let genericArgs = let genericArgs =
concreteType.Generics concreteType.Generics
|> List.map (fun h -> concreteHandleToTypeDefn baseClassTypes h concreteTypes assemblies) |> ImmutableArray.map (fun h -> concreteHandleToTypeDefn baseClassTypes h concreteTypes assemblies)
|> ImmutableArray.CreateRange
let baseDef = let baseDef =
TypeDefn.FromDefinition (concreteType.Definition, concreteType.Assembly.FullName, signatureTypeKind) TypeDefn.FromDefinition (concreteType.Definition, concreteType.Assembly.FullName, signatureTypeKind)

View File

@@ -149,6 +149,28 @@ type PrimitiveType =
| PrimitiveType.UIntPtr -> "uintptr" | PrimitiveType.UIntPtr -> "uintptr"
| PrimitiveType.Object -> "obj" | 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 -> 8
| PrimitiveType.UIntPtr -> 8
| PrimitiveType.Object -> 8
type TypeDefn = type TypeDefn =
| PrimitiveType of PrimitiveType | PrimitiveType of PrimitiveType
// TODO: array shapes // TODO: array shapes

View File

@@ -1,5 +1,6 @@
namespace WoofWare.PawPrint namespace WoofWare.PawPrint
open System
open System.Collections.Generic open System.Collections.Generic
open System.Collections.Immutable open System.Collections.Immutable
open System.Reflection open System.Reflection
@@ -19,6 +20,15 @@ type MethodImplParsed =
| MethodImplementation of MethodImplementationHandle | MethodImplementation of MethodImplementationHandle
| MethodDefinition of MethodDefinitionHandle | MethodDefinition of MethodDefinitionHandle
type InterfaceImplementation =
{
/// TypeDefinition, TypeReference, or TypeSpecification
InterfaceHandle : MetadataToken
/// The assembly which InterfaceHandle is relative to
RelativeToAssembly : AssemblyName
}
/// <summary> /// <summary>
/// Represents detailed information about a type definition in a .NET assembly. /// Represents detailed information about a type definition in a .NET assembly.
/// This is a strongly-typed representation of TypeDefinition from System.Reflection.Metadata. /// This is a strongly-typed representation of TypeDefinition from System.Reflection.Metadata.
@@ -34,7 +44,7 @@ type TypeInfo<'generic, 'fieldGeneric> =
/// <summary> /// <summary>
/// All methods defined within this type. /// All methods defined within this type.
/// </summary> /// </summary>
Methods : WoofWare.PawPrint.MethodInfo<FakeUnit, WoofWare.PawPrint.GenericParameter, TypeDefn> list Methods : WoofWare.PawPrint.MethodInfo<GenericParamFromMetadata, GenericParamFromMetadata, TypeDefn> list
/// <summary> /// <summary>
/// Method implementation mappings for this type, often used for interface implementations /// Method implementation mappings for this type, often used for interface implementations
@@ -45,7 +55,7 @@ type TypeInfo<'generic, 'fieldGeneric> =
/// <summary> /// <summary>
/// Fields defined in this type. /// Fields defined in this type.
/// </summary> /// </summary>
Fields : WoofWare.PawPrint.FieldInfo<FakeUnit, 'fieldGeneric> list Fields : WoofWare.PawPrint.FieldInfo<GenericParamFromMetadata, 'fieldGeneric> list
/// <summary> /// <summary>
/// The base type that this type inherits from, or None for types that don't have a base type /// The base type that this type inherits from, or None for types that don't have a base type
@@ -71,6 +81,8 @@ type TypeInfo<'generic, 'fieldGeneric> =
/// </summary> /// </summary>
TypeDefHandle : TypeDefinitionHandle TypeDefHandle : TypeDefinitionHandle
DeclaringType : TypeDefinitionHandle
/// <summary> /// <summary>
/// The assembly in which this type is defined. /// The assembly in which this type is defined.
/// </summary> /// </summary>
@@ -79,8 +91,23 @@ type TypeInfo<'generic, 'fieldGeneric> =
Generics : 'generic ImmutableArray Generics : 'generic ImmutableArray
Events : EventDefn ImmutableArray Events : EventDefn ImmutableArray
ImplementedInterfaces : InterfaceImplementation ImmutableArray
} }
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 () = override this.ToString () =
$"%s{this.Assembly.Name}.%s{this.Namespace}.%s{this.Name}" $"%s{this.Assembly.Name}.%s{this.Namespace}.%s{this.Name}"
@@ -129,36 +156,47 @@ module TypeInfoCrate =
type BaseClassTypes<'corelib> = type BaseClassTypes<'corelib> =
{ {
Corelib : 'corelib Corelib : 'corelib
String : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> String : TypeInfo<GenericParamFromMetadata, TypeDefn>
Boolean : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> Boolean : TypeInfo<GenericParamFromMetadata, TypeDefn>
Char : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> Char : TypeInfo<GenericParamFromMetadata, TypeDefn>
SByte : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> SByte : TypeInfo<GenericParamFromMetadata, TypeDefn>
Byte : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> Byte : TypeInfo<GenericParamFromMetadata, TypeDefn>
Int16 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> Int16 : TypeInfo<GenericParamFromMetadata, TypeDefn>
UInt16 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> UInt16 : TypeInfo<GenericParamFromMetadata, TypeDefn>
Int32 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> Int32 : TypeInfo<GenericParamFromMetadata, TypeDefn>
UInt32 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> UInt32 : TypeInfo<GenericParamFromMetadata, TypeDefn>
Int64 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> Int64 : TypeInfo<GenericParamFromMetadata, TypeDefn>
UInt64 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> UInt64 : TypeInfo<GenericParamFromMetadata, TypeDefn>
Single : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> Single : TypeInfo<GenericParamFromMetadata, TypeDefn>
Double : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> Double : TypeInfo<GenericParamFromMetadata, TypeDefn>
Array : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> Array : TypeInfo<GenericParamFromMetadata, TypeDefn>
Enum : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> Enum : TypeInfo<GenericParamFromMetadata, TypeDefn>
ValueType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> ValueType : TypeInfo<GenericParamFromMetadata, TypeDefn>
DelegateType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> DelegateType : TypeInfo<GenericParamFromMetadata, TypeDefn>
Object : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> Object : TypeInfo<GenericParamFromMetadata, TypeDefn>
RuntimeMethodHandle : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> RuntimeMethodHandle : TypeInfo<GenericParamFromMetadata, TypeDefn>
RuntimeFieldHandle : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> RuntimeFieldHandle : TypeInfo<GenericParamFromMetadata, TypeDefn>
RuntimeTypeHandle : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> RuntimeTypeHandle : TypeInfo<GenericParamFromMetadata, TypeDefn>
RuntimeType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> RuntimeFieldInfoStub : TypeInfo<GenericParamFromMetadata, TypeDefn>
Void : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> RuntimeFieldHandleInternal : TypeInfo<GenericParamFromMetadata, TypeDefn>
TypedReference : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> RuntimeType : TypeInfo<GenericParamFromMetadata, TypeDefn>
IntPtr : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> Void : TypeInfo<GenericParamFromMetadata, TypeDefn>
UIntPtr : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> TypedReference : TypeInfo<GenericParamFromMetadata, TypeDefn>
IntPtr : TypeInfo<GenericParamFromMetadata, TypeDefn>
UIntPtr : TypeInfo<GenericParamFromMetadata, TypeDefn>
} }
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module TypeInfo = 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> = let withGenerics<'a, 'b, 'field> (gen : 'b ImmutableArray) (t : TypeInfo<'a, 'field>) : TypeInfo<'b, 'field> =
{ {
Namespace = t.Namespace Namespace = t.Namespace
@@ -170,13 +208,15 @@ module TypeInfo =
TypeAttributes = t.TypeAttributes TypeAttributes = t.TypeAttributes
Attributes = t.Attributes Attributes = t.Attributes
TypeDefHandle = t.TypeDefHandle TypeDefHandle = t.TypeDefHandle
DeclaringType = t.DeclaringType
Assembly = t.Assembly Assembly = t.Assembly
Generics = gen Generics = gen
Events = t.Events Events = t.Events
ImplementedInterfaces = t.ImplementedInterfaces
} }
let mapGeneric<'a, 'b, 'field> (f : int -> 'a -> 'b) (t : TypeInfo<'a, 'field>) : TypeInfo<'b, 'field> = let mapGeneric<'a, 'b, 'field> (f : 'a -> 'b) (t : TypeInfo<'a, 'field>) : TypeInfo<'b, 'field> =
withGenerics (t.Generics |> Seq.mapi f |> ImmutableArray.CreateRange) t withGenerics (t.Generics |> ImmutableArray.map f) t
let internal read let internal read
(loggerFactory : ILoggerFactory) (loggerFactory : ILoggerFactory)
@@ -184,9 +224,10 @@ module TypeInfo =
(thisAssembly : AssemblyName) (thisAssembly : AssemblyName)
(metadataReader : MetadataReader) (metadataReader : MetadataReader)
(typeHandle : TypeDefinitionHandle) (typeHandle : TypeDefinitionHandle)
: TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> : TypeInfo<GenericParamFromMetadata, TypeDefn>
= =
let typeDef = metadataReader.GetTypeDefinition typeHandle let typeDef = metadataReader.GetTypeDefinition typeHandle
let declaringType = typeDef.GetDeclaringType ()
let methods = typeDef.GetMethods () let methods = typeDef.GetMethods ()
let methodImpls = let methodImpls =
@@ -253,6 +294,20 @@ module TypeInfo =
result.ToImmutable () 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 ()
{ {
Namespace = ns Namespace = ns
Name = name Name = name
@@ -266,6 +321,8 @@ module TypeInfo =
Assembly = thisAssembly Assembly = thisAssembly
Generics = genericParams Generics = genericParams
Events = events Events = events
ImplementedInterfaces = interfaces
DeclaringType = declaringType
} }
let isBaseType<'corelib> let isBaseType<'corelib>
@@ -291,10 +348,10 @@ module TypeInfo =
let rec resolveBaseType<'corelib, 'generic, 'field> let rec resolveBaseType<'corelib, 'generic, 'field>
(baseClassTypes : BaseClassTypes<'corelib>) (baseClassTypes : BaseClassTypes<'corelib>)
(sourceAssy : 'corelib)
(getName : 'corelib -> AssemblyName) (getName : 'corelib -> AssemblyName)
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic, 'field>) (getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic, 'field>)
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic, 'field>) (getTypeRef : 'corelib -> TypeReferenceHandle -> 'corelib * TypeInfo<'generic, 'field>)
(sourceAssembly : AssemblyName)
(value : BaseTypeInfo option) (value : BaseTypeInfo option)
: ResolvedBaseType : ResolvedBaseType
= =
@@ -304,40 +361,48 @@ module TypeInfo =
match value with match value with
| BaseTypeInfo.TypeDef typeDefinitionHandle -> | BaseTypeInfo.TypeDef typeDefinitionHandle ->
match isBaseType baseClassTypes getName sourceAssembly typeDefinitionHandle with match isBaseType baseClassTypes getName (getName sourceAssy) typeDefinitionHandle with
| Some x -> x | Some x -> x
| None -> | None ->
let baseType = getTypeDef baseClassTypes.Corelib typeDefinitionHandle 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 -> | BaseTypeInfo.TypeRef typeReferenceHandle ->
let typeRef = getTypeRef baseClassTypes.Corelib typeReferenceHandle let targetAssy, typeRef = getTypeRef sourceAssy typeReferenceHandle
failwith $"{typeRef}"
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.TypeSpec typeSpecificationHandle -> failwith "todo"
| BaseTypeInfo.ForeignAssemblyType (assemblyName, typeDefinitionHandle) -> | BaseTypeInfo.ForeignAssemblyType (assemblyName, typeDefinitionHandle) ->
resolveBaseType resolveBaseType
baseClassTypes baseClassTypes
sourceAssy
getName getName
getTypeDef getTypeDef
getTypeRef getTypeRef
assemblyName
(Some (BaseTypeInfo.TypeDef typeDefinitionHandle)) (Some (BaseTypeInfo.TypeDef typeDefinitionHandle))
let toTypeDefn let toTypeDefn
(corelib : BaseClassTypes<'corelib>) (baseClassTypes : BaseClassTypes<'corelib>)
(assemblies : AssemblyName -> 'corelib)
(getName : 'corelib -> AssemblyName) (getName : 'corelib -> AssemblyName)
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic, 'field>) (getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic, 'field>)
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic, 'field>) (getTypeRef : 'corelib -> TypeReferenceHandle -> 'corelib * TypeInfo<'generic, 'field>)
(ty : TypeInfo<TypeDefn, TypeDefn>) (ty : TypeInfo<TypeDefn, TypeDefn>)
: TypeDefn : TypeDefn
= =
let stk = 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.Enum
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType | ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
| ResolvedBaseType.Object | ResolvedBaseType.Object
| ResolvedBaseType.Delegate -> SignatureTypeKind.Class | ResolvedBaseType.Delegate -> SignatureTypeKind.Class
let defn = 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) TypeDefn.FromDefinition (ComparableTypeDefinitionHandle.Make ty.TypeDefHandle, ty.Assembly.FullName, stk)
if ty.Generics.IsEmpty then if ty.Generics.IsEmpty then

View File

@@ -7,13 +7,16 @@
<ItemGroup> <ItemGroup>
<Compile Include="StringToken.fs" /> <Compile Include="StringToken.fs" />
<Compile Include="ImmutableArray.fs" />
<Compile Include="Tokens.fs" /> <Compile Include="Tokens.fs" />
<Compile Include="TypeRef.fs" /> <Compile Include="TypeRef.fs" />
<Compile Include="IlOp.fs" /> <Compile Include="IlOp.fs" />
<Compile Include="CustomAttribute.fs" /> <Compile Include="CustomAttribute.fs" />
<Compile Include="GenericParameter.fs" />
<Compile Include="AssemblyReference.fs" /> <Compile Include="AssemblyReference.fs" />
<Compile Include="EventDefn.fs" /> <Compile Include="EventDefn.fs" />
<Compile Include="ComparableTypeDefinitionHandle.fs" /> <Compile Include="ComparableTypeDefinitionHandle.fs" />
<Compile Include="ComparableFieldDefinitionHandle.fs" />
<Compile Include="ComparableSignatureHeader.fs" /> <Compile Include="ComparableSignatureHeader.fs" />
<Compile Include="TypeDefn.fs" /> <Compile Include="TypeDefn.fs" />
<Compile Include="ConcreteType.fs" /> <Compile Include="ConcreteType.fs" />

View File

@@ -23,7 +23,7 @@ module LoggerFactory =
let makeTest () : (unit -> LogLine list) * ILoggerFactory = let makeTest () : (unit -> LogLine list) * ILoggerFactory =
// Shared sink for all loggers created by the factory. // Shared sink for all loggers created by the factory.
let sink = ResizeArray () let sink = ResizeArray ()
let isEnabled (logLevel : LogLevel) : bool = logLevel >= LogLevel.Information let isEnabled (logLevel : LogLevel) : bool = logLevel >= LogLevel.Debug
let createLogger (category : string) : ILogger = let createLogger (category : string) : ILogger =
{ new ILogger with { new ILogger with

View File

@@ -25,10 +25,9 @@ module MockEnv =
System_Threading_Monitor = System_Threading_MonitorMock.Empty System_Threading_Monitor = System_Threading_MonitorMock.Empty
} }
type TestCase = type EndToEndTestCase =
{ {
FileName : string FileName : string
ExpectedReturnCode : int ExpectedReturnCode : int
NativeImpls : NativeImpls NativeImpls : NativeImpls
LocalVariablesOfMain : CliType list option
} }

View File

@@ -20,11 +20,10 @@ module TestImpureCases =
FileName = "WriteLine.cs" FileName = "WriteLine.cs"
ExpectedReturnCode = 1 ExpectedReturnCode = 1
NativeImpls = NativeImpls.PassThru () NativeImpls = NativeImpls.PassThru ()
LocalVariablesOfMain = [] |> Some
} }
] ]
let cases : TestCase list = let cases : EndToEndTestCase list =
[ [
{ {
FileName = "InstaQuit.cs" FileName = "InstaQuit.cs"
@@ -47,12 +46,10 @@ module TestImpureCases =
ExecutionResult.Terminated (state, thread) ExecutionResult.Terminated (state, thread)
} }
} }
LocalVariablesOfMain = [] |> Some
} }
] ]
[<TestCaseSource(nameof cases)>] let runTest (case : EndToEndTestCase) : unit =
let ``Can evaluate C# files`` (case : TestCase) : unit =
let source = Assembly.getEmbeddedResourceAsString case.FileName assy let source = Assembly.getEmbeddedResourceAsString case.FileName assy
let image = Roslyn.compile [ source ] let image = Roslyn.compile [ source ]
let messages, loggerFactory = LoggerFactory.makeTest () let messages, loggerFactory = LoggerFactory.makeTest ()
@@ -75,15 +72,6 @@ module TestImpureCases =
| ret -> failwith $"expected program to return an int, but it returned %O{ret}" | ret -> failwith $"expected program to return an int, but it returned %O{ret}"
exitCode |> shouldEqual case.ExpectedReturnCode exitCode |> shouldEqual case.ExpectedReturnCode
let finalVariables =
terminalState.ThreadState.[terminatingThread].MethodState.LocalVariables
|> Seq.toList
match case.LocalVariablesOfMain with
| None -> ()
| Some expected -> finalVariables |> shouldEqual expected
with _ -> with _ ->
for message in messages () do for message in messages () do
System.Console.Error.WriteLine $"{message}" System.Console.Error.WriteLine $"{message}"
@@ -91,33 +79,8 @@ module TestImpureCases =
reraise () reraise ()
[<TestCaseSource(nameof unimplemented)>] [<TestCaseSource(nameof unimplemented)>]
[<Explicit "not yet implemented">] [<Explicit>]
let ``Can evaluate C# files, unimplemented`` (case : TestCase) : unit = let ``Can evaluate C# files, unimplemented`` (case : EndToEndTestCase) = runTest case
let source = Assembly.getEmbeddedResourceAsString case.FileName assy
let image = Roslyn.compile [ source ]
let messages, loggerFactory = LoggerFactory.makeTest ()
let dotnetRuntimes = [<TestCaseSource(nameof cases)>]
DotnetRuntime.SelectForDll assy.Location |> ImmutableArray.CreateRange let ``Can evaluate C# files`` (case : EndToEndTestCase) = runTest case
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 ()

View File

@@ -20,245 +20,100 @@ module TestPureCases =
FileName = "CrossAssemblyTypes.cs" FileName = "CrossAssemblyTypes.cs"
ExpectedReturnCode = 0 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
} }
{ {
FileName = "GenericEdgeCases.cs" FileName = "OverlappingStructs.cs"
ExpectedReturnCode = 0 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
} }
{ {
FileName = "TestShl.cs" FileName = "AdvancedStructLayout.cs"
ExpectedReturnCode = 0 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
} }
{ {
FileName = "TestShr.cs" FileName = "InitializeArray.cs"
ExpectedReturnCode = 0 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
} }
{ {
FileName = "Threads.cs" FileName = "Threads.cs"
ExpectedReturnCode = 3 ExpectedReturnCode = 3
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = [] |> Some
} }
{ {
FileName = "ComplexTryCatch.cs" FileName = "ComplexTryCatch.cs"
ExpectedReturnCode = 14 ExpectedReturnCode = 14
NativeImpls = NativeImpls.PassThru () 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" FileName = "ResizeArray.cs"
ExpectedReturnCode = 109 ExpectedReturnCode = 114
NativeImpls = MockEnv.make ()
}
{
FileName = "Sizeof.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
}
{
FileName = "LdtokenField.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ] |> Some
} }
] ]
let cases : TestCase list = let cases : EndToEndTestCase list =
[ [
{ {
FileName = "NoOp.cs" FileName = "NoOp.cs"
ExpectedReturnCode = 1 ExpectedReturnCode = 1
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 1) ] |> Some
} }
{ {
FileName = "CastClassSimpleInheritance.cs" FileName = "UnsafeAs.cs"
ExpectedReturnCode = 5 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
} }
{ {
FileName = "IsInstSimpleInheritance.cs" FileName = "Initobj.cs"
ExpectedReturnCode = 42 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
} }
{ {
FileName = "CastClassNull.cs" FileName = "GenericEdgeCases.cs"
ExpectedReturnCode = 42 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
} }
{ {
FileName = "CastClassArrayCovariance.cs" FileName = "TestShl.cs"
ExpectedReturnCode = 1 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
} }
{ {
FileName = "CastClassToObject.cs" FileName = "TestShr.cs"
ExpectedReturnCode = 1 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "IsinstPatternMatching.cs"
ExpectedReturnCode = 1
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "CastClassMultipleInterfaces.cs"
ExpectedReturnCode = 42
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "CastClassCrossAssembly.cs"
ExpectedReturnCode = 1
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "CastClassNestedTypes.cs"
ExpectedReturnCode = 1
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "CastClassGenerics.cs"
ExpectedReturnCode = 1
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "CastClassEnum.cs"
ExpectedReturnCode = 1
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "CastClassBoxing.cs"
ExpectedReturnCode = 1
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "IsinstBoxing.cs"
ExpectedReturnCode = 1
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "CastClassArray.cs"
ExpectedReturnCode = 1
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "IsinstArray.cs"
ExpectedReturnCode = 1
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "IsinstNull.cs"
ExpectedReturnCode = 42
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "CastClassInvalid.cs"
ExpectedReturnCode = 1
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "IsinstFailed.cs"
ExpectedReturnCode = 1
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "IsinstFailedInterface.cs"
ExpectedReturnCode = 1
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "CastClassInterface.cs"
ExpectedReturnCode = 1
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "IsinstInterface.cs"
ExpectedReturnCode = 42
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
} }
{ {
FileName = "StaticVariables.cs" FileName = "StaticVariables.cs"
ExpectedReturnCode = 0 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
} }
{ {
FileName = "Ldind.cs" FileName = "Ldind.cs"
ExpectedReturnCode = 0 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain =
[
// `failures`
CliType.Numeric (CliNumericType.Int32 0)
// Return value
CliType.Numeric (CliNumericType.Int32 0)
]
|> Some
} }
{ {
FileName = "CustomDelegate.cs" FileName = "CustomDelegate.cs"
ExpectedReturnCode = 8 ExpectedReturnCode = 8
NativeImpls = MockEnv.make () 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" FileName = "ArgumentOrdering.cs"
ExpectedReturnCode = 0 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
} }
{ {
FileName = "BasicLock.cs" FileName = "BasicLock.cs"
@@ -269,97 +124,55 @@ module TestPureCases =
{ mock with { mock with
System_Threading_Monitor = System_Threading_Monitor.passThru 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" FileName = "TriangleNumber.cs"
ExpectedReturnCode = 10 ExpectedReturnCode = 10
NativeImpls = MockEnv.make () 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" FileName = "ExceptionWithNoOpFinally.cs"
ExpectedReturnCode = 3 ExpectedReturnCode = 3
NativeImpls = MockEnv.make () 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" FileName = "ExceptionWithNoOpCatch.cs"
ExpectedReturnCode = 10 ExpectedReturnCode = 10
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ] |> Some
} }
{ {
FileName = "Floats.cs" FileName = "Floats.cs"
ExpectedReturnCode = 0 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
} }
{ {
FileName = "TryCatchWithThrowInBody.cs" FileName = "TryCatchWithThrowInBody.cs"
ExpectedReturnCode = 4 ExpectedReturnCode = 4
NativeImpls = MockEnv.make () 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" FileName = "Ldelema.cs"
ExpectedReturnCode = 0 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
} }
{ {
FileName = "TypeConcretization.cs" FileName = "TypeConcretization.cs"
ExpectedReturnCode = 0 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
} }
{ {
FileName = "TestOr.cs" FileName = "TestOr.cs"
ExpectedReturnCode = 0 ExpectedReturnCode = 0
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None }
{
FileName = "InterfaceDispatch.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
} }
] ]
[<TestCaseSource(nameof cases)>] let runTest (case : EndToEndTestCase) : unit =
let ``Can evaluate C# files`` (case : TestCase) : unit =
let source = Assembly.getEmbeddedResourceAsString case.FileName assy let source = Assembly.getEmbeddedResourceAsString case.FileName assy
let image = Roslyn.compile [ source ] let image = Roslyn.compile [ source ]
let messages, loggerFactory = LoggerFactory.makeTest () let messages, loggerFactory = LoggerFactory.makeTest ()
@@ -370,11 +183,12 @@ module TestPureCases =
use peImage = new MemoryStream (image) use peImage = new MemoryStream (image)
try try
let realResult = RealRuntime.executeWithRealRuntime [||] image
realResult.ExitCode |> shouldEqual case.ExpectedReturnCode
let terminalState, terminatingThread = let terminalState, terminatingThread =
Program.run loggerFactory (Some case.FileName) peImage dotnetRuntimes case.NativeImpls [] Program.run loggerFactory (Some case.FileName) peImage dotnetRuntimes case.NativeImpls []
let realResult = RealRuntime.executeWithRealRuntime [||] image
let exitCode = let exitCode =
match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with
| [] -> failwith "expected program to return a value, but it returned void" | [] -> failwith "expected program to return a value, but it returned void"
@@ -385,15 +199,6 @@ module TestPureCases =
exitCode |> shouldEqual realResult.ExitCode 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 _ -> with _ ->
for message in messages () do for message in messages () do
System.Console.Error.WriteLine $"{message}" System.Console.Error.WriteLine $"{message}"
@@ -401,33 +206,8 @@ module TestPureCases =
reraise () reraise ()
[<TestCaseSource(nameof unimplemented)>] [<TestCaseSource(nameof unimplemented)>]
[<Explicit "not yet implemented">] [<Explicit>]
let ``Can evaluate C# files, unimplemented`` (case : TestCase) : unit = let ``Can evaluate C# files, unimplemented`` (case : EndToEndTestCase) = runTest case
let source = Assembly.getEmbeddedResourceAsString case.FileName assy
let image = Roslyn.compile [ source ]
let messages, loggerFactory = LoggerFactory.makeTest ()
let dotnetRuntimes = [<TestCaseSource(nameof cases)>]
DotnetRuntime.SelectForDll assy.Location |> ImmutableArray.CreateRange let ``Can evaluate C# files`` (case : EndToEndTestCase) = runTest case
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 ()

View File

@@ -4,9 +4,6 @@
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<IsTestProject>true</IsTestProject>
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
<EnableNUnitRunner>true</EnableNUnitRunner>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -19,52 +16,8 @@
<Compile Include="TestImpureCases.fs" /> <Compile Include="TestImpureCases.fs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="sourcesPure\BasicLock.cs" /> <EmbeddedResource Include="sourcesPure\*.cs" />
<EmbeddedResource Include="sourcesPure\Floats.cs" /> <EmbeddedResource Include="sourcesImpure\*.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\CastClassSimpleInheritance.cs" />
<EmbeddedResource Include="sourcesPure\IsInstSimpleInheritance.cs" />
<EmbeddedResource Include="sourcesPure\CastClassNull.cs" />
<EmbeddedResource Include="sourcesPure\CastClassArrayCovariance.cs" />
<EmbeddedResource Include="sourcesPure\CastClassToObject.cs" />
<EmbeddedResource Include="sourcesPure\IsinstPatternMatching.cs" />
<EmbeddedResource Include="sourcesPure\CastClassMultipleInterfaces.cs" />
<EmbeddedResource Include="sourcesPure\CastClassCrossAssembly.cs" />
<EmbeddedResource Include="sourcesPure\CastClassNestedTypes.cs" />
<EmbeddedResource Include="sourcesPure\CastClassGenerics.cs" />
<EmbeddedResource Include="sourcesPure\CastClassEnum.cs" />
<EmbeddedResource Include="sourcesPure\CastClassBoxing.cs" />
<EmbeddedResource Include="sourcesPure\IsinstBoxing.cs" />
<EmbeddedResource Include="sourcesPure\CastClassArray.cs" />
<EmbeddedResource Include="sourcesPure\IsinstArray.cs" />
<EmbeddedResource Include="sourcesPure\IsinstNull.cs" />
<EmbeddedResource Include="sourcesPure\CastClassInvalid.cs" />
<EmbeddedResource Include="sourcesPure\IsinstFailed.cs" />
<EmbeddedResource Include="sourcesPure\IsinstFailedInterface.cs" />
<EmbeddedResource Include="sourcesPure\CastClassInterface.cs" />
<EmbeddedResource Include="sourcesPure\IsinstInterface.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" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -72,10 +25,10 @@
</ItemGroup> </ItemGroup>
<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="FsUnit" Version="7.1.1"/>
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/> <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.CodeAnalysis.CSharp" Version="4.14.0"/>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.6" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.6" />
<PackageReference Include="WoofWare.DotnetRuntimeLocator" Version="0.3.2"/> <PackageReference Include="WoofWare.DotnetRuntimeLocator" Version="0.3.2"/>

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

View File

@@ -1,12 +0,0 @@
public class Program
{
public static int Main(string[] args)
{
int[] numbers = new int[] { 1, 2, 3, 4, 5 };
// Cast array to System.Array - should succeed
System.Array array = (System.Array)numbers;
return array.Length;
}
}

View File

@@ -1,28 +0,0 @@
public class Program
{
public struct Counter
{
public int Count;
public Counter(int count)
{
Count = count;
}
}
public static int Main(string[] args)
{
Counter counter = new Counter(42);
// Box the value type
object boxed = counter;
// Check if boxed value is System.ValueType
if (boxed is System.ValueType)
{
return 42;
}
return 0;
}
}

View File

@@ -1,30 +0,0 @@
public class Program
{
public struct Point
{
public int X;
public int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}
public static int Main(string[] args)
{
Point p = new Point(10, 32);
// Box the value type
object boxed = p;
// Cast boxed value type to object (should succeed)
object obj = (object)boxed;
// Unbox
Point unboxed = (Point)obj;
return unboxed.X + unboxed.Y;
}
}

View File

@@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
public class Program
{
public static int Main(string[] args)
{
// Using types from System.Collections.Generic assembly
List<int> list = new List<int> { 1, 2, 3, 4, 5 };
// Cast to interface from another assembly
IEnumerable<int> enumerable = (IEnumerable<int>)list;
int count = 0;
foreach (var item in enumerable)
{
count++;
}
return count == 5 ? 42 : 0;
}
}

View File

@@ -1,25 +0,0 @@
public class Program
{
public enum Color
{
Red = 1,
Green = 2,
Blue = 42
}
public static int Main(string[] args)
{
Color myColor = Color.Blue;
// Box enum value
object boxed = myColor;
// Cast to System.Enum
System.Enum enumValue = (System.Enum)boxed;
// Cast back to specific enum
Color unboxed = (Color)enumValue;
return (int)unboxed;
}
}

View File

@@ -1,23 +0,0 @@
public class Program
{
public class Container<T>
{
public T Value { get; set; }
}
public static int Main(string[] args)
{
Container<int> intContainer = new Container<int> { Value = 42 };
// Cast generic type to object
object obj = (object)intContainer;
// Check type and cast back
if (obj is Container<int> container)
{
return container.Value;
}
return 0;
}
}

View File

@@ -1,25 +0,0 @@
public class Program
{
public interface ICalculator
{
int Calculate(int x, int y);
}
public class Adder : ICalculator
{
public int Calculate(int x, int y)
{
return x + y;
}
}
public static int Main(string[] args)
{
Adder adder = new Adder();
// Cast to interface - should succeed
ICalculator calc = (ICalculator)adder;
return calc.Calculate(10, 32);
}
}

View File

@@ -1,31 +0,0 @@
public class Program
{
public class Cat
{
public string Name { get; set; }
}
public class Dog
{
public string Name { get; set; }
}
public static int Main(string[] args)
{
try
{
object cat = new Cat { Name = "Whiskers" };
// Invalid cast - should throw InvalidCastException
Dog dog = (Dog)cat;
// Should not reach here
return 0;
}
catch (System.InvalidCastException)
{
// Expected exception caught
return 42;
}
}
}

View File

@@ -1,40 +0,0 @@
public class Program
{
public interface IReadable
{
string Read();
}
public interface IWritable
{
void Write(string data);
}
public class File : IReadable, IWritable
{
private string content = "Hello";
public string Read()
{
return content;
}
public void Write(string data)
{
content = data;
}
}
public static int Main(string[] args)
{
File file = new File();
// Cast to first interface
IReadable readable = (IReadable)file;
// Cast to second interface
IWritable writable = (IWritable)file;
return readable != null && writable != null ? 42 : 0;
}
}

View File

@@ -1,23 +0,0 @@
public class Program
{
public class Outer
{
public class Inner
{
public int Value { get; set; }
}
}
public static int Main(string[] args)
{
Outer.Inner inner = new Outer.Inner { Value = 42 };
// Cast nested type to object
object obj = (object)inner;
// Cast back
Outer.Inner casted = (Outer.Inner)obj;
return casted.Value;
}
}

View File

@@ -1,17 +0,0 @@
public class Program
{
public class MyClass
{
public int Value { get; set; }
}
public static int Main(string[] args)
{
MyClass obj = null;
// Cast null reference - should succeed and remain null
object result = (object)obj;
return result == null ? 42 : 0;
}
}

View File

@@ -1,22 +0,0 @@
public class Program
{
public class Animal
{
public int Age { get; set; }
}
public class Dog : Animal
{
public string Name { get; set; }
}
public static int Main(string[] args)
{
Dog myDog = new Dog { Age = 5, Name = "Rex" };
// Cast to base class - should succeed
Animal animal = (Animal)myDog;
return animal.Age;
}
}

View File

@@ -1,18 +0,0 @@
public class Program
{
public class CustomClass
{
public int Id { get; set; }
}
public static int Main(string[] args)
{
CustomClass custom = new CustomClass { Id = 42 };
// Everything can be cast to System.Object
System.Object obj = (System.Object)custom;
// Verify it's the same object
return obj != null && obj == custom ? 42 : 0;
}
}

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

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

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

View File

@@ -1,25 +0,0 @@
public class Program
{
public class Vehicle
{
public int Wheels { get; set; }
}
public class Car : Vehicle
{
public string Model { get; set; }
}
public static int Main(string[] args)
{
Car myCar = new Car { Wheels = 4, Model = "Tesla" };
// 'is' operator uses isinst instruction
if (myCar is Vehicle)
{
return 42;
}
return 0;
}
}

View File

@@ -1,15 +0,0 @@
public class Program
{
public static int Main(string[] args)
{
string[] names = new string[] { "Alice", "Bob", "Charlie" };
// Check if array is System.Array
if (names is System.Array)
{
return 42;
}
return 0;
}
}

View File

@@ -1,28 +0,0 @@
public class Program
{
public struct Counter
{
public int Count;
public Counter(int count)
{
Count = count;
}
}
public static int Main(string[] args)
{
Counter counter = new Counter(42);
// Box the value type
object boxed = counter;
// Check if boxed value is System.ValueType
if (boxed is System.ValueType)
{
return 42;
}
return 0;
}
}

View File

@@ -1,25 +0,0 @@
public class Program
{
public class Bird
{
public bool CanFly { get; set; }
}
public class Fish
{
public bool CanSwim { get; set; }
}
public static int Main(string[] args)
{
Bird sparrow = new Bird { CanFly = true };
// Cast to object first to bypass compile-time checking
object obj = sparrow;
// This should fail at runtime and return null (not throw)
Fish fish = obj as Fish;
return fish == null ? 42 : 0;
}
}

View File

@@ -1,30 +0,0 @@
public class Program
{
public interface IAnimal
{
string Name { get; set; }
}
public class Bird : IAnimal
{
public string Name { get; set; }
public bool CanFly { get; set; }
}
public class Fish : IAnimal
{
public string Name { get; set; }
public bool CanSwim { get; set; }
}
public static int Main(string[] args)
{
IAnimal animal = new Bird { Name = "Sparrow", CanFly = true };
// This should fail at runtime and return null (not throw)
// because the actual object is Bird, not Fish
Fish fish = animal as Fish;
return fish == null ? 42 : 0;
}
}

View File

@@ -1,27 +0,0 @@
public class Program
{
public interface IDisposable
{
void Dispose();
}
public class Resource : IDisposable
{
public int Id { get; set; }
public void Dispose()
{
// Cleanup
}
}
public static int Main(string[] args)
{
Resource resource = new Resource { Id = 42 };
// 'as' operator uses isinst instruction
IDisposable disposable = resource as IDisposable;
return disposable != null ? resource.Id : 0;
}
}

View File

@@ -1,17 +0,0 @@
public class Program
{
public class MyType
{
public int Value { get; set; }
}
public static int Main(string[] args)
{
MyType obj = null;
// isinst on null should return null
object result = obj as object;
return result == null ? 42 : 0;
}
}

View File

@@ -1,40 +0,0 @@
public class Program
{
public abstract class Shape
{
public abstract double GetArea();
}
public class Circle : Shape
{
public double Radius { get; set; }
public override double GetArea()
{
return 3.14 * Radius * Radius;
}
}
public class Square : Shape
{
public double Side { get; set; }
public override double GetArea()
{
return Side * Side;
}
}
public static int Main(string[] args)
{
Shape shape = new Circle { Radius = 10 };
// Pattern matching uses isinst
if (shape is Circle circle)
{
return (int)circle.Radius;
}
return 0;
}
}

View File

@@ -39,7 +39,7 @@ unsafe class LdindTest
failures += TestTruncation(); failures += TestTruncation();
// Test with managed pointers (ref) // Test with managed pointers (ref)
// failures += TestManagedPointers(); failures += TestManagedPointers();
// Test Ldind.i (native int) // Test Ldind.i (native int)
failures += TestLdindI(); failures += TestLdindI();
@@ -325,7 +325,10 @@ unsafe class LdindTest
} }
// Test with array element // 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]; ref int element = ref array[1];
if (element != 20) if (element != 20)
{ {

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

View File

@@ -0,0 +1,364 @@
using System;
using System.Runtime.InteropServices;
public class StructLayoutTests
{
// Test structs with various layouts
[StructLayout(LayoutKind.Sequential)]
struct SequentialStruct
{
public int A;
public byte B;
public long C;
}
[StructLayout(LayoutKind.Explicit)]
struct ExplicitUnion
{
[FieldOffset(0)] public int AsInt;
[FieldOffset(0)] public float AsFloat;
[FieldOffset(0)] public byte Byte0;
[FieldOffset(1)] public byte Byte1;
[FieldOffset(2)] public byte Byte2;
[FieldOffset(3)] public byte Byte3;
}
[StructLayout(LayoutKind.Explicit, Size = 16)]
struct FixedSizeStruct
{
[FieldOffset(0)] public long First;
[FieldOffset(8)] public int Second;
[FieldOffset(12)] public short Third;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct PackedStruct
{
public byte A;
public int B;
public byte C;
}
[StructLayout(LayoutKind.Auto)]
struct AutoLayoutStruct
{
public int X;
public string Y;
public double Z;
}
[StructLayout(LayoutKind.Explicit)]
struct NestedUnion
{
[FieldOffset(0)] public ExplicitUnion Inner;
[FieldOffset(0)] public long AsLong;
[FieldOffset(4)] public int UpperInt;
}
[StructLayout(LayoutKind.Explicit)]
struct LargeUnion
{
[FieldOffset(0)] public long Long1;
[FieldOffset(8)] public long Long2;
[FieldOffset(0)] public double Double1;
[FieldOffset(8)] public double Double2;
[FieldOffset(0)] public decimal AsDecimal;
}
// Static fields for testing
static SequentialStruct staticSequential;
static ExplicitUnion staticUnion;
static FixedSizeStruct staticFixed;
// Instance fields for testing
class FieldContainer
{
public SequentialStruct instanceSequential;
public ExplicitUnion instanceUnion;
public PackedStruct instancePacked;
public NestedUnion instanceNested;
}
static int TestSequentialLayout()
{
var s = new SequentialStruct { A = 42, B = 255, C = long.MaxValue };
// Test field access
if (s.A != 42) return 1;
if (s.B != 255) return 2;
if (s.C != long.MaxValue) return 3;
// Test copy semantics
var s2 = s;
s2.A = 100;
if (s.A != 42) return 4; // Should be unchanged (value type)
if (s2.A != 100) return 5;
// Test static field storage
staticSequential = s;
if (staticSequential.A != 42) return 6;
if (staticSequential.C != long.MaxValue) return 7;
return 0;
}
static int TestExplicitUnion()
{
var u = new ExplicitUnion();
// Test overlapping int/float
u.AsInt = 0x3F800000; // IEEE 754 representation of 1.0f
if (Math.Abs(u.AsFloat - 1.0f) > 0.0001f) return 10;
// Test byte-level access
u.AsInt = 0x12345678;
bool isLittleEndian = BitConverter.IsLittleEndian;
if (isLittleEndian)
{
if (u.Byte0 != 0x78) return 11;
if (u.Byte1 != 0x56) return 12;
if (u.Byte2 != 0x34) return 13;
if (u.Byte3 != 0x12) return 14;
}
else
{
if (u.Byte0 != 0x12) return 11;
if (u.Byte1 != 0x34) return 12;
if (u.Byte2 != 0x56) return 13;
if (u.Byte3 != 0x78) return 14;
}
// Test static field
staticUnion = u;
if (staticUnion.AsInt != 0x12345678) return 15;
return 0;
}
static int TestFixedSizeStruct()
{
var f = new FixedSizeStruct { First = -1, Second = 42, Third = 1000 };
if (f.First != -1) return 20;
if (f.Second != 42) return 21;
if (f.Third != 1000) return 22;
// Test size is respected
int size = Marshal.SizeOf(typeof(FixedSizeStruct));
if (size != 16) return 23;
staticFixed = f;
if (staticFixed.Second != 42) return 24;
return 0;
}
static int TestPackedStruct()
{
var p = new PackedStruct { A = 1, B = 0x12345678, C = 2 };
if (p.A != 1) return 30;
if (p.B != 0x12345678) return 31;
if (p.C != 2) return 32;
// Packed struct should be 6 bytes (1 + 4 + 1)
int size = Marshal.SizeOf(typeof(PackedStruct));
if (size != 6) return 33;
return 0;
}
static int TestInstanceFields()
{
var container = new FieldContainer();
container.instanceSequential = new SequentialStruct { A = 111, B = 222, C = 333 };
if (container.instanceSequential.A != 111) return 40;
container.instanceUnion = new ExplicitUnion { AsInt = 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 = TestSequentialLayout();
if (result != 0) return result;
result = TestExplicitUnion();
if (result != 0) return result;
result = TestFixedSizeStruct();
if (result != 0) return result;
result = TestPackedStruct();
if (result != 0) return result;
result = TestInstanceFields();
if (result != 0) return result;
result = TestStructPassing();
if (result != 0) return result;
result = TestNestedUnion();
if (result != 0) return result;
result = TestLargeUnion();
if (result != 0) return result;
result = TestAutoLayout();
if (result != 0) return result;
result = TestStructArray();
if (result != 0) return result;
result = TestBoxingUnboxing();
if (result != 0) return result;
result = TestDefaultValues();
if (result != 0) return result;
return 0; // All tests passed
}
}

View File

@@ -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 Value1;
public int Value2;
}
public struct LargeStruct
{
public long Value1;
public long Value2;
public long Value3;
public long Value4;
}
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;
}
}

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

View File

@@ -1,6 +1,5 @@
namespace WoofWare.PawPrint namespace WoofWare.PawPrint
open System.Collections.Immutable
open Microsoft.Extensions.Logging open Microsoft.Extensions.Logging
open Microsoft.FSharp.Core open Microsoft.FSharp.Core
open WoofWare.PawPrint.ExternImplementations open WoofWare.PawPrint.ExternImplementations
@@ -55,18 +54,19 @@ module AbstractMachine =
// We've been instructed to run a delegate. // We've been instructed to run a delegate.
let delegateToRunAddr = let delegateToRunAddr =
match instruction.Arguments.[0] with match instruction.Arguments.[0] with
| CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Heap addr))
| CliType.ObjectRef (Some addr) -> addr | CliType.ObjectRef (Some addr) -> addr
| _ -> failwith "expected a managed object ref to delegate" | _ -> failwith "expected a managed object ref to delegate"
let delegateToRun = state.ManagedHeap.NonArrayObjects.[delegateToRunAddr] let delegateToRun = state.ManagedHeap.NonArrayObjects.[delegateToRunAddr]
let target = let target =
match delegateToRun.Fields.["_target"] with match delegateToRun |> AllocatedNonArrayObject.DereferenceField "_target" with
| CliType.ObjectRef addr -> addr | CliType.ObjectRef addr -> addr
| x -> failwith $"TODO: delegate target wasn't an object ref: %O{x}" | x -> failwith $"TODO: delegate target wasn't an object ref: %O{x}"
let methodPtr = let methodPtr =
match delegateToRun.Fields.["_methodPtr"] with match delegateToRun |> AllocatedNonArrayObject.DereferenceField "_methodPtr" with
| CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer mi)) -> mi | CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer mi)) -> mi
| d -> failwith $"unexpectedly not a method pointer in delegate invocation: {d}" | d -> failwith $"unexpectedly not a method pointer in delegate invocation: {d}"
@@ -103,13 +103,14 @@ module AbstractMachine =
let currentThreadState = state.ThreadState.[thread] let currentThreadState = state.ThreadState.[thread]
let state = let state =
IlMachineState.callMethod IlMachineStateExecution.callMethod
loggerFactory loggerFactory
baseClassTypes baseClassTypes
None None
constructing constructing
false false
false false
false
methodGenerics methodGenerics
methodPtr methodPtr
thread thread
@@ -125,42 +126,55 @@ module AbstractMachine =
targetType.Namespace, targetType.Namespace,
targetType.Name, targetType.Name,
instruction.ExecutingMethod.Name, instruction.ExecutingMethod.Name,
instruction.ExecutingMethod.RawSignature.ParameterTypes, instruction.ExecutingMethod.Signature.ParameterTypes,
instruction.ExecutingMethod.RawSignature.ReturnType instruction.ExecutingMethod.Signature.ReturnType
with with
| "System.Private.CoreLib", | "System.Private.CoreLib",
"System", "System",
"Environment", "Environment",
"GetProcessorCount", "GetProcessorCount",
[], [],
TypeDefn.PrimitiveType PrimitiveType.Int32 -> ConcretePrimitive state.ConcreteTypes PrimitiveType.Int32 ->
let env = ISystem_Environment_Env.get impls let env = ISystem_Environment_Env.get impls
env.GetProcessorCount thread state env.GetProcessorCount thread state
| "System.Private.CoreLib", | "System.Private.CoreLib",
"System", "System",
"Environment", "Environment",
"_Exit", "_Exit",
[ TypeDefn.PrimitiveType PrimitiveType.Int32 ], [ ConcretePrimitive state.ConcreteTypes PrimitiveType.Int32 ],
TypeDefn.Void -> ConcreteVoid state.ConcreteTypes ->
let env = ISystem_Environment_Env.get impls let env = ISystem_Environment_Env.get impls
env._Exit thread state env._Exit thread state
| "System.Private.CoreLib", | "System.Private.CoreLib",
"System.Threading", "System.Threading",
"Monitor", "Monitor",
"ReliableEnter", "ReliableEnter",
[ TypeDefn.PrimitiveType PrimitiveType.Object [ ConcretePrimitive state.ConcreteTypes PrimitiveType.Object
TypeDefn.Byref (TypeDefn.PrimitiveType PrimitiveType.Boolean) ], ConcreteByref (ConcretePrimitive state.ConcreteTypes PrimitiveType.Boolean) ],
TypeDefn.Void -> ConcreteVoid state.ConcreteTypes ->
let env = ISystem_Threading_Monitor_Env.get impls let env = ISystem_Threading_Monitor_Env.get impls
env.ReliableEnter thread state env.ReliableEnter thread state
| "System.Private.CoreLib", | "System.Private.CoreLib",
"System.Threading", "System.Threading",
"Monitor", "Monitor",
"Exit", "Exit",
[ TypeDefn.PrimitiveType PrimitiveType.Object ], [ ConcretePrimitive state.ConcreteTypes PrimitiveType.Object ],
TypeDefn.Void -> ConcreteVoid state.ConcreteTypes ->
let env = ISystem_Threading_Monitor_Env.get impls let env = ISystem_Threading_Monitor_Env.get impls
env.Exit thread state 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 -> | assy, ns, typeName, methName, param, retType ->
failwith failwith
$"TODO: tried to IL-interpret a method in {assy} {ns}.{typeName} named {methName} with no implementation; {param} -> {retType}" $"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 |> ExecutionResult.Stepped
| IlOp.Switch immutableArray -> failwith "TODO: Switch unimplemented" | IlOp.Switch immutableArray -> failwith "TODO: Switch unimplemented"
| IlOp.UnaryStringToken (unaryStringTokenIlOp, stringHandle) -> | IlOp.UnaryStringToken (unaryStringTokenIlOp, stringHandle) ->
UnaryStringTokenIlOp.execute baseClassTypes unaryStringTokenIlOp stringHandle state thread UnaryStringTokenIlOp.execute loggerFactory baseClassTypes unaryStringTokenIlOp stringHandle state thread
|> ExecutionResult.Stepped |> ExecutionResult.Stepped

View File

@@ -14,6 +14,3 @@ type ManagedHeapAddress =
override this.ToString () : string = override this.ToString () : string =
match this with match this with
| ManagedHeapAddress.ManagedHeapAddress i -> $"<object #%i{i}>" | ManagedHeapAddress.ManagedHeapAddress i -> $"<object #%i{i}>"
[<Measure>]
type typeHandle

View File

@@ -41,6 +41,7 @@ type ManagedPointerSource =
| Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16 | Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
| Heap of ManagedHeapAddress | Heap of ManagedHeapAddress
| ArrayIndex of arr : ManagedHeapAddress * index : int | ArrayIndex of arr : ManagedHeapAddress * index : int
| Field of ManagedPointerSource * fieldName : string
| Null | Null
override this.ToString () = override this.ToString () =
@@ -52,6 +53,7 @@ type ManagedPointerSource =
| ManagedPointerSource.Argument (source, method, var) -> | ManagedPointerSource.Argument (source, method, var) ->
$"<argument %i{var} in method frame %i{method} of thread %O{source}>" $"<argument %i{var} in method frame %i{method} of thread %O{source}>"
| ManagedPointerSource.ArrayIndex (arr, index) -> $"<index %i{index} of array %O{arr}>" | ManagedPointerSource.ArrayIndex (arr, index) -> $"<index %i{index} of array %O{arr}>"
| ManagedPointerSource.Field (source, name) -> $"<field %s{name} of %O{source}>"
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
type UnsignedNativeIntSource = type UnsignedNativeIntSource =
@@ -63,7 +65,7 @@ type NativeIntSource =
| Verbatim of int64 | Verbatim of int64
| ManagedPointer of ManagedPointerSource | ManagedPointer of ManagedPointerSource
| FunctionPointer of MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle> | FunctionPointer of MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>
| TypeHandlePtr of int64<typeHandle> | TypeHandlePtr of ConcreteTypeHandle
override this.ToString () : string = override this.ToString () : string =
match this with match this with
@@ -71,7 +73,7 @@ type NativeIntSource =
| NativeIntSource.ManagedPointer ptr -> $"<managed pointer {ptr}>" | NativeIntSource.ManagedPointer ptr -> $"<managed pointer {ptr}>"
| NativeIntSource.FunctionPointer methodDefinition -> | NativeIntSource.FunctionPointer methodDefinition ->
$"<pointer to {methodDefinition.Name} in {methodDefinition.DeclaringType.Assembly.Name}>" $"<pointer to {methodDefinition.Name} in {methodDefinition.DeclaringType.Assembly.Name}>"
| NativeIntSource.TypeHandlePtr ptr -> $"<type ID %i{ptr}>" | NativeIntSource.TypeHandlePtr ptr -> $"<type ID %O{ptr}>"
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module NativeIntSource = module NativeIntSource =
@@ -113,26 +115,43 @@ type CliNumericType =
| Float32 of float32 | Float32 of float32
| Float64 of float | 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
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
type CliRuntimePointerSource = type CliRuntimePointerSource =
| LocalVariable of sourceThread : ThreadId * methodFrame : int * whichVar : uint16 | LocalVariable of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
| Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16 | Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
| Field of source : CliRuntimePointerSource * fieldName : string
| Heap of ManagedHeapAddress | Heap of ManagedHeapAddress
| ArrayIndex of arr : ManagedHeapAddress * index : int | ArrayIndex of arr : ManagedHeapAddress * index : int
| Null | Null
[<RequireQualifiedAccess>]
module CliRuntimePointerSource =
let rec ofManagedPointerSource (ptrSource : ManagedPointerSource) : CliRuntimePointerSource =
match ptrSource with
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, whichVar)
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
CliRuntimePointerSource.Argument (sourceThread, methodFrame, whichVar)
| ManagedPointerSource.Heap managedHeapAddress -> CliRuntimePointerSource.Heap managedHeapAddress
| ManagedPointerSource.Null -> CliRuntimePointerSource.Null
| ManagedPointerSource.ArrayIndex (arr, ind) -> CliRuntimePointerSource.ArrayIndex (arr, ind)
| ManagedPointerSource.Field (a, ind) ->
let a = ofManagedPointerSource a
CliRuntimePointerSource.Field (a, ind)
let rec toManagedPointerSource (ptrSource : CliRuntimePointerSource) : ManagedPointerSource =
match ptrSource with
| CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar)
| CliRuntimePointerSource.Argument (sourceThread, methodFrame, whichVar) ->
ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar)
| CliRuntimePointerSource.Heap managedHeapAddress -> ManagedPointerSource.Heap managedHeapAddress
| CliRuntimePointerSource.Null -> ManagedPointerSource.Null
| CliRuntimePointerSource.ArrayIndex (arr, ind) -> ManagedPointerSource.ArrayIndex (arr, ind)
| CliRuntimePointerSource.Field (a, ind) ->
let a = toManagedPointerSource a
ManagedPointerSource.Field (a, ind)
type CliRuntimePointer = type CliRuntimePointer =
| Unmanaged of int64 | Unmanaged of int64
| Managed of CliRuntimePointerSource | Managed of CliRuntimePointerSource
@@ -151,15 +170,29 @@ type CliType =
| RuntimePointer of CliRuntimePointer | RuntimePointer of CliRuntimePointer
/// This is *not* a CLI type as such. I don't actually know its status. A value type is represented simply /// 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. /// 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 and CliField =
static member OfBool (b : bool) = CliType.Bool (if b then 1uy else 0uy) {
Name : string
Contents : CliType
/// "None" for "no explicit offset specified"; we expect most offsets to be None.
Offset : int option
}
static member OfChar (c : char) = and CliValueType =
CliType.Char (byte (int c / 256), byte (int c % 256)) {
Fields : CliField list
}
static member OfManagedObject (ptr : ManagedHeapAddress) = CliType.ObjectRef (Some ptr) static member OfFields (f : CliField list) =
{
Fields = f
}
static member DereferenceField (name : string) (f : CliValueType) : CliType =
// TODO: this is wrong, it doesn't account for overlapping fields
f.Fields |> List.find (fun f -> f.Name = name) |> _.Contents
type CliTypeResolutionResult = type CliTypeResolutionResult =
| Resolved of CliType | Resolved of CliType
@@ -167,6 +200,40 @@ type CliTypeResolutionResult =
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module CliType = module 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 rec sizeOf (ty : CliType) : int =
match ty with
| CliType.Numeric ty ->
match ty 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
| CliType.Bool _ -> 1
| CliType.Char _ -> 2
| CliType.ObjectRef _ -> 8
| CliType.RuntimePointer _ -> 8
| CliType.ValueType vt ->
match vt.Fields with
| [] -> failwith "is it even possible to instantiate a value type with no fields"
| [ field ] -> sizeOf field.Contents
| fields ->
// TODO: consider struct layout (there's an `Explicit` test that will exercise that)
fields |> List.map (_.Contents >> sizeOf) |> List.sum
let zeroOfPrimitive (primitiveType : PrimitiveType) : CliType = let zeroOfPrimitive (primitiveType : PrimitiveType) : CliType =
match primitiveType with match primitiveType with
| PrimitiveType.Boolean -> CliType.Bool 0uy | PrimitiveType.Boolean -> CliType.Bool 0uy
@@ -187,8 +254,24 @@ module CliType =
| PrimitiveType.Double -> CliType.Numeric (CliNumericType.Float64 0.0) | PrimitiveType.Double -> CliType.Numeric (CliNumericType.Float64 0.0)
| PrimitiveType.String -> CliType.ObjectRef None | PrimitiveType.String -> CliType.ObjectRef None
| PrimitiveType.TypedReference -> failwith "todo" | PrimitiveType.TypedReference -> failwith "todo"
| PrimitiveType.IntPtr -> CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null) | PrimitiveType.IntPtr ->
| PrimitiveType.UIntPtr -> CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null) {
Name = "_value"
Contents = CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
Offset = Some 0
}
|> List.singleton
|> CliValueType.OfFields
|> CliType.ValueType
| PrimitiveType.UIntPtr ->
{
Name = "_value"
Contents = CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
Offset = Some 0
}
|> List.singleton
|> CliValueType.OfFields
|> CliType.ValueType
| PrimitiveType.Object -> CliType.ObjectRef None | PrimitiveType.Object -> CliType.ObjectRef None
let rec zeroOf let rec zeroOf
@@ -309,7 +392,7 @@ module CliType =
(corelib : BaseClassTypes<DumpedAssembly>) (corelib : BaseClassTypes<DumpedAssembly>)
(handle : ConcreteTypeHandle) (handle : ConcreteTypeHandle)
(concreteType : ConcreteType<ConcreteTypeHandle>) (concreteType : ConcreteType<ConcreteTypeHandle>)
(typeDef : WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>) (typeDef : WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn>)
(visited : Set<ConcreteTypeHandle>) (visited : Set<ConcreteTypeHandle>)
: CliType * AllConcreteTypes : CliType * AllConcreteTypes
= =
@@ -326,7 +409,7 @@ module CliType =
// It's a value type - need to create zero values for all non-static fields // It's a value type - need to create zero values for all non-static fields
let mutable currentConcreteTypes = concreteTypes let mutable currentConcreteTypes = concreteTypes
let fieldZeros = let vt =
typeDef.Fields typeDef.Fields
|> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static)) |> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static))
|> List.map (fun field -> |> List.map (fun field ->
@@ -342,10 +425,16 @@ module CliType =
zeroOfWithVisited currentConcreteTypes assemblies corelib fieldHandle visited zeroOfWithVisited currentConcreteTypes assemblies corelib fieldHandle visited
currentConcreteTypes <- updatedConcreteTypes2 currentConcreteTypes <- updatedConcreteTypes2
(field.Name, fieldZero)
)
CliType.ValueType fieldZeros, currentConcreteTypes {
Name = field.Name
Contents = fieldZero
Offset = field.Offset
}
)
|> CliValueType.OfFields
CliType.ValueType vt, currentConcreteTypes
else else
// It's a reference type // It's a reference type
CliType.ObjectRef None, concreteTypes CliType.ObjectRef None, concreteTypes
@@ -369,31 +458,66 @@ module CliType =
} }
// The field type might reference generic parameters of the declaring type // 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 methodGenerics = ImmutableArray.Empty // Fields don't have method generics
let loadAssembly let loadAssembly =
(assyName : AssemblyName) { new IAssemblyLoad with
(ref : AssemblyReferenceHandle) member _.LoadAssembly loaded assyName ref =
: (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly) match loaded.TryGetValue assyName.FullName with
= | true, currentAssy ->
match assemblies.TryGetValue assyName.FullName with let targetAssyRef = currentAssy.AssemblyReferences.[ref]
| true, currentAssy ->
let targetAssyRef = currentAssy.AssemblyReferences.[ref]
match assemblies.TryGetValue targetAssyRef.Name.FullName with match loaded.TryGetValue targetAssyRef.Name.FullName with
| true, targetAssy -> assemblies, targetAssy | true, targetAssy -> loaded, targetAssy
| false, _ -> | false, _ ->
failwithf "Assembly %s not loaded when trying to resolve reference" targetAssyRef.Name.FullName failwithf
| false, _ -> failwithf "Current assembly %s not loaded when trying to resolve reference" assyName.FullName "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 = let handle, newCtx =
TypeConcretization.concretizeType TypeConcretization.concretizeType
ctx ctx
loadAssembly loadAssembly
declaringType.Assembly declaringType.Assembly
typeGenerics declaringType.Generics
methodGenerics methodGenerics
fieldType fieldType
handle, newCtx.ConcreteTypes 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 ->
{
Fields =
cvt.Fields
|> List.replaceWhere (fun f ->
if f.Name = field then
{ f with
Contents = value
}
|> Some
else
None
)
}
|> 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 ->
cvt.Fields
|> List.pick (fun f -> if f.Name = field then Some f.Contents else None)

View File

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

View File

@@ -134,6 +134,21 @@ module Corelib =
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "UIntPtr" then Some v else None) |> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "UIntPtr" then Some v else None)
|> Seq.exactlyOne |> 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 Corelib = corelib
String = stringType String = stringType
@@ -157,6 +172,8 @@ module Corelib =
RuntimeTypeHandle = runtimeTypeHandleType RuntimeTypeHandle = runtimeTypeHandleType
RuntimeMethodHandle = runtimeMethodHandleType RuntimeMethodHandle = runtimeMethodHandleType
RuntimeFieldHandle = runtimeFieldHandleType RuntimeFieldHandle = runtimeFieldHandleType
RuntimeFieldInfoStub = runtimeFieldInfoStubType
RuntimeFieldHandleInternal = runtimeFieldHandleInternalType
RuntimeType = runtimeTypeType RuntimeType = runtimeTypeType
Void = voidType Void = voidType
TypedReference = typedReferenceType TypedReference = typedReferenceType

View File

@@ -10,8 +10,7 @@ type EvalStackValue =
| ObjectRef of ManagedHeapAddress | ObjectRef of ManagedHeapAddress
// Fraser thinks this isn't really a thing in CoreCLR // Fraser thinks this isn't really a thing in CoreCLR
// | TransientPointer of TransientPointerSource // | TransientPointer of TransientPointerSource
/// Mapping of field name to value | UserDefinedValueType of EvalStackValueUserType
| UserDefinedValueType of (string * EvalStackValue) list
override this.ToString () = override this.ToString () =
match this with match this with
@@ -23,12 +22,39 @@ type EvalStackValue =
| EvalStackValue.ObjectRef managedHeapAddress -> $"ObjectRef(%O{managedHeapAddress})" | EvalStackValue.ObjectRef managedHeapAddress -> $"ObjectRef(%O{managedHeapAddress})"
| EvalStackValue.UserDefinedValueType evalStackValues -> | EvalStackValue.UserDefinedValueType evalStackValues ->
let desc = let desc =
evalStackValues evalStackValues.Fields
|> List.map (snd >> string<EvalStackValue>) |> List.map (_.ContentsEval >> string<EvalStackValue>)
|> String.concat " | " |> String.concat " | "
$"Struct(%s{desc})" $"Struct(%s{desc})"
and EvalStackValueField =
{
Name : string
ContentsEval : EvalStackValue
Offset : int option
}
and EvalStackValueUserType =
{
Fields : EvalStackValueField list
}
static member DereferenceField (name : string) (this : EvalStackValueUserType) =
// TODO: this doesn't account for overlapping fields
this.Fields
|> List.pick (fun stackField ->
if stackField.Name = name then
Some stackField.ContentsEval
else
None
)
static member OfFields (fields : EvalStackValueField list) =
{
Fields = fields
}
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module EvalStackValue = module EvalStackValue =
/// The conversion performed by Conv_u. /// The conversion performed by Conv_u.
@@ -107,7 +133,11 @@ module EvalStackValue =
| CliNumericType.Int32 _ -> | CliNumericType.Int32 _ ->
match popped with match popped with
| EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.Int32 i) | EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.Int32 i)
| EvalStackValue.UserDefinedValueType [ popped ] -> toCliTypeCoerced target (snd popped) | EvalStackValue.UserDefinedValueType popped ->
match popped.Fields with
| [] -> failwith "unexpectedly empty"
| [ popped ] -> toCliTypeCoerced target popped.ContentsEval
| _ -> failwith $"TODO: %O{target}"
| i -> failwith $"TODO: %O{i}" | i -> failwith $"TODO: %O{i}"
| CliNumericType.Int64 _ -> | CliNumericType.Int64 _ ->
match popped with match popped with
@@ -123,11 +153,16 @@ module EvalStackValue =
| i -> failwith $"TODO: %O{i}" | i -> failwith $"TODO: %O{i}"
| CliNumericType.NativeInt _ -> | CliNumericType.NativeInt _ ->
match popped with match popped with
| EvalStackValue.NativeInt s -> CliNumericType.NativeInt s | EvalStackValue.NativeInt s -> CliNumericType.NativeInt s |> CliType.Numeric
| EvalStackValue.ManagedPointer ptrSrc -> | EvalStackValue.ManagedPointer ptrSrc ->
CliNumericType.NativeInt (NativeIntSource.ManagedPointer ptrSrc) CliNumericType.NativeInt (NativeIntSource.ManagedPointer ptrSrc)
|> CliType.Numeric
| EvalStackValue.UserDefinedValueType vt ->
match vt.Fields with
| [] -> failwith "unexpected"
| [ vt ] -> toCliTypeCoerced target vt.ContentsEval
| _ -> failwith $"TODO: {popped}"
| _ -> failwith $"TODO: {popped}" | _ -> failwith $"TODO: {popped}"
|> CliType.Numeric
| CliNumericType.NativeFloat f -> failwith "todo" | CliNumericType.NativeFloat f -> failwith "todo"
| CliNumericType.Int8 _ -> | CliNumericType.Int8 _ ->
match popped with match popped with
@@ -156,19 +191,13 @@ module EvalStackValue =
| CliType.ObjectRef _ -> | CliType.ObjectRef _ ->
match popped with match popped with
| EvalStackValue.ManagedPointer ptrSource -> | EvalStackValue.ManagedPointer ptrSource ->
match ptrSource with CliRuntimePointerSource.ofManagedPointerSource ptrSource
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> |> CliRuntimePointer.Managed
CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, whichVar) |> CliType.RuntimePointer
|> CliRuntimePointer.Managed | EvalStackValue.ObjectRef ptr ->
|> CliType.RuntimePointer CliRuntimePointerSource.Heap ptr
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> |> CliRuntimePointer.Managed
CliRuntimePointerSource.Argument (sourceThread, methodFrame, whichVar) |> CliType.RuntimePointer
|> 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)))
| EvalStackValue.NativeInt nativeIntSource -> | EvalStackValue.NativeInt nativeIntSource ->
match nativeIntSource with match nativeIntSource with
| NativeIntSource.Verbatim 0L -> CliType.ObjectRef None | NativeIntSource.Verbatim 0L -> CliType.ObjectRef None
@@ -180,9 +209,9 @@ module EvalStackValue =
| ManagedPointerSource.Null -> CliType.ObjectRef None | ManagedPointerSource.Null -> CliType.ObjectRef None
| ManagedPointerSource.Heap s -> CliType.ObjectRef (Some s) | ManagedPointerSource.Heap s -> CliType.ObjectRef (Some s)
| _ -> failwith "TODO" | _ -> failwith "TODO"
| EvalStackValue.UserDefinedValueType fields -> | EvalStackValue.UserDefinedValueType obj ->
match fields with match obj.Fields with
| [ esv ] -> toCliTypeCoerced target (snd esv) | [ esv ] -> toCliTypeCoerced target esv.ContentsEval
| fields -> failwith $"TODO: don't know how to coerce struct of {fields} to a pointer" | fields -> failwith $"TODO: don't know how to coerce struct of {fields} to a pointer"
| _ -> failwith $"TODO: {popped}" | _ -> failwith $"TODO: {popped}"
| CliType.Bool _ -> | CliType.Bool _ ->
@@ -196,40 +225,23 @@ module EvalStackValue =
| CliType.RuntimePointer _ -> | CliType.RuntimePointer _ ->
match popped with match popped with
| EvalStackValue.ManagedPointer src -> | EvalStackValue.ManagedPointer src ->
match src with CliRuntimePointerSource.ofManagedPointerSource src
| ManagedPointerSource.Heap addr -> CliType.OfManagedObject addr |> CliRuntimePointer.Managed
| ManagedPointerSource.Null -> CliType.ObjectRef None |> CliType.RuntimePointer
| 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.NativeInt intSrc -> | EvalStackValue.NativeInt intSrc ->
match intSrc with match intSrc with
| NativeIntSource.Verbatim i -> CliType.RuntimePointer (CliRuntimePointer.Unmanaged i) | NativeIntSource.Verbatim i -> CliType.RuntimePointer (CliRuntimePointer.Unmanaged i)
| NativeIntSource.ManagedPointer src -> | NativeIntSource.ManagedPointer src ->
match src with CliRuntimePointerSource.ofManagedPointerSource src
| ManagedPointerSource.Heap src -> |> CliRuntimePointer.Managed
CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Heap src)) |> CliType.RuntimePointer
| 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.FunctionPointer methodInfo -> | NativeIntSource.FunctionPointer methodInfo ->
CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer methodInfo)) CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer methodInfo))
| NativeIntSource.TypeHandlePtr int64 -> failwith "todo" | NativeIntSource.TypeHandlePtr int64 -> failwith "todo"
| EvalStackValue.ObjectRef addr ->
CliRuntimePointerSource.Heap addr
|> CliRuntimePointer.Managed
|> CliType.RuntimePointer
| _ -> failwith $"TODO: %O{popped}" | _ -> failwith $"TODO: %O{popped}"
| CliType.Char _ -> | CliType.Char _ ->
match popped with match popped with
@@ -238,26 +250,35 @@ module EvalStackValue =
let low = i % 256 let low = i % 256
CliType.Char (byte<int> high, byte<int> low) CliType.Char (byte<int> high, byte<int> low)
| popped -> failwith $"Unexpectedly wanted a char from {popped}" | popped -> failwith $"Unexpectedly wanted a char from {popped}"
| CliType.ValueType fields -> | CliType.ValueType vt ->
match popped with match popped with
| EvalStackValue.UserDefinedValueType popped -> | EvalStackValue.UserDefinedValueType popped ->
if fields.Length <> popped.Length then if vt.Fields.Length <> popped.Fields.Length then
// TODO: overlapping fields
failwith failwith
$"mismatch: popped value type {popped} (length %i{popped.Length}) into {fields} (length %i{fields.Length})" $"mismatch: popped value type {popped} (length %i{popped.Fields.Length}) into {vt} (length %i{vt.Fields.Length})"
List.map2 (vt.Fields, popped.Fields)
(fun (name1, v1) (name2, v2) -> ||> List.map2 (fun field1 popped ->
if name1 <> name2 then if field1.Name <> popped.Name then
failwith $"TODO: name mismatch, {name1} vs {name2}" 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}"
fields
popped let contents = toCliTypeCoerced field1.Contents popped.ContentsEval
{
CliField.Name = field1.Name
Contents = contents
Offset = field1.Offset
}
)
|> CliValueType.OfFields
|> CliType.ValueType |> CliType.ValueType
| popped -> | popped ->
match fields with match vt.Fields with
| [ _, target ] -> toCliTypeCoerced target popped | [ field ] -> toCliTypeCoerced field.Contents popped
| _ -> failwith $"TODO: {popped} into value type {target}" | _ -> failwith $"TODO: {popped} into value type {target}"
let rec ofCliType (v : CliType) : EvalStackValue = let rec ofCliType (v : CliType) : EvalStackValue =
@@ -298,9 +319,22 @@ module EvalStackValue =
|> EvalStackValue.ManagedPointer |> EvalStackValue.ManagedPointer
| CliRuntimePointerSource.Heap addr -> EvalStackValue.ObjectRef addr | CliRuntimePointerSource.Heap addr -> EvalStackValue.ObjectRef addr
| CliRuntimePointerSource.Null -> EvalStackValue.ManagedPointer ManagedPointerSource.Null | CliRuntimePointerSource.Null -> EvalStackValue.ManagedPointer ManagedPointerSource.Null
| CliRuntimePointerSource.Field (source, fieldName) ->
ManagedPointerSource.Field (CliRuntimePointerSource.toManagedPointerSource source, fieldName)
|> EvalStackValue.ManagedPointer
| CliType.ValueType fields -> | CliType.ValueType fields ->
fields // TODO: this is a bit dubious; we're being a bit sloppy with possibly-overlapping fields here
|> List.map (fun (name, f) -> name, ofCliType f) fields.Fields
|> List.map (fun field ->
let contents = ofCliType field.Contents
{
Name = field.Name
Offset = field.Offset
ContentsEval = contents
}
)
|> EvalStackValueUserType.OfFields
|> EvalStackValue.UserDefinedValueType |> EvalStackValue.UserDefinedValueType
type EvalStack = type EvalStack =
@@ -335,3 +369,5 @@ type EvalStack =
let v = EvalStackValue.ofCliType v let v = EvalStackValue.ofCliType v
EvalStack.Push' v stack EvalStack.Push' v stack
static member PeekNthFromTop (n : int) (stack : EvalStack) : EvalStackValue option = stack.Values |> List.tryItem n

View File

@@ -120,9 +120,27 @@ module EvalStackValueComparisons =
failwith "TODO" failwith "TODO"
| other1, other2 -> failwith $"Cgt.un instruction invalid for comparing {other1} vs {other2}" | 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 // Table III.4
match var1, var2 with match var1, var2 with
| EvalStackValue.UserDefinedValueType {
Fields = [ f ]
},
v -> ceq f.ContentsEval v
| u,
EvalStackValue.UserDefinedValueType {
Fields = [ f ]
} -> ceq u f.ContentsEval
| EvalStackValue.UserDefinedValueType {
Fields = []
},
EvalStackValue.UserDefinedValueType {
Fields = []
} ->
// hmm, surely this can't happen, but :shrug:
true
| EvalStackValue.UserDefinedValueType _, _
| _, EvalStackValue.UserDefinedValueType _ -> failwith $"TODO: ceq {var1} vs {var2}"
| EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> var1 = var2 | EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> var1 = var2
| EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 -> failwith "TODO: int32 CEQ nativeint" | EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 -> failwith "TODO: int32 CEQ nativeint"
| EvalStackValue.Int32 _, _ -> failwith $"bad ceq: Int32 vs {var2}" | EvalStackValue.Int32 _, _ -> failwith $"bad ceq: Int32 vs {var2}"
@@ -146,9 +164,17 @@ module EvalStackValueComparisons =
failwith $"TODO (CEQ): nativeint vs managed pointer" failwith $"TODO (CEQ): nativeint vs managed pointer"
| EvalStackValue.NativeInt _, _ -> failwith $"bad ceq: NativeInt vs {var2}" | EvalStackValue.NativeInt _, _ -> failwith $"bad ceq: NativeInt vs {var2}"
| EvalStackValue.ObjectRef var1, EvalStackValue.ObjectRef var2 -> var1 = 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"
| EvalStackValue.ObjectRef _, _ -> failwith $"bad ceq: ObjectRef vs {var2}" | EvalStackValue.ObjectRef _, _ -> failwith $"bad ceq: ObjectRef vs {var2}"
| EvalStackValue.ManagedPointer var1, EvalStackValue.ManagedPointer var2 -> var1 = var2 | EvalStackValue.ManagedPointer var1, EvalStackValue.ManagedPointer var2 -> var1 = var2
| EvalStackValue.ManagedPointer var1, EvalStackValue.NativeInt var2 -> | EvalStackValue.ManagedPointer var1, EvalStackValue.NativeInt var2 ->
failwith $"TODO (CEQ): managed pointer vs nativeint" failwith $"TODO (CEQ): managed pointer vs nativeint"
| EvalStackValue.ManagedPointer _, _ -> failwith $"bad ceq: ManagedPointer vs {var2}" | EvalStackValue.ManagedPointer _, _ -> failwith $"bad ceq: ManagedPointer vs {var2}"
| EvalStackValue.UserDefinedValueType _, _ -> failwith $"bad ceq: {var1} vs {var2}"

View File

@@ -34,7 +34,7 @@ module ExceptionHandling =
/// Check if an exception type matches a catch handler type /// Check if an exception type matches a catch handler type
let private isExceptionAssignableTo let private isExceptionAssignableTo
(exceptionTypeCrate : TypeInfoCrate) (exceptionType : ConcreteTypeHandle)
(catchTypeToken : MetadataToken) (catchTypeToken : MetadataToken)
(assemblies : ImmutableDictionary<string, DumpedAssembly>) (assemblies : ImmutableDictionary<string, DumpedAssembly>)
: bool : bool
@@ -46,7 +46,7 @@ module ExceptionHandling =
/// Also returns `isFinally : bool`: whether this is a `finally` block (as opposed to e.g. a `catch`). /// Also returns `isFinally : bool`: whether this is a `finally` block (as opposed to e.g. a `catch`).
let findExceptionHandler let findExceptionHandler
(currentPC : int) (currentPC : int)
(exceptionTypeCrate : TypeInfoCrate) (exceptionType : ConcreteTypeHandle)
(method : WoofWare.PawPrint.MethodInfo<'typeGen, 'methodGeneric, 'methodVar>) (method : WoofWare.PawPrint.MethodInfo<'typeGen, 'methodGeneric, 'methodVar>)
(assemblies : ImmutableDictionary<string, DumpedAssembly>) (assemblies : ImmutableDictionary<string, DumpedAssembly>)
: (WoofWare.PawPrint.ExceptionRegion * bool) option // handler, isFinally : (WoofWare.PawPrint.ExceptionRegion * bool) option // handler, isFinally
@@ -62,7 +62,7 @@ module ExceptionHandling =
| ExceptionRegion.Catch (typeToken, offset) -> | ExceptionRegion.Catch (typeToken, offset) ->
if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then
// Check if exception type matches // Check if exception type matches
if isExceptionAssignableTo exceptionTypeCrate typeToken assemblies then if isExceptionAssignableTo exceptionType typeToken assemblies then
Some (region, false) Some (region, false)
else else
None None

View File

@@ -62,6 +62,7 @@ module System_Threading_Monitor =
match lockObj with match lockObj with
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> | EvalStackValue.ManagedPointer ManagedPointerSource.Null ->
failwith "TODO: throw ArgumentNullException" failwith "TODO: throw ArgumentNullException"
| EvalStackValue.ObjectRef addr
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) ->
match IlMachineState.getSyncBlock addr state with match IlMachineState.getSyncBlock addr state with
| SyncBlock.Free -> | SyncBlock.Free ->
@@ -82,11 +83,12 @@ module System_Threading_Monitor =
| ManagedPointerSource.Null -> failwith "logic error" | ManagedPointerSource.Null -> failwith "logic error"
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> | ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
state state
|> IlMachineState.setLocalVariable sourceThread methodFrame whichVar (CliType.OfBool true) |> IlMachineState.setLocalVariable sourceThread methodFrame whichVar (CliType.ofBool true)
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> | ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
failwith "not really expecting to *edit* an argument..." failwith "not really expecting to *edit* an argument..."
| ManagedPointerSource.Heap addr -> failwith "todo: managed heap" | ManagedPointerSource.Heap addr -> failwith "todo: managed heap"
| ManagedPointerSource.ArrayIndex _ -> failwith "todo: array index" | ManagedPointerSource.ArrayIndex _ -> failwith "todo: array index"
| ManagedPointerSource.Field (managedPointerSource, fieldName) -> failwith "todo"
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped (state, WhatWeDid.Executed) |> ExecutionResult.Stepped
@@ -100,6 +102,7 @@ module System_Threading_Monitor =
match lockObj with match lockObj with
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> | EvalStackValue.ManagedPointer ManagedPointerSource.Null ->
failwith "TODO: throw ArgumentNullException" failwith "TODO: throw ArgumentNullException"
| EvalStackValue.ObjectRef addr
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) ->
match IlMachineState.getSyncBlock addr state with match IlMachineState.getSyncBlock addr state with
| SyncBlock.Free -> failwith "TODO: throw SynchronizationLockException" | SyncBlock.Free -> failwith "TODO: throw SynchronizationLockException"

View File

@@ -0,0 +1,146 @@
namespace WoofWare.PawPrint
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>)
(allocState : 'allocState)
(allocate : CliField list -> '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
}
|> List.singleton
|> CliValueType.OfFields
|> 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.Unmanaged newHandle)
Offset = None // no struct layout was specified
}
|> List.singleton
|> CliValueType.OfFields
|> CliType.ValueType
// https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1074
let runtimeFieldInfoStub =
// LayoutKind.Sequential
[
// If we ever implement a GC, something should change here
{
Name = "m_keepalive"
Contents = CliType.ObjectRef None
Offset = Some 0
}
{
Name = "m_c"
Contents = CliType.ObjectRef None
Offset = Some SIZEOF_OBJ
}
{
Name = "m_d"
Contents = CliType.ObjectRef None
Offset = Some (SIZEOF_OBJ * 2)
}
{
Name = "m_b"
Contents = CliType.Numeric (CliNumericType.Int32 0)
Offset = Some (SIZEOF_OBJ * 3)
}
{
Name = "m_e"
Contents = CliType.ObjectRef None
Offset = Some (SIZEOF_OBJ * 3 + SIZEOF_INT)
}
// RuntimeFieldHandleInternal: https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1048
{
Name = "m_fieldHandle"
Contents = runtimeFieldHandleInternal
Offset = Some (SIZEOF_OBJ * 4 + SIZEOF_INT)
}
]
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

View File

@@ -0,0 +1,813 @@
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"
| 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 baseClassTypes methodToCall thread state
else
None
with
| Some result -> result
| None ->
if methodToCall.Name = "GetValue" then
printfn ""
// 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 CliRuntimePointerSource.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 CliRuntimePointerSource.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

View 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 ()

View File

@@ -0,0 +1,416 @@
namespace WoofWare.PawPrint
open System
[<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"
]
|> Set.ofList
let call
(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 vt.Fields with
| [ field ] -> go field.ContentsEval
| _ -> failwith $"TODO: %O{vt}"
| 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 = Some 0
}
|> List.singleton
|> CliValueType.OfFields
IlMachineState.pushToEvalStack (CliType.ValueType vt) currentThread state
|> IlMachineState.advanceProgramCounter currentThread
Some state
| "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 ->
match ptr with
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
CliRuntimePointer.Managed (
CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, whichVar)
)
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
CliRuntimePointer.Managed (
CliRuntimePointerSource.Argument (sourceThread, methodFrame, whichVar)
)
| ManagedPointerSource.Heap managedHeapAddress ->
CliRuntimePointer.Managed (CliRuntimePointerSource.Heap managedHeapAddress)
| ManagedPointerSource.Null -> failwith "todo"
| ManagedPointerSource.ArrayIndex _ -> failwith "TODO"
| ManagedPointerSource.Field _ -> failwith "TODO"
| 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", "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", "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 {
Fields = [ f ]
} -> go f.ContentsEval
| EvalStackValue.UserDefinedValueType {
Fields = []
} -> failwith "unexpected no-fields object"
| EvalStackValue.UserDefinedValueType {
Fields = _ :: _ :: _
} ->
failwith "TODO: check overlapping fields to see if this is a pointer"
| EvalStackValue.Int32 _
| EvalStackValue.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 result =
// Some types appear circular, because they're hardcoded in the runtime. We have to special-case them.
match arg with
| ConcreteChar state.ConcreteTypes -> false
| _ ->
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 -> false
| ResolvedBaseType.ValueType ->
let nonStaticFields =
td.Fields
|> List.choose (fun field -> if field.IsStatic then None else Some field.Signature)
failwith $"TODO: search the fields on {td.Namespace}.{td.Name}: {nonStaticFields}"
| ResolvedBaseType.Object
| ResolvedBaseType.Delegate -> 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
failwith "TODO: transmute fields etc"
let state = state |> 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
| 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
View 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

View File

@@ -8,11 +8,38 @@ type SyncBlock =
type AllocatedNonArrayObject = type AllocatedNonArrayObject =
{ {
Fields : Map<string, CliType> Fields : CliField list
Type : WoofWare.PawPrint.TypeInfoCrate ConcreteType : ConcreteTypeHandle
SyncBlock : SyncBlock SyncBlock : SyncBlock
} }
static member DereferenceField (name : string) (f : AllocatedNonArrayObject) : CliType =
// TODO: this is wrong, it doesn't account for overlapping fields
f.Fields |> List.find (fun f -> f.Name = name) |> _.Contents
static member SetField (name : string) (v : CliType) (f : AllocatedNonArrayObject) : AllocatedNonArrayObject =
// TODO: this is wrong, it doesn't account for overlapping fields
let contents =
{
Name = name
Contents = v
Offset = None
}
{ f with
Fields =
f.Fields
|> List.replaceWhere (fun f ->
if f.Name = name then
Some
{ contents with
Offset = f.Offset
}
else
None
)
}
type AllocatedArray = type AllocatedArray =
{ {
Length : int Length : int
@@ -29,7 +56,9 @@ type ManagedHeap =
StringArrayData : ImmutableArray<char> StringArrayData : ImmutableArray<char>
} }
static member Empty : ManagedHeap = [<RequireQualifiedAccess>]
module ManagedHeap =
let empty : ManagedHeap =
{ {
NonArrayObjects = Map.empty NonArrayObjects = Map.empty
FirstAvailableAddress = 1 FirstAvailableAddress = 1
@@ -37,12 +66,12 @@ type ManagedHeap =
StringArrayData = ImmutableArray.Empty StringArrayData = ImmutableArray.Empty
} }
static member GetSyncBlock (addr : ManagedHeapAddress) (heap : ManagedHeap) : SyncBlock = let getSyncBlock (addr : ManagedHeapAddress) (heap : ManagedHeap) : SyncBlock =
match heap.NonArrayObjects.TryGetValue addr with match heap.NonArrayObjects.TryGetValue addr with
| false, _ -> failwith "TODO: getting sync block of array" | false, _ -> failwith "TODO: getting sync block of array"
| true, v -> v.SyncBlock | 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 match heap.NonArrayObjects.TryGetValue addr with
| false, _ -> failwith "TODO: locked on an array object" | false, _ -> failwith "TODO: locked on an array object"
| true, v -> | true, v ->
@@ -55,7 +84,7 @@ type ManagedHeap =
NonArrayObjects = heap.NonArrayObjects |> Map.add addr newV 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 addr = heap.FirstAvailableAddress
let heap = let heap =
@@ -68,7 +97,7 @@ type ManagedHeap =
ManagedHeapAddress addr, heap 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 addr = heap.StringArrayData.Length
let heap = let heap =
@@ -80,7 +109,7 @@ type ManagedHeap =
addr, heap addr, heap
static member SetStringData (addr : int) (contents : string) (heap : ManagedHeap) : ManagedHeap = let setStringData (addr : int) (contents : string) (heap : ManagedHeap) : ManagedHeap =
let newArr = let newArr =
(heap.StringArrayData, seq { 0 .. contents.Length - 1 }) (heap.StringArrayData, seq { 0 .. contents.Length - 1 })
||> Seq.fold (fun data count -> data.SetItem (addr + count, contents.[count])) ||> Seq.fold (fun data count -> data.SetItem (addr + count, contents.[count]))
@@ -92,11 +121,7 @@ type ManagedHeap =
heap heap
static member AllocateNonArray let allocateNonArray (ty : AllocatedNonArrayObject) (heap : ManagedHeap) : ManagedHeapAddress * ManagedHeap =
(ty : AllocatedNonArrayObject)
(heap : ManagedHeap)
: ManagedHeapAddress * ManagedHeap
=
let addr = heap.FirstAvailableAddress let addr = heap.FirstAvailableAddress
let heap = let heap =
@@ -109,7 +134,7 @@ type ManagedHeap =
ManagedHeapAddress addr, heap 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 match heap.Arrays.TryGetValue alloc with
| false, _ -> failwith "TODO: array not on heap" | false, _ -> failwith "TODO: array not on heap"
| true, arr -> | true, arr ->
@@ -119,13 +144,17 @@ type ManagedHeap =
arr.Elements.[offset] arr.Elements.[offset]
static member SetArrayValue let get (alloc : ManagedHeapAddress) (heap : ManagedHeap) : AllocatedNonArrayObject =
(alloc : ManagedHeapAddress) // TODO: arrays too
(offset : int) heap.NonArrayObjects.[alloc]
(v : CliType)
(heap : ManagedHeap) let set (alloc : ManagedHeapAddress) (v : AllocatedNonArrayObject) (heap : ManagedHeap) : 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 = let newArrs =
heap.Arrays heap.Arrays
|> Map.change |> Map.change

View File

@@ -137,7 +137,7 @@ and MethodState =
/// If `method` is static, `args` must be of length numParams. /// If `method` is static, `args` must be of length numParams.
static member Empty static member Empty
(concreteTypes : AllConcreteTypes) (concreteTypes : AllConcreteTypes)
(corelib : BaseClassTypes<DumpedAssembly>) (baseClassTypes : BaseClassTypes<DumpedAssembly>)
(loadedAssemblies : ImmutableDictionary<string, DumpedAssembly>) (loadedAssemblies : ImmutableDictionary<string, DumpedAssembly>)
(containingAssembly : DumpedAssembly) (containingAssembly : DumpedAssembly)
(method : WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>) (method : WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>)
@@ -172,7 +172,7 @@ and MethodState =
// Note: This assumes all types have already been concretized // Note: This assumes all types have already been concretized
// If this fails with "ConcreteTypeHandle not found", it means // If this fails with "ConcreteTypeHandle not found", it means
// we need to ensure types are concretized before creating the MethodState // 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.Add zero
result.ToImmutable () result.ToImmutable ()

View File

@@ -37,17 +37,6 @@ module NullaryIlOp =
| LdindR4 -> CliType.Numeric (CliNumericType.Float32 0.0f) | LdindR4 -> CliType.Numeric (CliNumericType.Float32 0.0f)
| LdindR8 -> CliType.Numeric (CliNumericType.Float64 0.0) | 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 // Unified Ldind implementation
let private executeLdind let private executeLdind
(targetType : LdindTargetType) (targetType : LdindTargetType)
@@ -59,11 +48,11 @@ module NullaryIlOp =
let loadedValue = let loadedValue =
match popped with match popped with
| EvalStackValue.ManagedPointer src -> loadFromPointerSource state src | EvalStackValue.ManagedPointer src -> IlMachineState.dereferencePointer state src
| EvalStackValue.NativeInt nativeIntSource -> | EvalStackValue.NativeInt nativeIntSource ->
failwith $"TODO: Native int pointer dereferencing not implemented for {targetType}" failwith $"TODO: Native int pointer dereferencing not implemented for {targetType}"
| EvalStackValue.ObjectRef managedHeapAddress -> | 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}" | other -> failwith $"Unexpected eval stack value for Ldind operation: {other}"
let loadedValue = loadedValue |> EvalStackValue.ofCliType let loadedValue = loadedValue |> EvalStackValue.ofCliType
@@ -123,6 +112,7 @@ module NullaryIlOp =
} }
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo" | ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
| ManagedPointerSource.ArrayIndex _ -> failwith "todo" | ManagedPointerSource.ArrayIndex _ -> failwith "todo"
| ManagedPointerSource.Field (managedPointerSource, fieldName) -> failwith "todo"
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo" | EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
let internal ldElem let internal ldElem
@@ -682,6 +672,7 @@ module NullaryIlOp =
let popped = let popped =
match popped with match popped with
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE" | EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE"
| EvalStackValue.ObjectRef addr
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> addr | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> addr
| _ -> failwith $"can't get len of {popped}" | _ -> failwith $"can't get len of {popped}"
@@ -775,7 +766,7 @@ module NullaryIlOp =
match match
ExceptionHandling.findExceptionHandler ExceptionHandling.findExceptionHandler
currentMethodState.IlOpIndex currentMethodState.IlOpIndex
heapObject.Type heapObject.ConcreteType
currentMethodState.ExecutingMethod currentMethodState.ExecutingMethod
state._LoadedAssemblies state._LoadedAssemblies
with with
@@ -889,16 +880,7 @@ module NullaryIlOp =
let referenced = let referenced =
match addr with match addr with
| EvalStackValue.ManagedPointer src -> | EvalStackValue.ManagedPointer src -> IlMachineState.dereferencePointer state 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"
| a -> failwith $"TODO: {a}" | a -> failwith $"TODO: {a}"
let state = let state =
@@ -926,6 +908,7 @@ module NullaryIlOp =
arr arr
(EvalStackValue.toCliTypeCoerced (CliType.ObjectRef None) value) (EvalStackValue.toCliTypeCoerced (CliType.ObjectRef None) value)
index index
| ManagedPointerSource.Field _ -> failwith "TODO"
| addr -> failwith $"TODO: {addr}" | addr -> failwith $"TODO: {addr}"
let state = state |> IlMachineState.advanceProgramCounter currentThread let state = state |> IlMachineState.advanceProgramCounter currentThread

View File

@@ -3,21 +3,33 @@ namespace WoofWare.PawPrint
open System open System
open System.Collections.Immutable open System.Collections.Immutable
open System.IO open System.IO
open System.Reflection.Metadata
open Microsoft.Extensions.Logging open Microsoft.Extensions.Logging
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module Program = module Program =
/// Returns the pointer to the resulting array on the heap. /// Returns the pointer to the resulting array on the heap.
let allocateArgs let allocateArgs
(loggerFactory : ILoggerFactory)
(args : string list) (args : string list)
(corelib : BaseClassTypes<DumpedAssembly>) (corelib : BaseClassTypes<DumpedAssembly>)
(state : IlMachineState) (state : IlMachineState)
: ManagedHeapAddress * 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 = let argsAllocations, state =
(state, args) (state, args)
||> Seq.mapFold (fun state arg -> ||> 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 // TODO: set the char values in memory
) )
@@ -28,7 +40,7 @@ module Program =
((state, 0), argsAllocations) ((state, 0), argsAllocations)
||> Seq.fold (fun (state, i) arg -> ||> Seq.fold (fun (state, i) arg ->
let state = let state =
IlMachineState.setArrayValue arrayAllocation (CliType.OfManagedObject arg) i state IlMachineState.setArrayValue arrayAllocation (CliType.ofManagedObject arg) i state
state, i + 1 state, i + 1
) )
@@ -94,7 +106,7 @@ module Program =
(currentAssembly : DumpedAssembly) (currentAssembly : DumpedAssembly)
(continueWithGeneric : (continueWithGeneric :
IlMachineState IlMachineState
-> TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> -> TypeInfo<GenericParamFromMetadata, TypeDefn>
-> DumpedAssembly -> DumpedAssembly
-> IlMachineState * BaseClassTypes<DumpedAssembly> option) -> IlMachineState * BaseClassTypes<DumpedAssembly> option)
(continueWithResolved : (continueWithResolved :
@@ -112,7 +124,7 @@ module Program =
let rec go state = let rec go state =
// Resolve the type reference to find which assembly it's in // Resolve the type reference to find which assembly it's in
match match
Assembly.resolveTypeRef state._LoadedAssemblies currentAssembly typeRef ImmutableArray.Empty Assembly.resolveTypeRef state._LoadedAssemblies currentAssembly ImmutableArray.Empty typeRef
with with
| TypeResolutionResult.FirstLoadAssy assyRef -> | TypeResolutionResult.FirstLoadAssy assyRef ->
// Need to load this assembly first // Need to load this assembly first
@@ -145,7 +157,7 @@ module Program =
let rec findCoreLibraryAssemblyFromGeneric let rec findCoreLibraryAssemblyFromGeneric
(state : IlMachineState) (state : IlMachineState)
(currentType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>) (currentType : TypeInfo<GenericParamFromMetadata, TypeDefn>)
(currentAssembly : DumpedAssembly) (currentAssembly : DumpedAssembly)
= =
match currentType.BaseType with match currentType.BaseType with
@@ -186,7 +198,7 @@ module Program =
// Use the original method from metadata, but convert FakeUnit to TypeDefn // Use the original method from metadata, but convert FakeUnit to TypeDefn
let rawMainMethod = let rawMainMethod =
mainMethodFromMetadata mainMethodFromMetadata
|> MethodInfo.mapTypeGenerics (fun i _ -> TypeDefn.GenericTypeParameter i) |> MethodInfo.mapTypeGenerics (fun (i, _) -> TypeDefn.GenericTypeParameter i.SequenceNumber)
let state, concretizedMainMethod, _ = let state, concretizedMainMethod, _ =
IlMachineState.concretizeMethodWithTypeGenerics IlMachineState.concretizeMethodWithTypeGenerics
@@ -233,7 +245,7 @@ module Program =
| Some baseTypes -> | Some baseTypes ->
let rawMainMethod = let rawMainMethod =
mainMethodFromMetadata mainMethodFromMetadata
|> MethodInfo.mapTypeGenerics (fun i _ -> TypeDefn.GenericTypeParameter i) |> MethodInfo.mapTypeGenerics (fun (i, _) -> TypeDefn.GenericTypeParameter i.SequenceNumber)
IlMachineState.concretizeMethodWithTypeGenerics IlMachineState.concretizeMethodWithTypeGenerics
loggerFactory loggerFactory
@@ -249,7 +261,11 @@ module Program =
let rec loadInitialState (state : IlMachineState) = let rec loadInitialState (state : IlMachineState) =
match match
state state
|> IlMachineState.loadClass loggerFactory (Option.toObj baseClassTypes) mainTypeHandle mainThread |> IlMachineStateExecution.loadClass
loggerFactory
(Option.toObj baseClassTypes)
mainTypeHandle
mainThread
with with
| StateLoadResult.NothingToDo ilMachineState -> ilMachineState | StateLoadResult.NothingToDo ilMachineState -> ilMachineState
| StateLoadResult.FirstLoadThis ilMachineState -> loadInitialState ilMachineState | StateLoadResult.FirstLoadThis ilMachineState -> loadInitialState ilMachineState
@@ -271,7 +287,7 @@ module Program =
let arrayAllocation, state = let arrayAllocation, state =
match mainMethodFromMetadata.Signature.ParameterTypes |> Seq.toList with match mainMethodFromMetadata.Signature.ParameterTypes |> Seq.toList with
| [ TypeDefn.OneDimensionalArrayLowerBoundZero (TypeDefn.PrimitiveType PrimitiveType.String) ] -> | [ 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" | _ -> failwith "Main method must take an array of strings; other signatures not yet implemented"
match mainMethodFromMetadata.Signature.ReturnType with match mainMethodFromMetadata.Signature.ReturnType with
@@ -297,7 +313,7 @@ module Program =
dumped dumped
concretizedMainMethod concretizedMainMethod
ImmutableArray.Empty ImmutableArray.Empty
(ImmutableArray.Create (CliType.OfManagedObject arrayAllocation)) (ImmutableArray.Create (CliType.ofManagedObject arrayAllocation))
None None
with with
| Ok s -> s | Ok s -> s
@@ -313,7 +329,7 @@ module Program =
{ state with { state with
ThreadState = state.ThreadState |> Map.add mainThread threadState ThreadState = state.ThreadState |> Map.add mainThread threadState
} }
|> IlMachineState.ensureTypeInitialised loggerFactory baseClassTypes mainThread mainTypeHandle |> IlMachineStateExecution.ensureTypeInitialised loggerFactory baseClassTypes mainThread mainTypeHandle
match init with match init with
| WhatWeDid.SuspendedForClassInit -> failwith "TODO: suspended for class init" | WhatWeDid.SuspendedForClassInit -> failwith "TODO: suspended for class init"

View File

@@ -1,18 +1,10 @@
namespace WoofWare.PawPrint namespace WoofWare.PawPrint
type CanonicalTypeIdentity =
{
AssemblyFullName : string
FullyQualifiedTypeName : string
Generics : CanonicalTypeIdentity list
}
type TypeHandleRegistry = type TypeHandleRegistry =
private private
{ {
TypeHandleToType : Map<int64<typeHandle>, CanonicalTypeIdentity> TypeHandleToType : Map<ManagedHeapAddress, ConcreteTypeHandle>
TypeToHandle : Map<CanonicalTypeIdentity, int64<typeHandle> * ManagedHeapAddress> TypeToHandle : Map<ConcreteTypeHandle, ManagedHeapAddress>
NextHandle : int64<typeHandle>
} }
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
@@ -21,44 +13,58 @@ module TypeHandleRegistry =
{ {
TypeHandleToType = Map.empty TypeHandleToType = Map.empty
TypeToHandle = Map.empty TypeToHandle = Map.empty
NextHandle = 1L<typeHandle>
} }
/// Returns an allocated System.RuntimeType as well. /// Returns an allocated System.RuntimeType as well.
let getOrAllocate let getOrAllocate
(allocState : 'allocState) (allocState : 'allocState)
(allocate : (string * CliType) list -> 'allocState -> ManagedHeapAddress * 'allocState) (allocate : CliField list -> 'allocState -> ManagedHeapAddress * 'allocState)
(def : CanonicalTypeIdentity) (def : ConcreteTypeHandle)
(reg : TypeHandleRegistry) (reg : TypeHandleRegistry)
: (int64<typeHandle> * ManagedHeapAddress) * TypeHandleRegistry * 'allocState : ManagedHeapAddress * TypeHandleRegistry * 'allocState
= =
match Map.tryFind def reg.TypeToHandle with match Map.tryFind def reg.TypeToHandle with
| Some v -> v, reg, allocState | Some v -> v, reg, allocState
| None -> | None ->
let handle = reg.NextHandle
// Here follows the class System.RuntimeType, which is an internal class type with a constructor // Here follows the class System.RuntimeType, which is an internal class type with a constructor
// whose only purpose is to throw. // whose only purpose is to throw.
// https://github.com/dotnet/runtime/blob/2b21c73fa2c32fa0195e4a411a435dda185efd08/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs#L14
// and https://github.com/dotnet/runtime/blob/f0168ee80ba9aca18a7e7140b2bb436defda623c/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs#L44
let fields = let fields =
[ [
// for the GC, I think? // for the GC, I think?
"m_keepalive", CliType.ObjectRef None {
Name = "m_keepalive"
Contents = CliType.ObjectRef None
Offset = None
}
// TODO: this is actually a System.IntPtr https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/coreclr/nativeaot/Runtime.Base/src/System/Primitives.cs#L339 // TODO: this is actually a System.IntPtr https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/coreclr/nativeaot/Runtime.Base/src/System/Primitives.cs#L339
"m_cache", CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.Verbatim 0L)) {
"m_handle", CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.TypeHandlePtr handle)) Name = "m_cache"
Contents = CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.Verbatim 0L))
Offset = None
}
{
Name = "m_handle"
Contents = CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.TypeHandlePtr def))
Offset = None
}
// This is the const -1, apparently?! // This is the const -1, apparently?!
// https://github.com/dotnet/runtime/blob/f0168ee80ba9aca18a7e7140b2bb436defda623c/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs#L2496 // https://github.com/dotnet/runtime/blob/f0168ee80ba9aca18a7e7140b2bb436defda623c/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs#L2496
"GenericParameterCountAny", CliType.Numeric (CliNumericType.Int32 -1) {
Name = "GenericParameterCountAny"
Contents = CliType.Numeric (CliNumericType.Int32 -1)
Offset = None
}
] ]
let alloc, state = allocate fields allocState let alloc, state = allocate fields allocState
let reg = let reg =
{ {
NextHandle = handle + 1L<typeHandle> TypeHandleToType = reg.TypeHandleToType |> Map.add alloc def
TypeHandleToType = reg.TypeHandleToType |> Map.add handle def TypeToHandle = reg.TypeToHandle |> Map.add def alloc
TypeToHandle = reg.TypeToHandle |> Map.add def (handle, alloc)
} }
(handle, alloc), reg, state alloc, reg, state

View File

@@ -107,8 +107,8 @@ module internal UnaryConstIlOp =
| EvalStackValue.NativeInt i -> not (NativeIntSource.isZero i) | EvalStackValue.NativeInt i -> not (NativeIntSource.isZero i)
| EvalStackValue.Float f -> failwith "TODO: Brfalse_s float semantics undocumented" | EvalStackValue.Float f -> failwith "TODO: Brfalse_s float semantics undocumented"
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> false | EvalStackValue.ManagedPointer ManagedPointerSource.Null -> false
| EvalStackValue.ObjectRef _
| EvalStackValue.ManagedPointer _ -> true | EvalStackValue.ManagedPointer _ -> true
| EvalStackValue.ObjectRef _ -> failwith "TODO: Brfalse_s ObjectRef comparison unimplemented"
| EvalStackValue.UserDefinedValueType _ -> | EvalStackValue.UserDefinedValueType _ ->
failwith "TODO: Brfalse_s UserDefinedValueType comparison unimplemented" failwith "TODO: Brfalse_s UserDefinedValueType comparison unimplemented"
@@ -129,8 +129,8 @@ module internal UnaryConstIlOp =
| EvalStackValue.NativeInt i -> not (NativeIntSource.isZero i) | EvalStackValue.NativeInt i -> not (NativeIntSource.isZero i)
| EvalStackValue.Float f -> failwith "TODO: Brtrue_s float semantics undocumented" | EvalStackValue.Float f -> failwith "TODO: Brtrue_s float semantics undocumented"
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> false | EvalStackValue.ManagedPointer ManagedPointerSource.Null -> false
| EvalStackValue.ObjectRef _
| EvalStackValue.ManagedPointer _ -> true | EvalStackValue.ManagedPointer _ -> true
| EvalStackValue.ObjectRef _ -> failwith "TODO: Brtrue_s ObjectRef comparison unimplemented"
| EvalStackValue.UserDefinedValueType _ -> | EvalStackValue.UserDefinedValueType _ ->
failwith "TODO: Brtrue_s UserDefinedValueType comparison unimplemented" failwith "TODO: Brtrue_s UserDefinedValueType comparison unimplemented"
@@ -212,7 +212,33 @@ module internal UnaryConstIlOp =
else else
id id
|> Tuple.withRight WhatWeDid.Executed |> 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 -> | Ble_s b ->
let value2, state = IlMachineState.popEvalStack currentThread state let value2, state = IlMachineState.popEvalStack currentThread state
let value1, state = IlMachineState.popEvalStack currentThread state let value1, state = IlMachineState.popEvalStack currentThread state
@@ -240,7 +266,33 @@ module internal UnaryConstIlOp =
else else
id id
|> Tuple.withRight WhatWeDid.Executed |> 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 -> | Bge_s b ->
let value2, state = IlMachineState.popEvalStack currentThread state let value2, state = IlMachineState.popEvalStack currentThread state
let value1, state = IlMachineState.popEvalStack currentThread state let value1, state = IlMachineState.popEvalStack currentThread state
@@ -351,7 +403,35 @@ module internal UnaryConstIlOp =
else else
id id
|> Tuple.withRight WhatWeDid.Executed |> 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 -> | Bge_un_s b ->
let value2, state = IlMachineState.popEvalStack currentThread state let value2, state = IlMachineState.popEvalStack currentThread state
let value1, state = IlMachineState.popEvalStack currentThread state let value1, state = IlMachineState.popEvalStack currentThread state

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,15 @@
namespace WoofWare.PawPrint namespace WoofWare.PawPrint
open System.Collections.Immutable
open System.Reflection open System.Reflection
open Microsoft.Extensions.Logging
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module internal UnaryStringTokenIlOp = module internal UnaryStringTokenIlOp =
let execute let execute
(baseClassTypes : BaseClassTypes<'a>) (loggerFactory : ILoggerFactory)
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
(op : UnaryStringTokenIlOp) (op : UnaryStringTokenIlOp)
(sh : StringToken) (sh : StringToken)
(state : IlMachineState) (state : IlMachineState)
@@ -25,6 +28,8 @@ module internal UnaryStringTokenIlOp =
let state = state |> IlMachineState.setStringData dataAddr stringToAllocate let state = state |> IlMachineState.setStringData dataAddr stringToAllocate
// String type is:
// https://github.com/dotnet/runtime/blob/f0168ee80ba9aca18a7e7140b2bb436defda623c/src/libraries/System.Private.CoreLib/src/System/String.cs#L26
let stringInstanceFields = let stringInstanceFields =
baseClassTypes.String.Fields baseClassTypes.String.Fields
|> List.choose (fun field -> |> List.choose (fun field ->
@@ -47,12 +52,29 @@ module internal UnaryStringTokenIlOp =
let fields = let fields =
[ [
"_firstChar", CliType.OfChar state.ManagedHeap.StringArrayData.[dataAddr] {
"_stringLength", CliType.Numeric (CliNumericType.Int32 stringToAllocate.Length) Name = "_firstChar"
Contents = CliType.ofChar state.ManagedHeap.StringArrayData.[dataAddr]
Offset = None
}
{
Name = "_stringLength"
Contents = CliType.Numeric (CliNumericType.Int32 stringToAllocate.Length)
Offset = None
}
] ]
let addr, state = let state, stringType =
IlMachineState.allocateManagedObject baseClassTypes.String fields state 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, addr,
{ state with { state with

View File

@@ -7,11 +7,15 @@
<ItemGroup> <ItemGroup>
<Compile Include="Tuple.fs" /> <Compile Include="Tuple.fs" />
<Compile Include="List.fs" />
<Compile Include="ImmutableArray.fs" />
<Compile Include="Result.fs" /> <Compile Include="Result.fs" />
<Compile Include="Constants.fs" />
<Compile Include="Corelib.fs" /> <Compile Include="Corelib.fs" />
<Compile Include="AbstractMachineDomain.fs" /> <Compile Include="AbstractMachineDomain.fs" />
<Compile Include="BasicCliType.fs" /> <Compile Include="BasicCliType.fs" />
<Compile Include="TypeHandleRegistry.fs" /> <Compile Include="TypeHandleRegistry.fs" />
<Compile Include="FieldHandleRegistry.fs" />
<Compile Include="ManagedHeap.fs" /> <Compile Include="ManagedHeap.fs" />
<Compile Include="TypeInitialisation.fs" /> <Compile Include="TypeInitialisation.fs" />
<Compile Include="Exceptions.fs" /> <Compile Include="Exceptions.fs" />
@@ -21,6 +25,8 @@
<Compile Include="MethodState.fs" /> <Compile Include="MethodState.fs" />
<Compile Include="ThreadState.fs" /> <Compile Include="ThreadState.fs" />
<Compile Include="IlMachineState.fs" /> <Compile Include="IlMachineState.fs" />
<Compile Include="Intrinsics.fs" />
<Compile Include="IlMachineStateExecution.fs" />
<Compile Include="NullaryIlOp.fs" /> <Compile Include="NullaryIlOp.fs" />
<Compile Include="UnaryMetadataIlOp.fs" /> <Compile Include="UnaryMetadataIlOp.fs" />
<Compile Include="UnaryStringTokenIlOp.fs" /> <Compile Include="UnaryStringTokenIlOp.fs" />

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1750836778, "lastModified": 1755736253,
"narHash": "sha256-sRLyRiC7TezRbbjGJwUFOgb2xMbSr3wQ0oJKfYlQ6s0=", "narHash": "sha256-jlIQRypNhB1PcB1BE+expE4xZeJxzoAGr1iUbHQta8s=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "d7bb1922f0bb3d0c990f56f9cdb767fdb20a5f22", "rev": "596312aae91421d6923f18cecce934a7d3bfd6b8",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -21,8 +21,8 @@
}, },
{ {
"pname": "FSharp.Core", "pname": "FSharp.Core",
"version": "9.0.202", "version": "9.0.303",
"hash": "sha256-64Gub0qemmCoMa1tDus6TeTuB1+5sHfE6KD2j4o84mA=" "hash": "sha256-AxR6wqodeU23KOTgkUfIgbavgbcSuzD4UBP+tiFydgA="
}, },
{ {
"pname": "FsUnit", "pname": "FsUnit",
@@ -31,33 +31,33 @@
}, },
{ {
"pname": "Microsoft.ApplicationInsights", "pname": "Microsoft.ApplicationInsights",
"version": "2.22.0", "version": "2.23.0",
"hash": "sha256-mUQ63atpT00r49ca50uZu2YCiLg3yd6r3HzTryqcuEA=" "hash": "sha256-5sf3bg7CZZjHseK+F3foOchEhmVeioePxMZVvS6Rjb0="
}, },
{ {
"pname": "Microsoft.AspNetCore.App.Ref", "pname": "Microsoft.AspNetCore.App.Ref",
"version": "8.0.17", "version": "8.0.19",
"hash": "sha256-NNGXfUV5RVt1VqLI99NlHoBkt2Vv/Hg3TAHzm8nGM8M=" "hash": "sha256-QySX2bih1UvwmLcn9cy1j+RuvZZwbcFKggL5Y/WcTnw="
}, },
{ {
"pname": "Microsoft.AspNetCore.App.Runtime.linux-arm64", "pname": "Microsoft.AspNetCore.App.Runtime.linux-arm64",
"version": "8.0.17", "version": "8.0.19",
"hash": "sha256-Eunz3nZF5r8a9nqwdeorQPgqd5G+Z4ddofMeAk6VmnA=" "hash": "sha256-69S+Ywyc5U8PDsVkkCVvZdHOgWb6ZZ3+f4UA0MLOLFI="
}, },
{ {
"pname": "Microsoft.AspNetCore.App.Runtime.linux-x64", "pname": "Microsoft.AspNetCore.App.Runtime.linux-x64",
"version": "8.0.17", "version": "8.0.19",
"hash": "sha256-SWdah72tC5i2CQL4mRUYfHC0Kh8+C2jiskIIeC74smY=" "hash": "sha256-u50rdLuoADSDCthx2Fg+AnT192TalHhFrzFCfMgmTn4="
}, },
{ {
"pname": "Microsoft.AspNetCore.App.Runtime.osx-arm64", "pname": "Microsoft.AspNetCore.App.Runtime.osx-arm64",
"version": "8.0.17", "version": "8.0.19",
"hash": "sha256-y55EGfQ2FzrY2X5+Ne5N3dqi5WNHkFTGVW1hEMrh6OI=" "hash": "sha256-QAKu2xD4UQ4+gX79ynNQ0aA07D+EW6Ke0jRiTZne8CY="
}, },
{ {
"pname": "Microsoft.AspNetCore.App.Runtime.osx-x64", "pname": "Microsoft.AspNetCore.App.Runtime.osx-x64",
"version": "8.0.17", "version": "8.0.19",
"hash": "sha256-uRCCNPevPemvKIuUxy/VtQlgskChbiAauMWVK/xhoc0=" "hash": "sha256-v5lzESMpodrH2grgk8ojA6BLDUfyxX5r6YY5Pgq61tA="
}, },
{ {
"pname": "Microsoft.CodeAnalysis.Analyzers", "pname": "Microsoft.CodeAnalysis.Analyzers",
@@ -106,48 +106,48 @@
}, },
{ {
"pname": "Microsoft.NETCore.App.Host.linux-arm64", "pname": "Microsoft.NETCore.App.Host.linux-arm64",
"version": "8.0.17", "version": "8.0.19",
"hash": "sha256-pzOqFCd+UrIXmWGDfds5GxkI+Asjx30yFtLIuHFu/h4=" "hash": "sha256-R86Kqzi3FUuPZlgj3zNOObLAvXtnGrS2mxsBAxWIZrY="
}, },
{ {
"pname": "Microsoft.NETCore.App.Host.linux-x64", "pname": "Microsoft.NETCore.App.Host.linux-x64",
"version": "8.0.17", "version": "8.0.19",
"hash": "sha256-AGnEGHcO2hfvChG3xEGOTA6dX4MiYPB7FoBkmWz3dc8=" "hash": "sha256-a9t/bX+WIKOu9q2R52b/hPGwOpkAgpYuP42SW2QXTak="
}, },
{ {
"pname": "Microsoft.NETCore.App.Host.osx-arm64", "pname": "Microsoft.NETCore.App.Host.osx-arm64",
"version": "8.0.17", "version": "8.0.19",
"hash": "sha256-fpMzkOWaA3OFNtHsqOk9s9xKVrcrqOyKHxE7jk8hebg=" "hash": "sha256-VaeGPR+6ApGNtQpEaky2rdUKd4X/Pp3xFGaSgUfGNiE="
}, },
{ {
"pname": "Microsoft.NETCore.App.Host.osx-x64", "pname": "Microsoft.NETCore.App.Host.osx-x64",
"version": "8.0.17", "version": "8.0.19",
"hash": "sha256-Hrn01x+S+gnGEEHhr6mN6bPyqVAhp5u3CqgWwQbh4To=" "hash": "sha256-hw7WMpTq7o544uSNvWUCIr6IRt5xZOo+eERMnwAbYyk="
}, },
{ {
"pname": "Microsoft.NETCore.App.Ref", "pname": "Microsoft.NETCore.App.Ref",
"version": "8.0.17", "version": "8.0.19",
"hash": "sha256-tKawpjkMjV0ysNIWWrgHTiLxncZJDRNiDkQBwl255l4=" "hash": "sha256-4ymel0R1c0HrX0plAWubJPzev52y0Fsx1esyQ1R7bXc="
}, },
{ {
"pname": "Microsoft.NETCore.App.Runtime.linux-arm64", "pname": "Microsoft.NETCore.App.Runtime.linux-arm64",
"version": "8.0.17", "version": "8.0.19",
"hash": "sha256-FutphE4bEjd8s6ZqpFXrD1zuCDkNCJ7Vnl0pBm86HBA=" "hash": "sha256-hqhpd8yT8bv05DhFTuMhfsaSISpLs3t4oM+R/ZkJH80="
}, },
{ {
"pname": "Microsoft.NETCore.App.Runtime.linux-x64", "pname": "Microsoft.NETCore.App.Runtime.linux-x64",
"version": "8.0.17", "version": "8.0.19",
"hash": "sha256-6YVEXiJ3b2gZAYri8iSRBdi/J+0DEl7FcwBX6h1Unkg=" "hash": "sha256-Ou51zUFTPESAAzP/z0+sLDAAXC54+oDlESBBT12M2lM="
}, },
{ {
"pname": "Microsoft.NETCore.App.Runtime.osx-arm64", "pname": "Microsoft.NETCore.App.Runtime.osx-arm64",
"version": "8.0.17", "version": "8.0.19",
"hash": "sha256-J3dfDial8GHyKQMFuBNFtOMD/mOK58vjrK2ZtrYObZg=" "hash": "sha256-IC/e8AmT9twcXwzFmXAelf4ctMbg4ancKPGrPLFMNn8="
}, },
{ {
"pname": "Microsoft.NETCore.App.Runtime.osx-x64", "pname": "Microsoft.NETCore.App.Runtime.osx-x64",
"version": "8.0.17", "version": "8.0.19",
"hash": "sha256-WnkJyhSBHMw/VtLHWy0AFwzzkbIC1YQugFuj3Adg+Ks=" "hash": "sha256-Rb0z0PT/FHyk/Fgjj9W3WDpkDMKJoXR9DgHB1cJeZSA="
}, },
{ {
"pname": "Microsoft.NETCore.Platforms", "pname": "Microsoft.NETCore.Platforms",
@@ -171,33 +171,38 @@
}, },
{ {
"pname": "Microsoft.Testing.Extensions.Telemetry", "pname": "Microsoft.Testing.Extensions.Telemetry",
"version": "1.5.3", "version": "1.7.3",
"hash": "sha256-bIXwPSa3jkr2b6xINOqMUs6/uj/r4oVFM7xq3uVIZDU=" "hash": "sha256-Z6WsY2FCUbNnT5HJd7IOrfOvqknVXp6PWzTVeb0idVg="
}, },
{ {
"pname": "Microsoft.Testing.Extensions.TrxReport.Abstractions", "pname": "Microsoft.Testing.Extensions.TrxReport.Abstractions",
"version": "1.5.3", "version": "1.7.3",
"hash": "sha256-IfMRfcyaIKEMRtx326ICKtinDBEfGw/Sv8ZHawJ96Yc=" "hash": "sha256-PTee04FHyTHx/gF5NLckXuVje807G51MzkPrZ1gkgCw="
}, },
{ {
"pname": "Microsoft.Testing.Extensions.VSTestBridge", "pname": "Microsoft.Testing.Extensions.VSTestBridge",
"version": "1.5.3", "version": "1.7.3",
"hash": "sha256-XpM/yFjhLSsuzyDV+xKubs4V1zVVYiV05E0+N4S1h0g=" "hash": "sha256-8d+wZmucfSO7PsviHjVxYB4q6NcjgxvnCUpLePq35sM="
}, },
{ {
"pname": "Microsoft.Testing.Platform", "pname": "Microsoft.Testing.Platform",
"version": "1.5.3", "version": "1.7.3",
"hash": "sha256-y61Iih6w5D79dmrj2V675mcaeIiHoj1HSa1FRit2BLM=" "hash": "sha256-cavX11P5o9rooqC3ZHw5h002OKRg2ZNR/VaRwpNTQYA="
}, },
{ {
"pname": "Microsoft.Testing.Platform.MSBuild", "pname": "Microsoft.Testing.Platform.MSBuild",
"version": "1.5.3", "version": "1.7.3",
"hash": "sha256-YspvjE5Jfi587TAfsvfDVJXNrFOkx1B3y1CKV6m7YLY=" "hash": "sha256-cREl529UQ/c5atT8KimMgrgNdy6MrAd0sBGT8sXRRPM="
},
{
"pname": "Microsoft.TestPlatform.AdapterUtilities",
"version": "17.13.0",
"hash": "sha256-Vr+3Tad/h/nk7f/5HMExn3HvCGFCarehFAzJSfCBaOc="
}, },
{ {
"pname": "Microsoft.TestPlatform.ObjectModel", "pname": "Microsoft.TestPlatform.ObjectModel",
"version": "17.12.0", "version": "17.13.0",
"hash": "sha256-3XBHBSuCxggAIlHXmKNQNlPqMqwFlM952Av6RrLw1/w=" "hash": "sha256-6S0fjfj8vA+h6dJVNwLi6oZhYDO/I/6hBZaq2VTW+Uk="
}, },
{ {
"pname": "Microsoft.TestPlatform.ObjectModel", "pname": "Microsoft.TestPlatform.ObjectModel",
@@ -231,13 +236,13 @@
}, },
{ {
"pname": "NUnit", "pname": "NUnit",
"version": "4.3.2", "version": "4.4.0",
"hash": "sha256-0RWe8uFoxYp6qhPlDDEghOMcKJgyw2ybvEoAqBLebeE=" "hash": "sha256-5geF5QOF+X/WkuCEgkPVKH4AdKx4U0olpU07S8+G3nU="
}, },
{ {
"pname": "NUnit3TestAdapter", "pname": "NUnit3TestAdapter",
"version": "5.0.0", "version": "5.1.0",
"hash": "sha256-7jZM4qAbIzne3AcdFfMbvbgogqpxvVe6q2S7Ls8xQy0=" "hash": "sha256-5z470sFjV67wGHaw8KfmSloIAYe81Dokp0f8I6zXsDc="
}, },
{ {
"pname": "runtime.any.System.Runtime", "pname": "runtime.any.System.Runtime",