mirror of
https://github.com/Smaug123/WoofWare.PawPrint
synced 2025-10-20 12:48:39 +00:00
Compare commits
35 Commits
more-ldind
...
9afc7efea1
Author | SHA1 | Date | |
---|---|---|---|
|
9afc7efea1 | ||
|
2190f148e1 | ||
|
e2e3d5c3bf | ||
|
92f22cff42 | ||
|
3bdfeaf8a1 | ||
|
5c14baec9f | ||
|
174e415c70 | ||
|
a531531aef | ||
|
4c64dd7eb5 | ||
|
cfd6716616 | ||
|
d711d6fff5 | ||
|
6dbe6614d5 | ||
|
d8b7e84f6c | ||
|
3af1f2cc97 | ||
|
56d1cf63d6 | ||
|
de1eefb436 | ||
|
30b8ec990f | ||
|
2efe9803da | ||
|
f39e7c07bf | ||
|
ad8e625678 | ||
|
0fc4335760 | ||
|
c79f775ce4 | ||
|
b5f4ed6dec | ||
|
af3e4f20f2 | ||
|
3d5667ebba | ||
|
4dbb737648 | ||
|
ddd6374c72 | ||
|
711bfd5aad | ||
|
84df17295b | ||
|
641040509f | ||
|
7c636b61a7 | ||
|
4352bfa218 | ||
|
477cb9b3fb | ||
|
c859f88f52 | ||
|
5cf0789439 |
140
CLAUDE.md
140
CLAUDE.md
@@ -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
|
||||||
@@ -81,15 +89,17 @@ dotnet publish --self-contained --runtime-id osx-arm64 CSharpExample/ && dotnet
|
|||||||
|
|
||||||
* Functions should be fully type-annotated, to give the most helpful error messages on type mismatches.
|
* Functions should be fully type-annotated, to give the most helpful error messages on type mismatches.
|
||||||
* Generally, prefer to fully-qualify discriminated union cases in `match` statements.
|
* Generally, prefer to fully-qualify discriminated union cases in `match` statements.
|
||||||
|
* ALWAYS fully-qualify enum cases when constructing them and matching on them (e.g., `PrimitiveType.Int16` not `Int16`).
|
||||||
* When writing a "TODO" `failwith`, specify in the error message what the condition is that triggers the failure, so that a failing run can easily be traced back to its cause.
|
* When writing a "TODO" `failwith`, specify in the error message what the condition is that triggers the failure, so that a failing run can easily be traced back to its cause.
|
||||||
|
* If a field name begins with an underscore (like `_LoadedAssemblies`), do not mutate it directly. Only mutate it via whatever intermediate methods have been defined for that purpose (like `WithLoadedAssembly`).
|
||||||
|
|
||||||
### Development Workflow
|
### Development Workflow
|
||||||
|
|
||||||
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.
|
||||||
@@ -98,3 +108,121 @@ It strongly prefers to avoid special-casing to get around problems, but instead
|
|||||||
### Common Gotchas
|
### Common Gotchas
|
||||||
|
|
||||||
* I've named several types in such a way as to overlap with built-in types, e.g. MethodInfo is in both WoofWare.PawPrint and System.Reflection.Metadata namespaces. Build errors can usually be fixed by fully-qualifying the type.
|
* I've named several types in such a way as to overlap with built-in types, e.g. MethodInfo is in both WoofWare.PawPrint and System.Reflection.Metadata namespaces. Build errors can usually be fixed by fully-qualifying the type.
|
||||||
|
|
||||||
|
## Type Concretization System
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
Type concretization converts abstract type definitions (`TypeDefn`) to concrete runtime types (`ConcreteTypeHandle`). This is essential because IL operations need exact types at runtime, including all generic instantiations. The system separates type concretization from IL execution, ensuring types are properly loaded before use.
|
||||||
|
|
||||||
|
### Key Concepts
|
||||||
|
|
||||||
|
#### Generic Parameters
|
||||||
|
- **Common error**: "Generic type/method parameter X out of range" probably means you're missing the proper generic context: some caller has passed the wrong list of generics through somewhere.
|
||||||
|
|
||||||
|
#### Assembly Context
|
||||||
|
TypeRefs must be resolved in the context of the assembly where they're defined, not where they're used. When resolving a TypeRef, always use the assembly that contains the TypeRef in its metadata.
|
||||||
|
|
||||||
|
### Common Scenarios and Solutions
|
||||||
|
|
||||||
|
#### Nested Generic Contexts
|
||||||
|
When inside `Array.Empty<T>()` calling `AsRef<T>`, the `T` refers to the outer method's generic parameter. Pass the current executing method's generics as context:
|
||||||
|
```fsharp
|
||||||
|
let currentMethod = state.ThreadState.[thread].MethodState.ExecutingMethod
|
||||||
|
concretizeMethodWithTypeGenerics ... currentMethod.Generics state
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Field Access in Generic Contexts
|
||||||
|
When accessing `EmptyArray<T>.Value` from within `Array.Empty<T>()`, use both type and method generics:
|
||||||
|
```fsharp
|
||||||
|
let contextTypeGenerics = currentMethod.DeclaringType.Generics
|
||||||
|
let contextMethodGenerics = currentMethod.Generics
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Call vs CallMethod
|
||||||
|
- `callMethodInActiveAssembly` expects unconcretized methods and does concretization internally
|
||||||
|
- `callMethod` expects already-concretized methods
|
||||||
|
- The refactoring changed to concretizing before calling to ensure types are loaded
|
||||||
|
|
||||||
|
### Common Pitfalls
|
||||||
|
|
||||||
|
1. **Don't create new generic parameters when they already exist**. It's *very rarely* correct to instantiate `TypeDefn.Generic{Type,Method}Parameter` yourself:
|
||||||
|
```fsharp
|
||||||
|
// Wrong: field.DeclaringType.Generics |> List.mapi (fun i _ -> TypeDefn.GenericTypeParameter i)
|
||||||
|
// Right: field.DeclaringType.Generics
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Assembly loading context**: The `loadAssembly` function expects the assembly that contains the reference as the first parameter, not the target assembly
|
||||||
|
|
||||||
|
3. **Type forwarding**: Use `Assembly.resolveTypeRef` which handles type forwarding and exported types correctly
|
||||||
|
|
||||||
|
### Key Files for Type System
|
||||||
|
|
||||||
|
- **TypeConcretisation.fs**: Core type concretization logic
|
||||||
|
- `concretizeType`: Main entry point
|
||||||
|
- `concretizeGenericInstantiation`: Handles generic instantiations like `List<T>`
|
||||||
|
- `ConcretizationContext`: Tracks state during concretization
|
||||||
|
|
||||||
|
- **IlMachineState.fs**:
|
||||||
|
- `concretizeMethodForExecution`: Prepares methods for execution
|
||||||
|
- `concretizeFieldForExecution`: Prepares fields for access
|
||||||
|
- Manages the flow of generic contexts through execution
|
||||||
|
|
||||||
|
- **Assembly.fs**:
|
||||||
|
- `resolveTypeRef`: Resolves type references across assemblies
|
||||||
|
- `resolveTypeFromName`: Handles type forwarding and exported types
|
||||||
|
- `resolveTypeFromExport`: Follows type forwarding chains
|
||||||
|
|
||||||
|
### Debugging Type Concretization Issues
|
||||||
|
|
||||||
|
When encountering errors:
|
||||||
|
1. Check the generic context (method name, generic parameters)
|
||||||
|
2. Verify the assembly context being used
|
||||||
|
3. Identify the TypeDefn variant being concretized
|
||||||
|
4. Add logging to see generic contexts: `failwithf "Failed to concretize: %A" typeDefn`
|
||||||
|
5. Check if you're in a generic method calling another generic method
|
||||||
|
6. Verify TypeRefs are being resolved in the correct assembly
|
||||||
|
|
||||||
|
## Common Type System Patterns
|
||||||
|
|
||||||
|
### Creating TypeDefn from Type Metadata
|
||||||
|
|
||||||
|
When you need to create a `TypeDefn` from type metadata (e.g., from a `TypeInfo`), there's a common pattern that involves:
|
||||||
|
1. Resolving the base type to determine `SignatureTypeKind`
|
||||||
|
2. Creating the base `TypeDefn.FromDefinition`
|
||||||
|
3. For generic types, creating a `GenericInstantiation` with type parameters
|
||||||
|
|
||||||
|
This pattern is implemented in `UnaryMetadataIlOp.lookupTypeDefn`. Example usage:
|
||||||
|
```fsharp
|
||||||
|
let state, typeDefn =
|
||||||
|
UnaryMetadataIlOp.lookupTypeDefn
|
||||||
|
baseClassTypes
|
||||||
|
state
|
||||||
|
activeAssembly
|
||||||
|
typeDefHandle
|
||||||
|
```
|
||||||
|
|
||||||
|
### Field Signature Comparison in Generic Contexts
|
||||||
|
|
||||||
|
When comparing field signatures in generic contexts (e.g., when resolving member references), signatures must be concretized before comparison. This ensures generic type parameters are properly substituted:
|
||||||
|
|
||||||
|
```fsharp
|
||||||
|
// Concretize both signatures before comparing
|
||||||
|
let state, concreteFieldSig = concretizeType ... fieldSig
|
||||||
|
let state, fieldSigConcrete = concretizeType ... fi.Signature
|
||||||
|
if fieldSigConcrete = concreteFieldSig then ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Static vs Instance Fields
|
||||||
|
|
||||||
|
When constructing objects with `Newobj`:
|
||||||
|
- Only instance fields should be included in the object
|
||||||
|
- Static fields belong to the type, not instances
|
||||||
|
- Filter using: `field.Attributes.HasFlag FieldAttributes.Static`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```fsharp
|
||||||
|
let instanceFields =
|
||||||
|
ctorType.Fields
|
||||||
|
|> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static))
|
||||||
|
```
|
||||||
|
@@ -4,6 +4,13 @@
|
|||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<WarningsAsErrors>false</WarningsAsErrors>
|
||||||
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
|
<EnableDefaultItems>false</EnableDefaultItems>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Class1.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -45,7 +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<TypeDefinitionHandle, WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter>>
|
IReadOnlyDictionary<TypeDefinitionHandle, WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, 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.
|
||||||
@@ -64,7 +64,7 @@ type DumpedAssembly =
|
|||||||
Methods :
|
Methods :
|
||||||
IReadOnlyDictionary<
|
IReadOnlyDictionary<
|
||||||
MethodDefinitionHandle,
|
MethodDefinitionHandle,
|
||||||
WoofWare.PawPrint.MethodInfo<FakeUnit, WoofWare.PawPrint.GenericParameter>
|
WoofWare.PawPrint.MethodInfo<GenericParamFromMetadata, GenericParamFromMetadata, TypeDefn>
|
||||||
>
|
>
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -75,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>>
|
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.
|
||||||
@@ -143,7 +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<string * string, WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter>>
|
ImmutableDictionary<string * string, WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn>>
|
||||||
}
|
}
|
||||||
|
|
||||||
static member internal BuildExportedTypesLookup
|
static member internal BuildExportedTypesLookup
|
||||||
@@ -199,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> seq)
|
(typeDefs : WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn> seq)
|
||||||
=
|
=
|
||||||
let result = ImmutableDictionary.CreateBuilder ()
|
let result = ImmutableDictionary.CreateBuilder ()
|
||||||
let keys = HashSet ()
|
let keys = HashSet ()
|
||||||
@@ -230,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> 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
|
||||||
@@ -247,13 +248,13 @@ type DumpedAssembly =
|
|||||||
|
|
||||||
type TypeResolutionResult =
|
type TypeResolutionResult =
|
||||||
| FirstLoadAssy of WoofWare.PawPrint.AssemblyReference
|
| FirstLoadAssy of WoofWare.PawPrint.AssemblyReference
|
||||||
| Resolved of DumpedAssembly * TypeInfo<TypeDefn>
|
| Resolved of DumpedAssembly * TypeInfo<TypeDefn, TypeDefn>
|
||||||
|
|
||||||
override this.ToString () : string =
|
override this.ToString () : string =
|
||||||
match this with
|
match this with
|
||||||
| TypeResolutionResult.FirstLoadAssy a -> $"FirstLoadAssy(%s{a.Name.FullName})"
|
| TypeResolutionResult.FirstLoadAssy a -> $"FirstLoadAssy(%s{a.Name.FullName})"
|
||||||
| TypeResolutionResult.Resolved (assy, ty) ->
|
| TypeResolutionResult.Resolved (assy, ty) ->
|
||||||
$"Resolved(%s{assy.Name.FullName}: {string<TypeInfo<TypeDefn>> ty})"
|
$"Resolved(%s{assy.Name.FullName}: {string<TypeInfo<TypeDefn, TypeDefn>> ty})"
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Assembly =
|
module Assembly =
|
||||||
@@ -423,50 +424,53 @@ module Assembly =
|
|||||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||||
(referencedInAssembly : DumpedAssembly)
|
(referencedInAssembly : DumpedAssembly)
|
||||||
(target : TypeRef)
|
(target : TypeRef)
|
||||||
(genericArgs : ImmutableArray<TypeDefn> option)
|
(genericArgs : ImmutableArray<TypeDefn>)
|
||||||
: TypeResolutionResult
|
: TypeResolutionResult
|
||||||
=
|
=
|
||||||
match target.ResolutionScope with
|
match target.ResolutionScope with
|
||||||
| TypeRefResolutionScope.Assembly r ->
|
| TypeRefResolutionScope.Assembly r ->
|
||||||
let assemblyRef = referencedInAssembly.AssemblyReferences.[r]
|
match referencedInAssembly.AssemblyReferences.TryGetValue r with
|
||||||
|
| false, _ ->
|
||||||
|
failwithf
|
||||||
|
"AssemblyReferenceHandle %A not found in assembly %s. Available references: %A"
|
||||||
|
r
|
||||||
|
referencedInAssembly.Name.FullName
|
||||||
|
(referencedInAssembly.AssemblyReferences.Keys |> Seq.toList)
|
||||||
|
| true, assemblyRef ->
|
||||||
|
|
||||||
let assemblyName = assemblyRef.Name
|
let assemblyName = assemblyRef.Name
|
||||||
|
|
||||||
match assemblies.TryGetValue assemblyName.FullName with
|
match assemblies.TryGetValue assemblyName.FullName with
|
||||||
| false, _ -> TypeResolutionResult.FirstLoadAssy assemblyRef
|
| false, _ -> TypeResolutionResult.FirstLoadAssy assemblyRef
|
||||||
| true, assy ->
|
| true, assy ->
|
||||||
|
|
||||||
let nsPath = target.Namespace.Split '.' |> Array.toList
|
let nsPath = target.Namespace.Split '.' |> Array.toList
|
||||||
|
|
||||||
let targetNs = assy.NonRootNamespaces.[nsPath]
|
let targetNs = assy.NonRootNamespaces.[nsPath]
|
||||||
|
|
||||||
let targetType =
|
let targetType =
|
||||||
targetNs.TypeDefinitions
|
targetNs.TypeDefinitions
|
||||||
|> Seq.choose (fun td ->
|
|> Seq.choose (fun td ->
|
||||||
let ty = assy.TypeDefs.[td]
|
let ty = assy.TypeDefs.[td]
|
||||||
|
|
||||||
if ty.Name = target.Name && ty.Namespace = target.Namespace then
|
if ty.Name = target.Name && ty.Namespace = target.Namespace then
|
||||||
Some ty
|
Some ty
|
||||||
else
|
else
|
||||||
None
|
None
|
||||||
)
|
|
||||||
|> Seq.toList
|
|
||||||
|
|
||||||
match targetType with
|
|
||||||
| [ t ] ->
|
|
||||||
let t =
|
|
||||||
t
|
|
||||||
|> TypeInfo.mapGeneric (fun _ param ->
|
|
||||||
match genericArgs with
|
|
||||||
| None -> failwith "got a generic TypeRef but no generic args in context"
|
|
||||||
| Some genericArgs -> genericArgs.[param.SequenceNumber]
|
|
||||||
)
|
)
|
||||||
|
|> Seq.toList
|
||||||
|
|
||||||
TypeResolutionResult.Resolved (assy, t)
|
match targetType with
|
||||||
| _ :: _ :: _ -> failwith $"Multiple matching type definitions! {nsPath} {target.Name}"
|
| [ t ] ->
|
||||||
| [] ->
|
let t =
|
||||||
match assy.ExportedType (Some target.Namespace) target.Name with
|
t |> TypeInfo.mapGeneric (fun (param, md) -> genericArgs.[param.SequenceNumber])
|
||||||
| None -> failwith $"Failed to find type {nsPath} {target.Name} in {assy.Name.FullName}!"
|
|
||||||
| Some ty -> resolveTypeFromExport assy assemblies ty genericArgs
|
TypeResolutionResult.Resolved (assy, t)
|
||||||
|
| _ :: _ :: _ -> failwith $"Multiple matching type definitions! {nsPath} {target.Name}"
|
||||||
|
| [] ->
|
||||||
|
match assy.ExportedType (Some target.Namespace) target.Name with
|
||||||
|
| None -> failwith $"Failed to find type {nsPath} {target.Name} in {assy.Name.FullName}!"
|
||||||
|
| Some ty -> resolveTypeFromExport assy assemblies ty genericArgs
|
||||||
| k -> failwith $"Unexpected: {k}"
|
| k -> failwith $"Unexpected: {k}"
|
||||||
|
|
||||||
and resolveTypeFromName
|
and resolveTypeFromName
|
||||||
@@ -474,7 +478,7 @@ module Assembly =
|
|||||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||||
(ns : string option)
|
(ns : string option)
|
||||||
(name : string)
|
(name : string)
|
||||||
(genericArgs : ImmutableArray<TypeDefn> option)
|
(genericArgs : ImmutableArray<TypeDefn>)
|
||||||
: TypeResolutionResult
|
: TypeResolutionResult
|
||||||
=
|
=
|
||||||
match ns with
|
match ns with
|
||||||
@@ -485,11 +489,7 @@ module Assembly =
|
|||||||
| Some typeDef ->
|
| Some typeDef ->
|
||||||
let typeDef =
|
let typeDef =
|
||||||
typeDef
|
typeDef
|
||||||
|> TypeInfo.mapGeneric (fun _ param ->
|
|> TypeInfo.mapGeneric (fun (param, md) -> genericArgs.[param.SequenceNumber])
|
||||||
match genericArgs with
|
|
||||||
| None -> failwith<TypeDefn> $"tried to resolve generic type {ns}.{name} but no generics in scope"
|
|
||||||
| Some genericArgs -> genericArgs.[param.SequenceNumber]
|
|
||||||
)
|
|
||||||
|
|
||||||
TypeResolutionResult.Resolved (assy, typeDef)
|
TypeResolutionResult.Resolved (assy, typeDef)
|
||||||
| None ->
|
| None ->
|
||||||
@@ -506,7 +506,7 @@ module Assembly =
|
|||||||
(fromAssembly : DumpedAssembly)
|
(fromAssembly : DumpedAssembly)
|
||||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||||
(ty : WoofWare.PawPrint.ExportedType)
|
(ty : WoofWare.PawPrint.ExportedType)
|
||||||
(genericArgs : ImmutableArray<TypeDefn> option)
|
(genericArgs : ImmutableArray<TypeDefn>)
|
||||||
: TypeResolutionResult
|
: TypeResolutionResult
|
||||||
=
|
=
|
||||||
match ty.Data with
|
match ty.Data with
|
||||||
@@ -532,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] None with
|
match Assembly.resolveTypeRef loadedAssemblies assy assy.TypeRefs.[r] ImmutableArray.Empty 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"
|
||||||
|
37
WoofWare.PawPrint.Domain/ComparableFieldDefinitionHandle.fs
Normal file
37
WoofWare.PawPrint.Domain/ComparableFieldDefinitionHandle.fs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
namespace WoofWare.PawPrint
|
||||||
|
|
||||||
|
open System
|
||||||
|
open System.Reflection.Metadata
|
||||||
|
|
||||||
|
[<CustomEquality>]
|
||||||
|
[<CustomComparison>]
|
||||||
|
type ComparableFieldDefinitionHandle =
|
||||||
|
private
|
||||||
|
{
|
||||||
|
_Inner : FieldDefinitionHandle
|
||||||
|
}
|
||||||
|
|
||||||
|
override this.Equals other =
|
||||||
|
match other with
|
||||||
|
| :? ComparableFieldDefinitionHandle as other -> this._Inner.GetHashCode () = other._Inner.GetHashCode ()
|
||||||
|
| _ -> false
|
||||||
|
|
||||||
|
override this.GetHashCode () : int = this._Inner.GetHashCode ()
|
||||||
|
|
||||||
|
interface IComparable<ComparableFieldDefinitionHandle> with
|
||||||
|
member this.CompareTo (other : ComparableFieldDefinitionHandle) : int =
|
||||||
|
this._Inner.GetHashCode().CompareTo (other._Inner.GetHashCode ())
|
||||||
|
|
||||||
|
interface IComparable with
|
||||||
|
member this.CompareTo (other : obj) : int =
|
||||||
|
match other with
|
||||||
|
| :? ComparableFieldDefinitionHandle as other ->
|
||||||
|
(this :> IComparable<ComparableFieldDefinitionHandle>).CompareTo other
|
||||||
|
| _ -> failwith "invalid comparison"
|
||||||
|
|
||||||
|
static member Make (h : FieldDefinitionHandle) =
|
||||||
|
{
|
||||||
|
_Inner = h
|
||||||
|
}
|
||||||
|
|
||||||
|
member this.Get = this._Inner
|
@@ -11,7 +11,7 @@ type ComparableTypeDefinitionHandle =
|
|||||||
_Inner : TypeDefinitionHandle
|
_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
|
||||||
|
@@ -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,20 +16,28 @@ 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`
|
||||||
|
/// instead.
|
||||||
_AssemblyName : AssemblyName
|
_AssemblyName : AssemblyName
|
||||||
|
/// Do not use this, because it's intended to be private; use the accessor `.Definition` instead.
|
||||||
_Definition : ComparableTypeDefinitionHandle
|
_Definition : ComparableTypeDefinitionHandle
|
||||||
|
/// Do not use this, because it's intended to be private; use the accessor `.Name` instead.
|
||||||
_Name : string
|
_Name : string
|
||||||
_Generics : 'typeGeneric list
|
/// Do not use this, because it's intended to be private; use the accessor `.Namespace` instead.
|
||||||
|
_Namespace : string
|
||||||
|
/// Do not use this, because it's intended to be private; use the accessor `.Generics` instead.
|
||||||
|
_Generics : 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
|
||||||
|
|
||||||
override this.Equals (other : obj) : bool =
|
override this.Equals (other : obj) : bool =
|
||||||
match other with
|
match other with
|
||||||
@@ -42,72 +50,31 @@ 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
|
|
||||||
let comp =
|
|
||||||
(this._Definition :> IComparable<ComparableTypeDefinitionHandle>).CompareTo other._Definition
|
|
||||||
|
|
||||||
if comp = 0 then
|
|
||||||
let thisGen = (this._Generics : 'typeGeneric list) :> IComparable<'typeGeneric list>
|
|
||||||
thisGen.CompareTo other._Generics
|
|
||||||
else
|
|
||||||
comp
|
|
||||||
else
|
|
||||||
comp
|
|
||||||
|
|
||||||
interface IComparable with
|
|
||||||
member this.CompareTo other =
|
|
||||||
match other with
|
|
||||||
| :? ConcreteType<'typeGeneric> as other ->
|
|
||||||
(this :> IComparable<ConcreteType<'typeGeneric>>).CompareTo other
|
|
||||||
| _ -> failwith "bad comparison"
|
|
||||||
|
|
||||||
type RuntimeConcreteType = ConcreteType<TypeDefn>
|
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module ConcreteType =
|
module ConcreteType =
|
||||||
let make
|
let make
|
||||||
(assemblyName : AssemblyName)
|
(assemblyName : AssemblyName)
|
||||||
(name : string)
|
|
||||||
(defn : TypeDefinitionHandle)
|
(defn : TypeDefinitionHandle)
|
||||||
(generics : TypeDefn list)
|
(ns : string)
|
||||||
: RuntimeConcreteType
|
(name : string)
|
||||||
|
(genericParam : ImmutableArray<GenericParamFromMetadata>)
|
||||||
|
: ConcreteType<GenericParamFromMetadata>
|
||||||
=
|
=
|
||||||
{
|
{
|
||||||
_AssemblyName = assemblyName
|
_AssemblyName = assemblyName
|
||||||
_Definition = ComparableTypeDefinitionHandle.Make defn
|
_Definition = ComparableTypeDefinitionHandle.Make defn
|
||||||
_Name = name
|
_Name = name
|
||||||
_Generics = generics
|
_Namespace = ns
|
||||||
|
_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)
|
|
||||||
(name : string)
|
|
||||||
(genericParamCount : int)
|
|
||||||
: ConcreteType<FakeUnit>
|
|
||||||
=
|
|
||||||
{
|
|
||||||
_AssemblyName = assemblyName
|
|
||||||
_Definition = ComparableTypeDefinitionHandle.Make defn
|
|
||||||
_Name = name
|
|
||||||
_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
|
||||||
_Definition = x._Definition
|
_Definition = x._Definition
|
||||||
_Generics = generics
|
_Generics = generics
|
||||||
_Name = x._Name
|
_Name = x._Name
|
||||||
|
_Namespace = x._Namespace
|
||||||
}
|
}
|
||||||
|
@@ -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,7 +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 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.
|
||||||
@@ -26,7 +25,7 @@ type FieldInfo<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :> I
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The type of the field.
|
/// The type of the field.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Signature : TypeDefn
|
Signature : 'fieldGeneric
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The attributes applied to this field, including visibility, static/instance,
|
/// The attributes applied to this field, including visibility, static/instance,
|
||||||
@@ -35,6 +34,8 @@ type FieldInfo<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :> I
|
|||||||
Attributes : FieldAttributes
|
Attributes : FieldAttributes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
member this.HasFieldRVA = this.Attributes.HasFlag FieldAttributes.HasFieldRVA
|
||||||
|
|
||||||
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}"
|
||||||
|
|
||||||
@@ -45,16 +46,22 @@ module FieldInfo =
|
|||||||
(assembly : AssemblyName)
|
(assembly : AssemblyName)
|
||||||
(handle : FieldDefinitionHandle)
|
(handle : FieldDefinitionHandle)
|
||||||
(def : FieldDefinition)
|
(def : FieldDefinition)
|
||||||
: FieldInfo<FakeUnit>
|
: 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 declaringTypeName = mr.GetString (mr.GetTypeDefinition(declaringType).Name)
|
let typeGenerics =
|
||||||
|
mr.GetTypeDefinition(declaringType).GetGenericParameters ()
|
||||||
|
|> GenericParameter.readAll mr
|
||||||
|
|
||||||
|
let decType = mr.GetTypeDefinition declaringType
|
||||||
|
let declaringTypeNamespace = mr.GetString decType.Namespace
|
||||||
|
let declaringTypeName = mr.GetString decType.Name
|
||||||
|
|
||||||
let declaringType =
|
let declaringType =
|
||||||
ConcreteType.make' assembly declaringType declaringTypeName typeGenerics
|
ConcreteType.make assembly declaringType declaringTypeNamespace declaringTypeName typeGenerics
|
||||||
|
|
||||||
{
|
{
|
||||||
Name = name
|
Name = name
|
||||||
@@ -64,12 +71,7 @@ module FieldInfo =
|
|||||||
Attributes = def.Attributes
|
Attributes = def.Attributes
|
||||||
}
|
}
|
||||||
|
|
||||||
let mapTypeGenerics<'a, 'b
|
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>)
|
|
||||||
: FieldInfo<'b>
|
|
||||||
=
|
|
||||||
let declaringType = input.DeclaringType |> ConcreteType.mapGeneric f
|
let declaringType = input.DeclaringType |> ConcreteType.mapGeneric f
|
||||||
|
|
||||||
{
|
{
|
||||||
|
85
WoofWare.PawPrint.Domain/GenericParameter.fs
Normal file
85
WoofWare.PawPrint.Domain/GenericParameter.fs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
namespace WoofWare.PawPrint
|
||||||
|
|
||||||
|
open System.Collections.Immutable
|
||||||
|
open System.Reflection
|
||||||
|
open System.Reflection.Metadata
|
||||||
|
|
||||||
|
type GenericVariance =
|
||||||
|
| Covariant
|
||||||
|
| Contravariant
|
||||||
|
|
||||||
|
type GenericConstraint =
|
||||||
|
| Reference
|
||||||
|
| NonNullableValue
|
||||||
|
|
||||||
|
type GenericParamMetadata =
|
||||||
|
{
|
||||||
|
Variance : GenericVariance option
|
||||||
|
Constraint : GenericConstraint option
|
||||||
|
RequiresParameterlessConstructor : bool
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a generic type or method parameter definition.
|
||||||
|
/// Corresponds to GenericParameter in System.Reflection.Metadata.
|
||||||
|
/// </summary>
|
||||||
|
type GenericParameter =
|
||||||
|
{
|
||||||
|
/// <summary>The name of the generic parameter (e.g., 'T', 'TKey', etc.).</summary>
|
||||||
|
Name : string
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The zero-based index of the generic parameter in the generic parameter list.
|
||||||
|
/// For example, in Dictionary<TKey, TValue&rt;, TKey has index 0 and TValue has index 1.
|
||||||
|
/// </summary>
|
||||||
|
SequenceNumber : int
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenericParamFromMetadata = GenericParameter * GenericParamMetadata
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module GenericParameter =
|
||||||
|
let readAll
|
||||||
|
(metadata : MetadataReader)
|
||||||
|
(param : GenericParameterHandleCollection)
|
||||||
|
: GenericParamFromMetadata ImmutableArray
|
||||||
|
=
|
||||||
|
param
|
||||||
|
|> Seq.map (fun param ->
|
||||||
|
let param = metadata.GetGenericParameter param
|
||||||
|
|
||||||
|
let requiresParamlessCons =
|
||||||
|
param.Attributes.HasFlag GenericParameterAttributes.DefaultConstructorConstraint
|
||||||
|
|
||||||
|
let constr =
|
||||||
|
if param.Attributes.HasFlag GenericParameterAttributes.NotNullableValueTypeConstraint then
|
||||||
|
Some GenericConstraint.NonNullableValue
|
||||||
|
elif param.Attributes.HasFlag GenericParameterAttributes.ReferenceTypeConstraint then
|
||||||
|
Some GenericConstraint.Reference
|
||||||
|
else
|
||||||
|
None
|
||||||
|
|
||||||
|
let variance =
|
||||||
|
if param.Attributes.HasFlag GenericParameterAttributes.Contravariant then
|
||||||
|
Some GenericVariance.Contravariant
|
||||||
|
elif param.Attributes.HasFlag GenericParameterAttributes.Covariant then
|
||||||
|
Some GenericVariance.Covariant
|
||||||
|
else
|
||||||
|
None
|
||||||
|
|
||||||
|
let md =
|
||||||
|
{
|
||||||
|
Variance = variance
|
||||||
|
Constraint = constr
|
||||||
|
RequiresParameterlessConstructor = requiresParamlessCons
|
||||||
|
}
|
||||||
|
|
||||||
|
let p =
|
||||||
|
{
|
||||||
|
Name = metadata.GetString param.Name
|
||||||
|
SequenceNumber = param.Index
|
||||||
|
}
|
||||||
|
|
||||||
|
p, md
|
||||||
|
)
|
||||||
|
|> ImmutableArray.CreateRange
|
@@ -78,6 +78,9 @@ type NullaryIlOp =
|
|||||||
| Not
|
| Not
|
||||||
| Shr
|
| Shr
|
||||||
| Shr_un
|
| Shr_un
|
||||||
|
/// Shifts an integer value to the left (in zeroes) by a specified number of bits, pushing the result onto the evaluation stack.
|
||||||
|
/// Top of stack is number of bits to be shifted.
|
||||||
|
/// Inserts a zero bit in the lowest positions.
|
||||||
| Shl
|
| Shl
|
||||||
| Conv_ovf_i
|
| Conv_ovf_i
|
||||||
| Conv_ovf_u
|
| Conv_ovf_u
|
||||||
@@ -123,6 +126,7 @@ type NullaryIlOp =
|
|||||||
| Localloc
|
| Localloc
|
||||||
/// Dereferences the pointer on top of the stack, and pushes the target to the stack as a type O (object reference).
|
/// Dereferences the pointer on top of the stack, and pushes the target to the stack as a type O (object reference).
|
||||||
| Ldind_ref
|
| Ldind_ref
|
||||||
|
/// Stores an object reference value at a supplied address.
|
||||||
| Stind_ref
|
| Stind_ref
|
||||||
| Stind_I
|
| Stind_I
|
||||||
| Stind_I1
|
| Stind_I1
|
||||||
@@ -166,18 +170,25 @@ type NullaryIlOp =
|
|||||||
| Ldelem_u8
|
| Ldelem_u8
|
||||||
| Ldelem_r4
|
| Ldelem_r4
|
||||||
| Ldelem_r8
|
| Ldelem_r8
|
||||||
|
/// Loads the element containing an object reference at a specified array index onto the top of the evaluation stack as type O (object reference).
|
||||||
| Ldelem_ref
|
| Ldelem_ref
|
||||||
|
/// Replaces the array element at a given index with the nativeint value on the evaluation stack.
|
||||||
| Stelem_i
|
| Stelem_i
|
||||||
|
/// Replaces the array element at a given index with the int8 value on the evaluation stack.
|
||||||
| Stelem_i1
|
| Stelem_i1
|
||||||
| Stelem_u1
|
| Stelem_u1
|
||||||
|
/// Replaces the array element at a given index with the int16 value on the evaluation stack.
|
||||||
| Stelem_i2
|
| Stelem_i2
|
||||||
| Stelem_u2
|
| Stelem_u2
|
||||||
|
/// Replaces the array element at a given index with the int32 value on the evaluation stack.
|
||||||
| Stelem_i4
|
| Stelem_i4
|
||||||
| Stelem_u4
|
| Stelem_u4
|
||||||
|
/// Replaces the array element at a given index with the int64 value on the evaluation stack.
|
||||||
| Stelem_i8
|
| Stelem_i8
|
||||||
| Stelem_u8
|
| Stelem_u8
|
||||||
| Stelem_r4
|
| Stelem_r4
|
||||||
| Stelem_r8
|
| Stelem_r8
|
||||||
|
/// Replaces the array element at a given index with the object ref value (type O) on the evaluation stack.
|
||||||
| Stelem_ref
|
| Stelem_ref
|
||||||
| Cpblk
|
| Cpblk
|
||||||
| Initblk
|
| Initblk
|
||||||
@@ -384,6 +395,7 @@ type UnaryConstIlOp =
|
|||||||
| Bge_un_s of int8
|
| Bge_un_s of int8
|
||||||
| Bgt_un_s of int8
|
| Bgt_un_s of int8
|
||||||
| Ble_un_s of int8
|
| Ble_un_s of int8
|
||||||
|
/// Transfers control to a target instruction if the first value is less than the second value.
|
||||||
| Blt_un_s of int8
|
| Blt_un_s of int8
|
||||||
| Bne_un of int32
|
| Bne_un of int32
|
||||||
| Bge_un of int32
|
| Bge_un of int32
|
||||||
@@ -529,6 +541,7 @@ type UnaryMetadataTokenIlOp =
|
|||||||
| Newobj
|
| Newobj
|
||||||
| Newarr
|
| Newarr
|
||||||
| Box
|
| Box
|
||||||
|
/// Loads the address of the array element at a specified array index onto the top of the evaluation stack as type "managed pointer"
|
||||||
| Ldelema
|
| Ldelema
|
||||||
| Isinst
|
| Isinst
|
||||||
/// Pop value from stack; pop object ref from stack; set specified field on that object to that value.
|
/// Pop value from stack; pop object ref from stack; set specified field on that object to that value.
|
||||||
|
22
WoofWare.PawPrint.Domain/ImmutableArray.fs
Normal file
22
WoofWare.PawPrint.Domain/ImmutableArray.fs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
namespace WoofWare.PawPrint
|
||||||
|
|
||||||
|
open System.Collections.Immutable
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module internal ImmutableArray =
|
||||||
|
|
||||||
|
let inline map ([<InlineIfLambda>] f : 'a -> 'b) (arr : ImmutableArray<'a>) : ImmutableArray<'b> =
|
||||||
|
let b = ImmutableArray.CreateBuilder ()
|
||||||
|
|
||||||
|
for i in arr do
|
||||||
|
b.Add (f i)
|
||||||
|
|
||||||
|
b.ToImmutable ()
|
||||||
|
|
||||||
|
let inline mapi ([<InlineIfLambda>] f : int -> 'a -> 'b) (arr : ImmutableArray<'a>) : ImmutableArray<'b> =
|
||||||
|
let b = ImmutableArray.CreateBuilder ()
|
||||||
|
|
||||||
|
for i = 0 to arr.Length - 1 do
|
||||||
|
b.Add (f i arr.[i])
|
||||||
|
|
||||||
|
b.ToImmutable ()
|
@@ -52,40 +52,6 @@ module Parameter =
|
|||||||
|
|
||||||
result.ToImmutable ()
|
result.ToImmutable ()
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a generic type or method parameter definition.
|
|
||||||
/// Corresponds to GenericParameter in System.Reflection.Metadata.
|
|
||||||
/// </summary>
|
|
||||||
type GenericParameter =
|
|
||||||
{
|
|
||||||
/// <summary>The name of the generic parameter (e.g., 'T', 'TKey', etc.).</summary>
|
|
||||||
Name : string
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The zero-based index of the generic parameter in the generic parameter list.
|
|
||||||
/// For example, in Dictionary<TKey, TValue&rt;, TKey has index 0 and TValue has index 1.
|
|
||||||
/// </summary>
|
|
||||||
SequenceNumber : int
|
|
||||||
}
|
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
|
||||||
module GenericParameter =
|
|
||||||
let readAll
|
|
||||||
(metadata : MetadataReader)
|
|
||||||
(param : GenericParameterHandleCollection)
|
|
||||||
: GenericParameter ImmutableArray
|
|
||||||
=
|
|
||||||
param
|
|
||||||
|> Seq.map (fun param ->
|
|
||||||
let param = metadata.GetGenericParameter param
|
|
||||||
|
|
||||||
{
|
|
||||||
Name = metadata.GetString param.Name
|
|
||||||
SequenceNumber = param.Index
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|> ImmutableArray.CreateRange
|
|
||||||
|
|
||||||
type ExceptionOffset =
|
type ExceptionOffset =
|
||||||
{
|
{
|
||||||
TryLength : int
|
TryLength : int
|
||||||
@@ -117,7 +83,7 @@ type ExceptionRegion =
|
|||||||
| ExceptionRegionKind.Fault -> ExceptionRegion.Fault offset
|
| ExceptionRegionKind.Fault -> ExceptionRegion.Fault offset
|
||||||
| _ -> raise (ArgumentOutOfRangeException ())
|
| _ -> raise (ArgumentOutOfRangeException ())
|
||||||
|
|
||||||
type MethodInstructions =
|
type MethodInstructions<'methodVars> =
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The IL instructions that compose the method body, along with their offset positions.
|
/// The IL instructions that compose the method body, along with their offset positions.
|
||||||
@@ -137,12 +103,14 @@ type MethodInstructions =
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
LocalsInit : bool
|
LocalsInit : bool
|
||||||
|
|
||||||
LocalVars : ImmutableArray<TypeDefn> option
|
LocalVars : ImmutableArray<'methodVars> option
|
||||||
|
|
||||||
ExceptionRegions : ImmutableArray<ExceptionRegion>
|
ExceptionRegions : ImmutableArray<ExceptionRegion>
|
||||||
}
|
}
|
||||||
|
|
||||||
static member OnlyRet : MethodInstructions =
|
[<RequireQualifiedAccess>]
|
||||||
|
module MethodInstructions =
|
||||||
|
let onlyRet () : MethodInstructions<'methodVars> =
|
||||||
let op = IlOp.Nullary NullaryIlOp.Ret
|
let op = IlOp.Nullary NullaryIlOp.Ret
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -153,12 +121,20 @@ type MethodInstructions =
|
|||||||
ExceptionRegions = ImmutableArray.Empty
|
ExceptionRegions = ImmutableArray.Empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let setLocalVars<'a, 'b> (v : ImmutableArray<'b> option) (s : MethodInstructions<'a>) : MethodInstructions<'b> =
|
||||||
|
{
|
||||||
|
Instructions = s.Instructions
|
||||||
|
Locations = s.Locations
|
||||||
|
LocalsInit = s.LocalsInit
|
||||||
|
LocalVars = v
|
||||||
|
ExceptionRegions = s.ExceptionRegions
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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
|
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.
|
||||||
@@ -178,7 +154,7 @@ type MethodInfo<'typeGenerics, 'methodGenerics
|
|||||||
///
|
///
|
||||||
/// There may be no instructions for this method, e.g. if it's an `InternalCall`.
|
/// There may be no instructions for this method, e.g. if it's an `InternalCall`.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Instructions : MethodInstructions option
|
Instructions : MethodInstructions<'methodVars> option
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The parameters of this method.
|
/// The parameters of this method.
|
||||||
@@ -193,7 +169,12 @@ type MethodInfo<'typeGenerics, 'methodGenerics
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The signature of the method, including return type and parameter types.
|
/// The signature of the method, including return type and parameter types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Signature : TypeMethodSignature<TypeDefn>
|
Signature : TypeMethodSignature<'methodVars>
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The signature as it was read from assembly metadata.
|
||||||
|
/// </summary>
|
||||||
|
RawSignature : TypeMethodSignature<TypeDefn>
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Custom attributes defined on the method. I've never yet seen one of these in practice.
|
/// Custom attributes defined on the method. I've never yet seen one of these in practice.
|
||||||
@@ -226,9 +207,12 @@ type MethodInfo<'typeGenerics, 'methodGenerics
|
|||||||
member this.IsPinvokeImpl : bool =
|
member this.IsPinvokeImpl : bool =
|
||||||
this.MethodAttributes.HasFlag MethodAttributes.PinvokeImpl
|
this.MethodAttributes.HasFlag MethodAttributes.PinvokeImpl
|
||||||
|
|
||||||
member this.IsJITIntrinsic
|
[<RequireQualifiedAccess>]
|
||||||
|
module MethodInfo =
|
||||||
|
let isJITIntrinsic
|
||||||
(getMemberRefParentType : MemberReferenceHandle -> TypeRef)
|
(getMemberRefParentType : MemberReferenceHandle -> TypeRef)
|
||||||
(methodDefs : IReadOnlyDictionary<MethodDefinitionHandle, MethodInfo<FakeUnit, GenericParameter>>)
|
(methodDefs : IReadOnlyDictionary<MethodDefinitionHandle, MethodInfo<'a, 'b, 'c>>)
|
||||||
|
(this : MethodInfo<'d, 'e, 'f>)
|
||||||
: bool
|
: bool
|
||||||
=
|
=
|
||||||
this.CustomAttributes
|
this.CustomAttributes
|
||||||
@@ -248,47 +232,68 @@ type MethodInfo<'typeGenerics, 'methodGenerics
|
|||||||
| con -> failwith $"TODO: {con}"
|
| con -> failwith $"TODO: {con}"
|
||||||
)
|
)
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
let mapTypeGenerics<'a, 'b, 'methodGen, 'vars>
|
||||||
module MethodInfo =
|
(f : 'a -> 'b)
|
||||||
let mapTypeGenerics<'a, 'b, 'methodGen
|
(m : MethodInfo<'a, 'methodGen, 'vars>)
|
||||||
when 'a :> IComparable<'a> and 'a : comparison and 'b : comparison and 'b :> IComparable<'b>>
|
: MethodInfo<'b, 'methodGen, 'vars>
|
||||||
(f : int -> 'a -> 'b)
|
|
||||||
(m : MethodInfo<'a, 'methodGen>)
|
|
||||||
: MethodInfo<'b, 'methodGen>
|
|
||||||
=
|
=
|
||||||
{
|
{
|
||||||
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
|
||||||
Parameters = m.Parameters
|
Parameters = m.Parameters
|
||||||
Generics = m.Generics
|
Generics = m.Generics
|
||||||
Signature = m.Signature
|
Signature = m.Signature
|
||||||
|
RawSignature = m.RawSignature
|
||||||
CustomAttributes = m.CustomAttributes
|
CustomAttributes = m.CustomAttributes
|
||||||
MethodAttributes = m.MethodAttributes
|
MethodAttributes = m.MethodAttributes
|
||||||
ImplAttributes = m.ImplAttributes
|
ImplAttributes = m.ImplAttributes
|
||||||
IsStatic = m.IsStatic
|
IsStatic = m.IsStatic
|
||||||
}
|
}
|
||||||
|
|
||||||
let mapMethodGenerics<'a, 'b, '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>)
|
(m : MethodInfo<'typeGen, 'a, 'vars>)
|
||||||
: MethodInfo<'typeGen, 'b>
|
: 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
|
||||||
CustomAttributes = m.CustomAttributes
|
CustomAttributes = m.CustomAttributes
|
||||||
MethodAttributes = m.MethodAttributes
|
MethodAttributes = m.MethodAttributes
|
||||||
ImplAttributes = m.ImplAttributes
|
ImplAttributes = m.ImplAttributes
|
||||||
IsStatic = m.IsStatic
|
IsStatic = m.IsStatic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let setMethodVars
|
||||||
|
(vars2 : MethodInstructions<'vars2> option)
|
||||||
|
(signature : TypeMethodSignature<'vars2>)
|
||||||
|
(m : MethodInfo<'typeGen, 'methodGen, 'vars1>)
|
||||||
|
: MethodInfo<'typeGen, 'methodGen, 'vars2>
|
||||||
|
=
|
||||||
|
{
|
||||||
|
DeclaringType = m.DeclaringType
|
||||||
|
Handle = m.Handle
|
||||||
|
Name = m.Name
|
||||||
|
Instructions = vars2
|
||||||
|
Parameters = m.Parameters
|
||||||
|
Generics = m.Generics
|
||||||
|
Signature = signature
|
||||||
|
RawSignature = m.RawSignature
|
||||||
|
CustomAttributes = m.CustomAttributes
|
||||||
|
MethodAttributes = m.MethodAttributes
|
||||||
|
ImplAttributes = m.ImplAttributes
|
||||||
|
IsStatic = m.IsStatic
|
||||||
|
}
|
||||||
|
|
||||||
type private Dummy = class end
|
type private Dummy = class end
|
||||||
|
|
||||||
@@ -637,7 +642,7 @@ module MethodInfo =
|
|||||||
(peReader : PEReader)
|
(peReader : PEReader)
|
||||||
(metadataReader : MetadataReader)
|
(metadataReader : MetadataReader)
|
||||||
(methodHandle : MethodDefinitionHandle)
|
(methodHandle : MethodDefinitionHandle)
|
||||||
: MethodInfo<FakeUnit, GenericParameter> 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 ()
|
||||||
@@ -671,11 +676,15 @@ module MethodInfo =
|
|||||||
|
|
||||||
let declaringType = methodDef.GetDeclaringType ()
|
let declaringType = methodDef.GetDeclaringType ()
|
||||||
|
|
||||||
let declaringTypeName =
|
let declaringDefn = metadataReader.GetTypeDefinition (declaringType)
|
||||||
metadataReader.GetString (metadataReader.GetTypeDefinition(declaringType).Name)
|
|
||||||
|
let declaringTypeNamespace = metadataReader.GetString declaringDefn.Namespace
|
||||||
|
|
||||||
|
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 ()
|
||||||
@@ -696,7 +705,12 @@ module MethodInfo =
|
|||||||
GenericParameter.readAll metadataReader (methodDef.GetGenericParameters ())
|
GenericParameter.readAll metadataReader (methodDef.GetGenericParameters ())
|
||||||
|
|
||||||
let declaringType =
|
let declaringType =
|
||||||
ConcreteType.make' assemblyName declaringType declaringTypeName declaringTypeGenericParams
|
ConcreteType.make
|
||||||
|
assemblyName
|
||||||
|
declaringType
|
||||||
|
declaringTypeNamespace
|
||||||
|
declaringTypeName
|
||||||
|
declaringTypeGenericParams
|
||||||
|
|
||||||
{
|
{
|
||||||
DeclaringType = declaringType
|
DeclaringType = declaringType
|
||||||
@@ -706,6 +720,7 @@ module MethodInfo =
|
|||||||
Parameters = methodParams
|
Parameters = methodParams
|
||||||
Generics = methodGenericParams
|
Generics = methodGenericParams
|
||||||
Signature = typeSig
|
Signature = typeSig
|
||||||
|
RawSignature = typeSig
|
||||||
MethodAttributes = methodDef.Attributes
|
MethodAttributes = methodDef.Attributes
|
||||||
CustomAttributes = attrs
|
CustomAttributes = attrs
|
||||||
IsStatic = not methodSig.Header.IsInstance
|
IsStatic = not methodSig.Header.IsInstance
|
||||||
@@ -715,7 +730,7 @@ module MethodInfo =
|
|||||||
|
|
||||||
let rec resolveBaseType
|
let rec resolveBaseType
|
||||||
(methodGenerics : TypeDefn ImmutableArray option)
|
(methodGenerics : TypeDefn ImmutableArray option)
|
||||||
(executingMethod : MethodInfo<TypeDefn, 'methodGen>)
|
(executingMethod : MethodInfo<TypeDefn, 'methodGen, 'vars>)
|
||||||
(td : TypeDefn)
|
(td : TypeDefn)
|
||||||
: ResolvedBaseType
|
: ResolvedBaseType
|
||||||
=
|
=
|
||||||
|
@@ -15,6 +15,17 @@ type MethodSpec =
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Method : MetadataToken
|
Method : MetadataToken
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The actual type arguments for generic instantiation.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// For <c>Volatile.Read<System.IO.TextWriter></c>, the <c>Signature</c> is <c>[System.IO.TextWriter]</c>.
|
||||||
|
/// </example>
|
||||||
|
/// <remarks>
|
||||||
|
/// The contents might themselves be <c>TypeDefn.GenericMethodParameter</c>, for example.
|
||||||
|
/// This happens when the method is itself being called from within a generic method, and the generic parameters
|
||||||
|
/// of the spec are being instantiated with generic parameters from the caller.
|
||||||
|
/// </remarks>
|
||||||
Signature : TypeDefn ImmutableArray
|
Signature : TypeDefn ImmutableArray
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1149
WoofWare.PawPrint.Domain/TypeConcretisation.fs
Normal file
1149
WoofWare.PawPrint.Domain/TypeConcretisation.fs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -46,13 +46,6 @@ type TypeMethodSignature<'Types> =
|
|||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module TypeMethodSignature =
|
module TypeMethodSignature =
|
||||||
let sigsEqual<'T> (equal : 'T -> 'T -> bool) (s1 : TypeMethodSignature<'T>) (s2 : TypeMethodSignature<'T>) : bool =
|
|
||||||
s1.GenericParameterCount = s2.GenericParameterCount
|
|
||||||
&& s1.ParameterTypes.Length = s2.ParameterTypes.Length
|
|
||||||
&& equal s1.ReturnType s2.ReturnType
|
|
||||||
&& List.zip s1.ParameterTypes s2.ParameterTypes
|
|
||||||
|> List.forall (fun (x, y) -> equal x y)
|
|
||||||
|
|
||||||
let make<'T> (p : MethodSignature<'T>) : TypeMethodSignature<'T> =
|
let make<'T> (p : MethodSignature<'T>) : TypeMethodSignature<'T> =
|
||||||
{
|
{
|
||||||
Header = ComparableSignatureHeader.Make p.Header
|
Header = ComparableSignatureHeader.Make p.Header
|
||||||
@@ -62,6 +55,34 @@ module TypeMethodSignature =
|
|||||||
RequiredParameterCount = p.RequiredParameterCount
|
RequiredParameterCount = p.RequiredParameterCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let map<'a, 'b, 'state>
|
||||||
|
(state : 'state)
|
||||||
|
(f : 'state -> 'a -> 'state * 'b)
|
||||||
|
(signature : TypeMethodSignature<'a>)
|
||||||
|
: 'state * TypeMethodSignature<'b>
|
||||||
|
=
|
||||||
|
let state, ret = f state signature.ReturnType
|
||||||
|
|
||||||
|
let state, pars =
|
||||||
|
((state, []), signature.ParameterTypes)
|
||||||
|
||> List.fold (fun (state, acc) par ->
|
||||||
|
let state, result = f state par
|
||||||
|
state, result :: acc
|
||||||
|
)
|
||||||
|
|
||||||
|
let pars = List.rev pars
|
||||||
|
|
||||||
|
let answer =
|
||||||
|
{
|
||||||
|
Header = signature.Header
|
||||||
|
ReturnType = ret
|
||||||
|
ParameterTypes = pars
|
||||||
|
GenericParameterCount = signature.GenericParameterCount
|
||||||
|
RequiredParameterCount = signature.RequiredParameterCount
|
||||||
|
}
|
||||||
|
|
||||||
|
state, answer
|
||||||
|
|
||||||
/// See I.8.2.2
|
/// See I.8.2.2
|
||||||
type PrimitiveType =
|
type PrimitiveType =
|
||||||
| Boolean
|
| Boolean
|
||||||
@@ -128,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
|
||||||
@@ -141,7 +184,21 @@ type TypeDefn =
|
|||||||
| FromDefinition of ComparableTypeDefinitionHandle * assemblyFullName : string * SignatureTypeKind
|
| FromDefinition of ComparableTypeDefinitionHandle * assemblyFullName : string * SignatureTypeKind
|
||||||
| GenericInstantiation of generic : TypeDefn * args : ImmutableArray<TypeDefn>
|
| GenericInstantiation of generic : TypeDefn * args : ImmutableArray<TypeDefn>
|
||||||
| FunctionPointer of TypeMethodSignature<TypeDefn>
|
| FunctionPointer of TypeMethodSignature<TypeDefn>
|
||||||
|
/// <summary>
|
||||||
|
/// A class/interface generic.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// The type <c>List<T></c> has a generic parameter; an instance method on that <c>List</c> would refer to
|
||||||
|
/// <c>T</c> as <c>GenericTypeParameter 0</c>.
|
||||||
|
/// </example>
|
||||||
| GenericTypeParameter of index : int
|
| GenericTypeParameter of index : int
|
||||||
|
/// <summary>
|
||||||
|
/// A method generic.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// The method <c>List.map<'a, 'b></c> takes two generic parameters; those are referred to as
|
||||||
|
/// <c>GenericMethodParameter 0</c> and <c>GenericMethodParameter 1</c> respectively.
|
||||||
|
/// </example>
|
||||||
| GenericMethodParameter of index : int
|
| GenericMethodParameter of index : int
|
||||||
/// Not really a type: this indicates the *absence* of a return value.
|
/// Not really a type: this indicates the *absence* of a return value.
|
||||||
| Void
|
| Void
|
||||||
@@ -177,30 +234,6 @@ type TypeDefn =
|
|||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module TypeDefn =
|
module TypeDefn =
|
||||||
let rec equals
|
|
||||||
(t1TypeGenerics : TypeDefn ImmutableArray)
|
|
||||||
(t1MethodGenerics : TypeDefn ImmutableArray)
|
|
||||||
(t2TypeGenerics : TypeDefn ImmutableArray)
|
|
||||||
(t2MethodGenerics : TypeDefn ImmutableArray)
|
|
||||||
(t1 : TypeDefn)
|
|
||||||
(t2 : TypeDefn)
|
|
||||||
: bool
|
|
||||||
=
|
|
||||||
match t1, t2 with
|
|
||||||
| TypeDefn.GenericTypeParameter i, j ->
|
|
||||||
equals t1TypeGenerics t1MethodGenerics t2TypeGenerics t2MethodGenerics t1TypeGenerics.[i] j
|
|
||||||
| TypeDefn.GenericMethodParameter i, j ->
|
|
||||||
equals t1TypeGenerics t1MethodGenerics t2TypeGenerics t2MethodGenerics t1MethodGenerics.[i] j
|
|
||||||
| i, TypeDefn.GenericTypeParameter j ->
|
|
||||||
equals t1TypeGenerics t1MethodGenerics t2TypeGenerics t2MethodGenerics i t2TypeGenerics.[j]
|
|
||||||
| i, TypeDefn.GenericMethodParameter j ->
|
|
||||||
equals t1TypeGenerics t1MethodGenerics t2TypeGenerics t2MethodGenerics i t2MethodGenerics.[j]
|
|
||||||
| TypeDefn.PrimitiveType x, TypeDefn.PrimitiveType y -> x = y
|
|
||||||
| TypeDefn.Array (x, shapeX), TypeDefn.Array (y, shapeY) ->
|
|
||||||
shapeX = shapeY
|
|
||||||
&& equals t1TypeGenerics t1MethodGenerics t2TypeGenerics t2TypeGenerics x y
|
|
||||||
| t1, t2 -> failwith $"TODO: {t1} = {t2}"
|
|
||||||
|
|
||||||
let isManaged (typeDefn : TypeDefn) : bool =
|
let isManaged (typeDefn : TypeDefn) : bool =
|
||||||
match typeDefn with
|
match typeDefn with
|
||||||
| TypeDefn.PrimitiveType primitiveType -> failwith "todo"
|
| TypeDefn.PrimitiveType primitiveType -> failwith "todo"
|
||||||
|
@@ -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,11 +20,20 @@ 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
type TypeInfo<'generic> =
|
type TypeInfo<'generic, 'fieldGeneric> =
|
||||||
{
|
{
|
||||||
/// <summary>The namespace containing the type.</summary>
|
/// <summary>The namespace containing the type.</summary>
|
||||||
Namespace : string
|
Namespace : string
|
||||||
@@ -34,7 +44,7 @@ type TypeInfo<'generic> =
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// All methods defined within this type.
|
/// All methods defined within this type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Methods : WoofWare.PawPrint.MethodInfo<FakeUnit, WoofWare.PawPrint.GenericParameter> 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> =
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fields defined in this type.
|
/// Fields defined in this type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Fields : WoofWare.PawPrint.FieldInfo<FakeUnit> 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> =
|
|||||||
/// </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,13 +91,38 @@ type TypeInfo<'generic> =
|
|||||||
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}"
|
||||||
|
|
||||||
|
static member NominallyEqual
|
||||||
|
(a : TypeInfo<'generic, 'fieldGeneric>)
|
||||||
|
(b : TypeInfo<'generic, 'fieldGeneric>)
|
||||||
|
: bool
|
||||||
|
=
|
||||||
|
a.Assembly.FullName = b.Assembly.FullName
|
||||||
|
&& a.Namespace = b.Namespace
|
||||||
|
&& a.Name = b.Name
|
||||||
|
&& a.Generics = b.Generics
|
||||||
|
|
||||||
type TypeInfoEval<'ret> =
|
type TypeInfoEval<'ret> =
|
||||||
abstract Eval<'a> : TypeInfo<'a> -> 'ret
|
abstract Eval<'a, 'field> : TypeInfo<'a, 'field> -> 'ret
|
||||||
|
|
||||||
type TypeInfoCrate =
|
type TypeInfoCrate =
|
||||||
abstract Apply<'ret> : TypeInfoEval<'ret> -> 'ret
|
abstract Apply<'ret> : TypeInfoEval<'ret> -> 'ret
|
||||||
@@ -97,13 +134,13 @@ type TypeInfoCrate =
|
|||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module TypeInfoCrate =
|
module TypeInfoCrate =
|
||||||
let make<'a> (t : TypeInfo<'a>) =
|
let make<'a, 'field> (t : TypeInfo<'a, 'field>) : TypeInfoCrate =
|
||||||
{ new TypeInfoCrate with
|
{ new TypeInfoCrate with
|
||||||
member _.Apply e = e.Eval t
|
member _.Apply e = e.Eval t
|
||||||
|
|
||||||
member this.ToString () =
|
member this.ToString () =
|
||||||
{ new TypeInfoEval<_> with
|
{ new TypeInfoEval<_> with
|
||||||
member _.Eval this = string<TypeInfo<_>> this
|
member _.Eval this = string<TypeInfo<_, _>> this
|
||||||
}
|
}
|
||||||
|> this.Apply
|
|> this.Apply
|
||||||
|
|
||||||
@@ -119,33 +156,48 @@ module TypeInfoCrate =
|
|||||||
type BaseClassTypes<'corelib> =
|
type BaseClassTypes<'corelib> =
|
||||||
{
|
{
|
||||||
Corelib : 'corelib
|
Corelib : 'corelib
|
||||||
String : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
String : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
Boolean : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
Boolean : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
Char : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
Char : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
SByte : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
SByte : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
Byte : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
Byte : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
Int16 : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
Int16 : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
UInt16 : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
UInt16 : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
Int32 : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
Int32 : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
UInt32 : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
UInt32 : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
Int64 : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
Int64 : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
UInt64 : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
UInt64 : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
Single : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
Single : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
Double : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
Double : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
Array : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
Array : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
Enum : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
Enum : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
ValueType : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
ValueType : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
DelegateType : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
DelegateType : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
Object : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
Object : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
RuntimeMethodHandle : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
RuntimeMethodHandle : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
RuntimeFieldHandle : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
RuntimeFieldHandle : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
RuntimeTypeHandle : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
RuntimeTypeHandle : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
RuntimeType : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
RuntimeFieldInfoStub : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
|
RuntimeFieldHandleInternal : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
|
RuntimeType : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
|
Void : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
|
TypedReference : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
|
IntPtr : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
|
UIntPtr : TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
}
|
}
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module TypeInfo =
|
module TypeInfo =
|
||||||
let withGenerics<'a, 'b> (gen : 'b ImmutableArray) (t : TypeInfo<'a>) : TypeInfo<'b> =
|
let rec fullName (get : TypeDefinitionHandle -> TypeInfo<_, _>) (ty : TypeInfo<'a, 'b>) =
|
||||||
|
if ty.IsNested then
|
||||||
|
let parent = get ty.DeclaringType |> fullName get
|
||||||
|
$"%s{parent}.{ty.Name}"
|
||||||
|
else if not (String.IsNullOrEmpty ty.Namespace) then
|
||||||
|
$"{ty.Namespace}.{ty.Name}"
|
||||||
|
else
|
||||||
|
ty.Name
|
||||||
|
|
||||||
|
let withGenerics<'a, 'b, 'field> (gen : 'b ImmutableArray) (t : TypeInfo<'a, 'field>) : TypeInfo<'b, 'field> =
|
||||||
{
|
{
|
||||||
Namespace = t.Namespace
|
Namespace = t.Namespace
|
||||||
Name = t.Name
|
Name = t.Name
|
||||||
@@ -156,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> (f : int -> 'a -> 'b) (t : TypeInfo<'a>) : TypeInfo<'b> =
|
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)
|
||||||
@@ -170,9 +224,10 @@ module TypeInfo =
|
|||||||
(thisAssembly : AssemblyName)
|
(thisAssembly : AssemblyName)
|
||||||
(metadataReader : MetadataReader)
|
(metadataReader : MetadataReader)
|
||||||
(typeHandle : TypeDefinitionHandle)
|
(typeHandle : TypeDefinitionHandle)
|
||||||
: TypeInfo<WoofWare.PawPrint.GenericParameter>
|
: 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 =
|
||||||
@@ -239,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
|
||||||
@@ -252,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>
|
||||||
@@ -275,11 +346,11 @@ module TypeInfo =
|
|||||||
else
|
else
|
||||||
None
|
None
|
||||||
|
|
||||||
let rec resolveBaseType<'corelib, 'generic>
|
let rec resolveBaseType<'corelib, 'generic, 'field>
|
||||||
(baseClassTypes : BaseClassTypes<'corelib>)
|
(baseClassTypes : BaseClassTypes<'corelib>)
|
||||||
(getName : 'corelib -> AssemblyName)
|
(getName : 'corelib -> AssemblyName)
|
||||||
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic>)
|
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic, 'field>)
|
||||||
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic>)
|
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic, 'field>)
|
||||||
(sourceAssembly : AssemblyName)
|
(sourceAssembly : AssemblyName)
|
||||||
(value : BaseTypeInfo option)
|
(value : BaseTypeInfo option)
|
||||||
: ResolvedBaseType
|
: ResolvedBaseType
|
||||||
@@ -309,18 +380,25 @@ module TypeInfo =
|
|||||||
(Some (BaseTypeInfo.TypeDef typeDefinitionHandle))
|
(Some (BaseTypeInfo.TypeDef typeDefinitionHandle))
|
||||||
|
|
||||||
let toTypeDefn
|
let toTypeDefn
|
||||||
(corelib : BaseClassTypes<'corelib>)
|
(baseClassTypes : BaseClassTypes<'corelib>)
|
||||||
(getName : 'corelib -> AssemblyName)
|
(getName : 'corelib -> AssemblyName)
|
||||||
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic>)
|
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic, 'field>)
|
||||||
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic>)
|
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic, 'field>)
|
||||||
(ty : TypeInfo<'generic>)
|
(ty : TypeInfo<TypeDefn, TypeDefn>)
|
||||||
: TypeDefn
|
: TypeDefn
|
||||||
=
|
=
|
||||||
let stk =
|
let stk =
|
||||||
match resolveBaseType corelib getName getTypeDef getTypeRef ty.Assembly ty.BaseType with
|
match resolveBaseType baseClassTypes getName getTypeDef getTypeRef ty.Assembly ty.BaseType with
|
||||||
| ResolvedBaseType.Enum
|
| ResolvedBaseType.Enum
|
||||||
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
||||||
| ResolvedBaseType.Object -> SignatureTypeKind.Class
|
| ResolvedBaseType.Object
|
||||||
| ResolvedBaseType.Delegate -> failwith "todo"
|
| ResolvedBaseType.Delegate -> SignatureTypeKind.Class
|
||||||
|
|
||||||
TypeDefn.FromDefinition (ComparableTypeDefinitionHandle.Make ty.TypeDefHandle, ty.Assembly.FullName, stk)
|
let defn =
|
||||||
|
TypeDefn.FromDefinition (ComparableTypeDefinitionHandle.Make ty.TypeDefHandle, ty.Assembly.FullName, stk)
|
||||||
|
|
||||||
|
if ty.Generics.IsEmpty then
|
||||||
|
defn
|
||||||
|
else
|
||||||
|
let generics = ty.Generics
|
||||||
|
TypeDefn.GenericInstantiation (defn, generics)
|
||||||
|
@@ -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" />
|
||||||
@@ -26,6 +29,7 @@
|
|||||||
<Compile Include="ExportedType.fs" />
|
<Compile Include="ExportedType.fs" />
|
||||||
<Compile Include="TypeSpec.fs" />
|
<Compile Include="TypeSpec.fs" />
|
||||||
<Compile Include="Assembly.fs" />
|
<Compile Include="Assembly.fs" />
|
||||||
|
<Compile Include="TypeConcretisation.fs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@@ -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
|
||||||
|
18
WoofWare.PawPrint.Test/RealRuntime.fs
Normal file
18
WoofWare.PawPrint.Test/RealRuntime.fs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
namespace WoofWare.PawPrint.Test
|
||||||
|
|
||||||
|
/// Result of executing the program using the real .NET runtime.
|
||||||
|
type RealRuntimeResult =
|
||||||
|
{
|
||||||
|
ExitCode : int
|
||||||
|
}
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module RealRuntime =
|
||||||
|
/// Execute an assembly using the real .NET runtime and capture the result.
|
||||||
|
let executeWithRealRuntime (args : string[]) (assemblyBytes : byte array) : RealRuntimeResult =
|
||||||
|
let assy = System.Reflection.Assembly.Load assemblyBytes
|
||||||
|
let result = assy.EntryPoint.Invoke ((null : obj), [| args |]) |> unbox<int>
|
||||||
|
|
||||||
|
{
|
||||||
|
ExitCode = result
|
||||||
|
}
|
@@ -32,7 +32,7 @@ module Roslyn =
|
|||||||
|> Array.map (fun path -> MetadataReference.CreateFromFile path :> MetadataReference)
|
|> Array.map (fun path -> MetadataReference.CreateFromFile path :> MetadataReference)
|
||||||
|
|
||||||
let compilationOptions =
|
let compilationOptions =
|
||||||
CSharpCompilationOptions(OutputKind.ConsoleApplication).WithAllowUnsafe (true)
|
CSharpCompilationOptions(OutputKind.ConsoleApplication).WithAllowUnsafe true
|
||||||
|
|
||||||
let compilation =
|
let compilation =
|
||||||
CSharpCompilation.Create (
|
CSharpCompilation.Create (
|
||||||
|
@@ -1,278 +0,0 @@
|
|||||||
namespace WoofWare.Pawprint.Test
|
|
||||||
|
|
||||||
open System.Collections.Immutable
|
|
||||||
open System.IO
|
|
||||||
open FsUnitTyped
|
|
||||||
open NUnit.Framework
|
|
||||||
open WoofWare.DotnetRuntimeLocator
|
|
||||||
open WoofWare.PawPrint
|
|
||||||
open WoofWare.PawPrint.ExternImplementations
|
|
||||||
open WoofWare.PawPrint.Test
|
|
||||||
|
|
||||||
[<TestFixture>]
|
|
||||||
[<Parallelizable(ParallelScope.All)>]
|
|
||||||
module TestCases =
|
|
||||||
let assy = typeof<RunResult>.Assembly
|
|
||||||
|
|
||||||
let unimplemented =
|
|
||||||
[
|
|
||||||
{
|
|
||||||
FileName = "Threads.cs"
|
|
||||||
ExpectedReturnCode = 3
|
|
||||||
NativeImpls = MockEnv.make ()
|
|
||||||
LocalVariablesOfMain = []
|
|
||||||
}
|
|
||||||
{
|
|
||||||
FileName = "ComplexTryCatch.cs"
|
|
||||||
ExpectedReturnCode = 14
|
|
||||||
NativeImpls = NativeImpls.PassThru ()
|
|
||||||
LocalVariablesOfMain =
|
|
||||||
[
|
|
||||||
4
|
|
||||||
20
|
|
||||||
115
|
|
||||||
12
|
|
||||||
1
|
|
||||||
10
|
|
||||||
2
|
|
||||||
112
|
|
||||||
12
|
|
||||||
1111
|
|
||||||
42
|
|
||||||
99
|
|
||||||
25
|
|
||||||
50
|
|
||||||
123
|
|
||||||
20
|
|
||||||
35
|
|
||||||
5
|
|
||||||
11111
|
|
||||||
100001
|
|
||||||
]
|
|
||||||
|> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i))
|
|
||||||
}
|
|
||||||
{
|
|
||||||
FileName = "WriteLine.cs"
|
|
||||||
ExpectedReturnCode = 1
|
|
||||||
NativeImpls = NativeImpls.PassThru ()
|
|
||||||
LocalVariablesOfMain = []
|
|
||||||
}
|
|
||||||
{
|
|
||||||
FileName = "ResizeArray.cs"
|
|
||||||
ExpectedReturnCode = 109
|
|
||||||
NativeImpls = MockEnv.make ()
|
|
||||||
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
let cases : TestCase list =
|
|
||||||
[
|
|
||||||
{
|
|
||||||
FileName = "NoOp.cs"
|
|
||||||
ExpectedReturnCode = 1
|
|
||||||
NativeImpls = MockEnv.make ()
|
|
||||||
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 1) ]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
FileName = "Ldind.cs"
|
|
||||||
ExpectedReturnCode = 0
|
|
||||||
NativeImpls = MockEnv.make ()
|
|
||||||
LocalVariablesOfMain =
|
|
||||||
[
|
|
||||||
// `failures`
|
|
||||||
CliType.Numeric (CliNumericType.Int32 0)
|
|
||||||
// Return value
|
|
||||||
CliType.Numeric (CliNumericType.Int32 0)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
FileName = "CustomDelegate.cs"
|
|
||||||
ExpectedReturnCode = 8
|
|
||||||
NativeImpls = MockEnv.make ()
|
|
||||||
LocalVariablesOfMain =
|
|
||||||
[
|
|
||||||
// filter
|
|
||||||
CliType.ObjectRef (Some (ManagedHeapAddress 2))
|
|
||||||
// result
|
|
||||||
CliType.OfBool true
|
|
||||||
// result, cloned for "if(result)" check
|
|
||||||
CliType.OfBool true
|
|
||||||
// ret
|
|
||||||
CliType.Numeric (CliNumericType.Int32 8)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
FileName = "ArgumentOrdering.cs"
|
|
||||||
ExpectedReturnCode = 42
|
|
||||||
NativeImpls = MockEnv.make ()
|
|
||||||
LocalVariablesOfMain =
|
|
||||||
[
|
|
||||||
// localVar
|
|
||||||
CliType.Numeric (CliNumericType.Int32 42)
|
|
||||||
// t
|
|
||||||
CliType.ValueType [ CliType.Numeric (CliNumericType.Int32 42) ]
|
|
||||||
// return value
|
|
||||||
CliType.Numeric (CliNumericType.Int32 42)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
FileName = "BasicLock.cs"
|
|
||||||
ExpectedReturnCode = 1
|
|
||||||
NativeImpls =
|
|
||||||
let mock = MockEnv.make ()
|
|
||||||
|
|
||||||
{ mock with
|
|
||||||
System_Threading_Monitor = System_Threading_Monitor.passThru
|
|
||||||
}
|
|
||||||
LocalVariablesOfMain =
|
|
||||||
[
|
|
||||||
// Four variables:
|
|
||||||
// locker
|
|
||||||
CliType.ObjectRef (Some (ManagedHeapAddress 2))
|
|
||||||
// a copy of locker, taken so that the contents of the implicit `finally` have a stable copy
|
|
||||||
CliType.ObjectRef (Some (ManagedHeapAddress 2))
|
|
||||||
// out param of `ReliableEnter`
|
|
||||||
CliType.OfBool true
|
|
||||||
// return value
|
|
||||||
CliType.Numeric (CliNumericType.Int32 1)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
FileName = "TriangleNumber.cs"
|
|
||||||
ExpectedReturnCode = 10
|
|
||||||
NativeImpls = MockEnv.make ()
|
|
||||||
LocalVariablesOfMain =
|
|
||||||
[
|
|
||||||
// answer
|
|
||||||
CliType.Numeric (CliNumericType.Int32 10)
|
|
||||||
// i
|
|
||||||
CliType.Numeric (CliNumericType.Int32 5)
|
|
||||||
// End-loop condition
|
|
||||||
CliType.OfBool false
|
|
||||||
// Ret
|
|
||||||
CliType.Numeric (CliNumericType.Int32 10)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
FileName = "InstaQuit.cs"
|
|
||||||
ExpectedReturnCode = 1
|
|
||||||
NativeImpls =
|
|
||||||
let mock = MockEnv.make ()
|
|
||||||
|
|
||||||
{ mock with
|
|
||||||
System_Environment =
|
|
||||||
{ System_EnvironmentMock.Empty with
|
|
||||||
GetProcessorCount =
|
|
||||||
fun thread state ->
|
|
||||||
let state =
|
|
||||||
state |> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 1) thread
|
|
||||||
|
|
||||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
|
||||||
_Exit =
|
|
||||||
fun thread state ->
|
|
||||||
let state = state |> IlMachineState.loadArgument thread 0
|
|
||||||
ExecutionResult.Terminated (state, thread)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LocalVariablesOfMain = []
|
|
||||||
}
|
|
||||||
{
|
|
||||||
FileName = "ExceptionWithNoOpFinally.cs"
|
|
||||||
ExpectedReturnCode = 3
|
|
||||||
NativeImpls = MockEnv.make ()
|
|
||||||
LocalVariablesOfMain =
|
|
||||||
[
|
|
||||||
// Variable 1 is `x`, variable 2 is the implicit return value
|
|
||||||
4
|
|
||||||
3
|
|
||||||
]
|
|
||||||
|> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i))
|
|
||||||
}
|
|
||||||
{
|
|
||||||
FileName = "ExceptionWithNoOpCatch.cs"
|
|
||||||
ExpectedReturnCode = 10
|
|
||||||
NativeImpls = MockEnv.make ()
|
|
||||||
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
FileName = "TryCatchWithThrowInBody.cs"
|
|
||||||
ExpectedReturnCode = 4
|
|
||||||
NativeImpls = MockEnv.make ()
|
|
||||||
LocalVariablesOfMain =
|
|
||||||
[
|
|
||||||
// one variable is x, one variable is the return value which also happens to have the same value
|
|
||||||
4
|
|
||||||
4
|
|
||||||
]
|
|
||||||
|> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i))
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
[<TestCaseSource(nameof cases)>]
|
|
||||||
let ``Can evaluate C# files`` (case : TestCase) : unit =
|
|
||||||
let source = Assembly.getEmbeddedResourceAsString case.FileName assy
|
|
||||||
let image = Roslyn.compile [ source ]
|
|
||||||
let messages, loggerFactory = LoggerFactory.makeTest ()
|
|
||||||
|
|
||||||
let dotnetRuntimes =
|
|
||||||
DotnetRuntime.SelectForDll assy.Location |> ImmutableArray.CreateRange
|
|
||||||
|
|
||||||
use peImage = new MemoryStream (image)
|
|
||||||
|
|
||||||
try
|
|
||||||
let terminalState, terminatingThread =
|
|
||||||
Program.run loggerFactory (Some case.FileName) peImage dotnetRuntimes case.NativeImpls []
|
|
||||||
|
|
||||||
let exitCode =
|
|
||||||
match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with
|
|
||||||
| [] -> failwith "expected program to return a value, but it returned void"
|
|
||||||
| head :: _ ->
|
|
||||||
match head with
|
|
||||||
| EvalStackValue.Int32 i -> i
|
|
||||||
| ret -> failwith $"expected program to return an int, but it returned %O{ret}"
|
|
||||||
|
|
||||||
exitCode |> shouldEqual case.ExpectedReturnCode
|
|
||||||
|
|
||||||
let finalVariables =
|
|
||||||
terminalState.ThreadState.[terminatingThread].MethodState.LocalVariables
|
|
||||||
|> Seq.toList
|
|
||||||
|
|
||||||
finalVariables |> shouldEqual case.LocalVariablesOfMain
|
|
||||||
|
|
||||||
with _ ->
|
|
||||||
for message in messages () do
|
|
||||||
System.Console.Error.WriteLine $"{message}"
|
|
||||||
|
|
||||||
reraise ()
|
|
||||||
|
|
||||||
[<TestCaseSource(nameof unimplemented)>]
|
|
||||||
[<Explicit "not yet implemented">]
|
|
||||||
let ``Can evaluate C# files, unimplemented`` (case : TestCase) : unit =
|
|
||||||
let source = Assembly.getEmbeddedResourceAsString case.FileName assy
|
|
||||||
let image = Roslyn.compile [ source ]
|
|
||||||
let messages, loggerFactory = LoggerFactory.makeTest ()
|
|
||||||
|
|
||||||
let dotnetRuntimes =
|
|
||||||
DotnetRuntime.SelectForDll assy.Location |> ImmutableArray.CreateRange
|
|
||||||
|
|
||||||
use peImage = new MemoryStream (image)
|
|
||||||
|
|
||||||
try
|
|
||||||
let terminalState, terminatingThread =
|
|
||||||
Program.run loggerFactory (Some case.FileName) peImage dotnetRuntimes case.NativeImpls []
|
|
||||||
|
|
||||||
let exitCode =
|
|
||||||
match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with
|
|
||||||
| [] -> failwith "expected program to return a value, but it returned void"
|
|
||||||
| head :: _ ->
|
|
||||||
match head with
|
|
||||||
| EvalStackValue.Int32 i -> i
|
|
||||||
| ret -> failwith $"expected program to return an int, but it returned %O{ret}"
|
|
||||||
|
|
||||||
exitCode |> shouldEqual case.ExpectedReturnCode
|
|
||||||
|
|
||||||
with _ ->
|
|
||||||
for message in messages () do
|
|
||||||
System.Console.Error.WriteLine $"{message}"
|
|
||||||
|
|
||||||
reraise ()
|
|
@@ -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
|
|
||||||
}
|
}
|
||||||
|
@@ -1,47 +0,0 @@
|
|||||||
namespace WoofWare.Pawprint.Test
|
|
||||||
|
|
||||||
open System
|
|
||||||
open System.Collections.Immutable
|
|
||||||
open System.IO
|
|
||||||
open FsUnitTyped
|
|
||||||
open NUnit.Framework
|
|
||||||
open WoofWare.PawPrint
|
|
||||||
open WoofWare.PawPrint.ExternImplementations
|
|
||||||
open WoofWare.PawPrint.Test
|
|
||||||
open WoofWare.DotnetRuntimeLocator
|
|
||||||
|
|
||||||
[<TestFixture>]
|
|
||||||
module TestHelloWorld =
|
|
||||||
let assy = typeof<RunResult>.Assembly
|
|
||||||
|
|
||||||
[<Test ; Explicit "This test doesn't run yet">]
|
|
||||||
let ``Can run Hello World`` () : unit =
|
|
||||||
let source = Assembly.getEmbeddedResourceAsString "WriteLine.cs" assy
|
|
||||||
let image = Roslyn.compile [ source ]
|
|
||||||
let messages, loggerFactory = LoggerFactory.makeTest ()
|
|
||||||
|
|
||||||
let dotnetRuntimes =
|
|
||||||
DotnetRuntime.SelectForDll assy.Location |> ImmutableArray.CreateRange
|
|
||||||
|
|
||||||
let impls = NativeImpls.PassThru ()
|
|
||||||
|
|
||||||
try
|
|
||||||
use peImage = new MemoryStream (image)
|
|
||||||
|
|
||||||
let terminalState, terminatingThread =
|
|
||||||
Program.run loggerFactory (Some "HelloWorld.cs") peImage dotnetRuntimes impls []
|
|
||||||
|
|
||||||
let exitCode =
|
|
||||||
match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with
|
|
||||||
| [] -> failwith "expected program to return 1, but it returned void"
|
|
||||||
| head :: _ ->
|
|
||||||
match head with
|
|
||||||
| EvalStackValue.Int32 i -> i
|
|
||||||
| _ -> failwith "TODO"
|
|
||||||
|
|
||||||
exitCode |> shouldEqual 0
|
|
||||||
with _ ->
|
|
||||||
for m in messages () do
|
|
||||||
Console.Error.WriteLine $"{m}"
|
|
||||||
|
|
||||||
reraise ()
|
|
86
WoofWare.PawPrint.Test/TestImpureCases.fs
Normal file
86
WoofWare.PawPrint.Test/TestImpureCases.fs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
namespace WoofWare.Pawprint.Test
|
||||||
|
|
||||||
|
open System.Collections.Immutable
|
||||||
|
open System.IO
|
||||||
|
open FsUnitTyped
|
||||||
|
open NUnit.Framework
|
||||||
|
open WoofWare.DotnetRuntimeLocator
|
||||||
|
open WoofWare.PawPrint
|
||||||
|
open WoofWare.PawPrint.ExternImplementations
|
||||||
|
open WoofWare.PawPrint.Test
|
||||||
|
|
||||||
|
[<TestFixture>]
|
||||||
|
[<Parallelizable(ParallelScope.All)>]
|
||||||
|
module TestImpureCases =
|
||||||
|
let assy = typeof<RunResult>.Assembly
|
||||||
|
|
||||||
|
let unimplemented =
|
||||||
|
[
|
||||||
|
{
|
||||||
|
FileName = "WriteLine.cs"
|
||||||
|
ExpectedReturnCode = 1
|
||||||
|
NativeImpls = NativeImpls.PassThru ()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
let cases : EndToEndTestCase list =
|
||||||
|
[
|
||||||
|
{
|
||||||
|
FileName = "InstaQuit.cs"
|
||||||
|
ExpectedReturnCode = 1
|
||||||
|
NativeImpls =
|
||||||
|
let mock = MockEnv.make ()
|
||||||
|
|
||||||
|
{ mock with
|
||||||
|
System_Environment =
|
||||||
|
{ System_EnvironmentMock.Empty with
|
||||||
|
GetProcessorCount =
|
||||||
|
fun thread state ->
|
||||||
|
let state =
|
||||||
|
state |> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 1) thread
|
||||||
|
|
||||||
|
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||||
|
_Exit =
|
||||||
|
fun thread state ->
|
||||||
|
let state = state |> IlMachineState.loadArgument thread 0
|
||||||
|
ExecutionResult.Terminated (state, thread)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
let runTest (case : EndToEndTestCase) : unit =
|
||||||
|
let source = Assembly.getEmbeddedResourceAsString case.FileName assy
|
||||||
|
let image = Roslyn.compile [ source ]
|
||||||
|
let messages, loggerFactory = LoggerFactory.makeTest ()
|
||||||
|
|
||||||
|
let dotnetRuntimes =
|
||||||
|
DotnetRuntime.SelectForDll assy.Location |> ImmutableArray.CreateRange
|
||||||
|
|
||||||
|
use peImage = new MemoryStream (image)
|
||||||
|
|
||||||
|
try
|
||||||
|
let terminalState, terminatingThread =
|
||||||
|
Program.run loggerFactory (Some case.FileName) peImage dotnetRuntimes case.NativeImpls []
|
||||||
|
|
||||||
|
let exitCode =
|
||||||
|
match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with
|
||||||
|
| [] -> failwith "expected program to return a value, but it returned void"
|
||||||
|
| head :: _ ->
|
||||||
|
match head with
|
||||||
|
| EvalStackValue.Int32 i -> i
|
||||||
|
| ret -> failwith $"expected program to return an int, but it returned %O{ret}"
|
||||||
|
|
||||||
|
exitCode |> shouldEqual case.ExpectedReturnCode
|
||||||
|
with _ ->
|
||||||
|
for message in messages () do
|
||||||
|
System.Console.Error.WriteLine $"{message}"
|
||||||
|
|
||||||
|
reraise ()
|
||||||
|
|
||||||
|
[<TestCaseSource(nameof unimplemented)>]
|
||||||
|
[<Explicit>]
|
||||||
|
let ``Can evaluate C# files, unimplemented`` (case : EndToEndTestCase) = runTest case
|
||||||
|
|
||||||
|
[<TestCaseSource(nameof cases)>]
|
||||||
|
let ``Can evaluate C# files`` (case : EndToEndTestCase) = runTest case
|
193
WoofWare.PawPrint.Test/TestPureCases.fs
Normal file
193
WoofWare.PawPrint.Test/TestPureCases.fs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
namespace WoofWare.Pawprint.Test
|
||||||
|
|
||||||
|
open System.Collections.Immutable
|
||||||
|
open System.IO
|
||||||
|
open FsUnitTyped
|
||||||
|
open NUnit.Framework
|
||||||
|
open WoofWare.DotnetRuntimeLocator
|
||||||
|
open WoofWare.PawPrint
|
||||||
|
open WoofWare.PawPrint.ExternImplementations
|
||||||
|
open WoofWare.PawPrint.Test
|
||||||
|
|
||||||
|
[<TestFixture>]
|
||||||
|
[<Parallelizable(ParallelScope.All)>]
|
||||||
|
module TestPureCases =
|
||||||
|
let assy = typeof<RunResult>.Assembly
|
||||||
|
|
||||||
|
let unimplemented =
|
||||||
|
[
|
||||||
|
{
|
||||||
|
FileName = "CrossAssemblyTypes.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "InitializeArray.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "GenericEdgeCases.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "Threads.cs"
|
||||||
|
ExpectedReturnCode = 3
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "ComplexTryCatch.cs"
|
||||||
|
ExpectedReturnCode = 14
|
||||||
|
NativeImpls = NativeImpls.PassThru ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "ResizeArray.cs"
|
||||||
|
ExpectedReturnCode = 109
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "Sizeof.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "LdtokenField.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
let cases : EndToEndTestCase list =
|
||||||
|
[
|
||||||
|
{
|
||||||
|
FileName = "NoOp.cs"
|
||||||
|
ExpectedReturnCode = 1
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "TestShl.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "TestShr.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "StaticVariables.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "Ldind.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "CustomDelegate.cs"
|
||||||
|
ExpectedReturnCode = 8
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "ArgumentOrdering.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "BasicLock.cs"
|
||||||
|
ExpectedReturnCode = 1
|
||||||
|
NativeImpls =
|
||||||
|
let mock = MockEnv.make ()
|
||||||
|
|
||||||
|
{ mock with
|
||||||
|
System_Threading_Monitor = System_Threading_Monitor.passThru
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "TriangleNumber.cs"
|
||||||
|
ExpectedReturnCode = 10
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "ExceptionWithNoOpFinally.cs"
|
||||||
|
ExpectedReturnCode = 3
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "ExceptionWithNoOpCatch.cs"
|
||||||
|
ExpectedReturnCode = 10
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "Floats.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "TryCatchWithThrowInBody.cs"
|
||||||
|
ExpectedReturnCode = 4
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "Ldelema.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "TypeConcretization.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "TestOr.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FileName = "InterfaceDispatch.cs"
|
||||||
|
ExpectedReturnCode = 0
|
||||||
|
NativeImpls = MockEnv.make ()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
let runTest (case : EndToEndTestCase) : unit =
|
||||||
|
let source = Assembly.getEmbeddedResourceAsString case.FileName assy
|
||||||
|
let image = Roslyn.compile [ source ]
|
||||||
|
let messages, loggerFactory = LoggerFactory.makeTest ()
|
||||||
|
|
||||||
|
let dotnetRuntimes =
|
||||||
|
DotnetRuntime.SelectForDll assy.Location |> ImmutableArray.CreateRange
|
||||||
|
|
||||||
|
use peImage = new MemoryStream (image)
|
||||||
|
|
||||||
|
try
|
||||||
|
let terminalState, terminatingThread =
|
||||||
|
Program.run loggerFactory (Some case.FileName) peImage dotnetRuntimes case.NativeImpls []
|
||||||
|
|
||||||
|
let realResult = RealRuntime.executeWithRealRuntime [||] image
|
||||||
|
|
||||||
|
let exitCode =
|
||||||
|
match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with
|
||||||
|
| [] -> failwith "expected program to return a value, but it returned void"
|
||||||
|
| head :: _ ->
|
||||||
|
match head with
|
||||||
|
| EvalStackValue.Int32 i -> i
|
||||||
|
| ret -> failwith $"expected program to return an int, but it returned %O{ret}"
|
||||||
|
|
||||||
|
exitCode |> shouldEqual realResult.ExitCode
|
||||||
|
|
||||||
|
exitCode |> shouldEqual case.ExpectedReturnCode
|
||||||
|
with _ ->
|
||||||
|
for message in messages () do
|
||||||
|
System.Console.Error.WriteLine $"{message}"
|
||||||
|
|
||||||
|
reraise ()
|
||||||
|
|
||||||
|
[<TestCaseSource(nameof unimplemented)>]
|
||||||
|
[<Explicit>]
|
||||||
|
let ``Can evaluate C# files, unimplemented`` (case : EndToEndTestCase) = runTest case
|
||||||
|
|
||||||
|
[<TestCaseSource(nameof cases)>]
|
||||||
|
let ``Can evaluate C# files`` (case : EndToEndTestCase) = runTest case
|
@@ -4,32 +4,20 @@
|
|||||||
<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>
|
||||||
<Compile Include="LoggerFactory.fs" />
|
<Compile Include="LoggerFactory.fs" />
|
||||||
<Compile Include="Assembly.fs" />
|
<Compile Include="Assembly.fs" />
|
||||||
<Compile Include="Roslyn.fs" />
|
<Compile Include="Roslyn.fs" />
|
||||||
|
<Compile Include="RealRuntime.fs" />
|
||||||
<Compile Include="TestHarness.fs"/>
|
<Compile Include="TestHarness.fs"/>
|
||||||
<Compile Include="TestCases.fs" />
|
<Compile Include="TestPureCases.fs" />
|
||||||
<Compile Include="TestHelloWorld.fs" />
|
<Compile Include="TestImpureCases.fs" />
|
||||||
<EmbeddedResource Include="sources\BasicLock.cs" />
|
</ItemGroup>
|
||||||
<EmbeddedResource Include="sources\NoOp.cs" />
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="sources\ExceptionWithNoOpCatch.cs" />
|
<EmbeddedResource Include="sourcesPure\*.cs" />
|
||||||
<EmbeddedResource Include="sources\ExceptionWithNoOpFinally.cs" />
|
<EmbeddedResource Include="sourcesImpure\*.cs" />
|
||||||
<EmbeddedResource Include="sources\TryCatchWithThrowInBody.cs" />
|
|
||||||
<EmbeddedResource Include="sources\ComplexTryCatch.cs" />
|
|
||||||
<EmbeddedResource Include="sources\TriangleNumber.cs" />
|
|
||||||
<EmbeddedResource Include="sources\WriteLine.cs" />
|
|
||||||
<EmbeddedResource Include="sources\InstaQuit.cs" />
|
|
||||||
<EmbeddedResource Include="sources\Threads.cs" />
|
|
||||||
<EmbeddedResource Include="sources\ResizeArray.cs" />
|
|
||||||
<EmbeddedResource Include="sources\ArgumentOrdering.cs" />
|
|
||||||
<EmbeddedResource Include="sources\CustomDelegate.cs" />
|
|
||||||
<EmbeddedResource Include="sources\Ldind.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -37,14 +25,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FsUnit" Version="7.0.1"/>
|
<PackageReference Include="NUnit" Version="4.4.0"/>
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/>
|
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0"/>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0"/>
|
<PackageReference Include="FsUnit" Version="7.1.1"/>
|
||||||
<PackageReference Include="NUnit" Version="4.3.2"/>
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0"/>
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.2" />
|
<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"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
@@ -1,19 +0,0 @@
|
|||||||
public class Program
|
|
||||||
{
|
|
||||||
public struct TestStruct
|
|
||||||
{
|
|
||||||
public int Value;
|
|
||||||
|
|
||||||
public TestStruct(ref int x)
|
|
||||||
{
|
|
||||||
Value = x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int Main(string[] args)
|
|
||||||
{
|
|
||||||
int localVar = 42;
|
|
||||||
TestStruct t = new TestStruct(ref localVar);
|
|
||||||
return t.Value;
|
|
||||||
}
|
|
||||||
}
|
|
49
WoofWare.PawPrint.Test/sourcesPure/ArgumentOrdering.cs
Normal file
49
WoofWare.PawPrint.Test/sourcesPure/ArgumentOrdering.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public struct TestStruct
|
||||||
|
{
|
||||||
|
public int Value;
|
||||||
|
|
||||||
|
public TestStruct(ref int x)
|
||||||
|
{
|
||||||
|
Value = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Calculator
|
||||||
|
{
|
||||||
|
private int baseValue;
|
||||||
|
|
||||||
|
public Calculator(int initial)
|
||||||
|
{
|
||||||
|
baseValue = initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Add(int a, int b, int c)
|
||||||
|
{
|
||||||
|
return baseValue + a + b + c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SubtractIsh(int a, int b)
|
||||||
|
{
|
||||||
|
return baseValue - a + b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Main(string[] args)
|
||||||
|
{
|
||||||
|
int localVar = 42;
|
||||||
|
TestStruct t = new TestStruct(ref localVar);
|
||||||
|
if (t.Value != 42) return 1;
|
||||||
|
|
||||||
|
Calculator calc = new Calculator(10);
|
||||||
|
int addResult = calc.Add(1, 2, 3); // Should be 10 + 1 + 2 + 3 = 16
|
||||||
|
if (addResult != 16) return 2;
|
||||||
|
|
||||||
|
// Test 2: Verify order matters
|
||||||
|
int subResult = calc.SubtractIsh(3, 2); // Should be 10 - 3 + 2 = 9
|
||||||
|
if (subResult != 9) return 3;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
120
WoofWare.PawPrint.Test/sourcesPure/CrossAssemblyTypes.cs
Normal file
120
WoofWare.PawPrint.Test/sourcesPure/CrossAssemblyTypes.cs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
// Test cross-assembly type resolution using standard library types
|
||||||
|
public class CrossAssemblyTypeTest
|
||||||
|
{
|
||||||
|
public static int TestSystemTypes()
|
||||||
|
{
|
||||||
|
// Test various System types to ensure proper assembly resolution
|
||||||
|
|
||||||
|
// System.DateTime
|
||||||
|
var date = new DateTime(2023, 1, 1);
|
||||||
|
if (date.Year != 2023) return 1;
|
||||||
|
|
||||||
|
// System.Guid
|
||||||
|
var guid = Guid.Empty;
|
||||||
|
if (guid != Guid.Empty) return 2;
|
||||||
|
|
||||||
|
// System.TimeSpan
|
||||||
|
var timeSpan = TimeSpan.FromMinutes(30);
|
||||||
|
if (timeSpan.TotalMinutes != 30) return 3;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int TestCollectionTypes()
|
||||||
|
{
|
||||||
|
// Test various collection types from different assemblies
|
||||||
|
|
||||||
|
// Dictionary<TKey, TValue>
|
||||||
|
var dict = new Dictionary<string, int>();
|
||||||
|
dict["test"] = 42;
|
||||||
|
if (dict["test"] != 42) return 1;
|
||||||
|
|
||||||
|
// HashSet<T>
|
||||||
|
var hashSet = new HashSet<int>();
|
||||||
|
hashSet.Add(1);
|
||||||
|
hashSet.Add(2);
|
||||||
|
hashSet.Add(1); // duplicate
|
||||||
|
if (hashSet.Count != 2) return 2;
|
||||||
|
|
||||||
|
// Queue<T>
|
||||||
|
var queue = new Queue<string>();
|
||||||
|
queue.Enqueue("first");
|
||||||
|
queue.Enqueue("second");
|
||||||
|
if (queue.Dequeue() != "first") return 3;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int TestGenericInterfaces()
|
||||||
|
{
|
||||||
|
// Test generic interfaces across assemblies
|
||||||
|
|
||||||
|
var list = new List<int> { 1, 2, 3 };
|
||||||
|
|
||||||
|
// IEnumerable<T>
|
||||||
|
IEnumerable<int> enumerable = list;
|
||||||
|
int count = 0;
|
||||||
|
foreach (int item in enumerable)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (count != 3) return 1;
|
||||||
|
|
||||||
|
// ICollection<T>
|
||||||
|
ICollection<int> collection = list;
|
||||||
|
if (collection.Count != 3) return 2;
|
||||||
|
|
||||||
|
// IList<T>
|
||||||
|
IList<int> ilist = list;
|
||||||
|
if (ilist[0] != 1) return 3;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Array.Empty<T> which was mentioned in the diff as a specific case
|
||||||
|
public class ArrayEmptyTest
|
||||||
|
{
|
||||||
|
public static int TestArrayEmpty()
|
||||||
|
{
|
||||||
|
// Test Array.Empty<T> for different types
|
||||||
|
var emptyInts = Array.Empty<int>();
|
||||||
|
var emptyStrings = Array.Empty<string>();
|
||||||
|
|
||||||
|
if (emptyInts.Length != 0) return 1;
|
||||||
|
if (emptyStrings.Length != 0) return 2;
|
||||||
|
|
||||||
|
// Verify they are different instances for different types
|
||||||
|
// but same instance for same type
|
||||||
|
var emptyInts2 = Array.Empty<int>();
|
||||||
|
if (!ReferenceEquals(emptyInts, emptyInts2)) return 3;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static int Main(string[] args)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
|
||||||
|
result = CrossAssemblyTypeTest.TestSystemTypes();
|
||||||
|
if (result != 0) return 100 + result;
|
||||||
|
|
||||||
|
result = CrossAssemblyTypeTest.TestCollectionTypes();
|
||||||
|
if (result != 0) return 200 + result;
|
||||||
|
|
||||||
|
result = CrossAssemblyTypeTest.TestGenericInterfaces();
|
||||||
|
if (result != 0) return 300 + result;
|
||||||
|
|
||||||
|
result = ArrayEmptyTest.TestArrayEmpty();
|
||||||
|
if (result != 0) return 400 + result;
|
||||||
|
|
||||||
|
return 0; // All tests passed
|
||||||
|
}
|
||||||
|
}
|
216
WoofWare.PawPrint.Test/sourcesPure/Floats.cs
Normal file
216
WoofWare.PawPrint.Test/sourcesPure/Floats.cs
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
// Thanks Gemini 2.5 Pro
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Main entry point for the test harness. It runs test suites for float and double comparisons.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>0 if all tests pass, otherwise a non-zero error code indicating the first failed test.</returns>
|
||||||
|
public static int Main(string[] args)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
|
||||||
|
result = FloatCompareTests.RunTests();
|
||||||
|
if (result != 0)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = DoubleCompareTests.RunTests();
|
||||||
|
if (result != 0)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; // Success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains a suite of tests for System.Single (float) comparisons.
|
||||||
|
/// Each test corresponds to a specific CIL comparison instruction.
|
||||||
|
/// </summary>
|
||||||
|
public class FloatCompareTests
|
||||||
|
{
|
||||||
|
private static int testCounter = 100; // Start error codes at 100 for this suite
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks a boolean condition. If the condition is false, it prints a failure message
|
||||||
|
/// and returns a unique error code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="condition">The boolean result of the test.</param>
|
||||||
|
/// <param name="testName">A descriptive name for the test case.</param>
|
||||||
|
/// <returns>0 if the test passes, otherwise a unique non-zero error code.</returns>
|
||||||
|
private static int Check(bool condition, string testName)
|
||||||
|
{
|
||||||
|
testCounter++;
|
||||||
|
if (!condition)
|
||||||
|
{
|
||||||
|
return testCounter;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs all float comparison tests.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>0 if all tests pass, otherwise the error code of the first failing test.</returns>
|
||||||
|
public static int RunTests()
|
||||||
|
{
|
||||||
|
float pz = 0.0f;
|
||||||
|
float nz = -0.0f;
|
||||||
|
float one = 1.0f;
|
||||||
|
float negOne = -1.0f;
|
||||||
|
float two = 2.0f;
|
||||||
|
float pInf = float.PositiveInfinity;
|
||||||
|
float nInf = float.NegativeInfinity;
|
||||||
|
float nan = float.NaN;
|
||||||
|
float subnormal = BitConverter.ToSingle(new byte[] { 1, 0, 0, 0 }, 0); // Smallest positive subnormal
|
||||||
|
|
||||||
|
int result;
|
||||||
|
|
||||||
|
// --- Ceq Tests (==) ---
|
||||||
|
result = Check(one == one, "1.0f == 1.0f"); if (result != 0) return result;
|
||||||
|
result = Check(!(one == two), "!(1.0f == 2.0f)"); if (result != 0) return result;
|
||||||
|
result = Check(pz == nz, "0.0f == -0.0f"); if (result != 0) return result;
|
||||||
|
result = Check(pInf == pInf, "+Inf == +Inf"); if (result != 0) return result;
|
||||||
|
result = Check(nInf == nInf, "-Inf == -Inf"); if (result != 0) return result;
|
||||||
|
result = Check(!(pInf == nInf), "!(+Inf == -Inf)"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan == nan), "!(NaN == NaN)"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan == one), "!(NaN == 1.0f)"); if (result != 0) return result;
|
||||||
|
result = Check(!(one == nan), "!(1.0f == NaN)"); if (result != 0) return result;
|
||||||
|
|
||||||
|
// --- Cgt Tests (>) ---
|
||||||
|
result = Check(two > one, "2.0f > 1.0f"); if (result != 0) return result;
|
||||||
|
result = Check(!(one > two), "!(1.0f > 2.0f)"); if (result != 0) return result;
|
||||||
|
result = Check(!(one > one), "!(1.0f > 1.0f)"); if (result != 0) return result;
|
||||||
|
result = Check(pInf > one, "+Inf > 1.0f"); if (result != 0) return result;
|
||||||
|
result = Check(!(nInf > one), "!( -Inf > 1.0f)"); if (result != 0) return result;
|
||||||
|
result = Check(pInf > nInf, "+Inf > -Inf"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan > one), "!(NaN > 1.0f)"); if (result != 0) return result;
|
||||||
|
result = Check(!(one > nan), "!(1.0f > NaN)"); if (result != 0) return result;
|
||||||
|
result = Check(one > subnormal, "1.0f > subnormal"); if (result != 0) return result;
|
||||||
|
|
||||||
|
// --- Cgt.un Tests (unordered >) ---
|
||||||
|
// cgt.un is equivalent to !(a <= b) for floats
|
||||||
|
result = Check(!(two <= one), "cgt.un: 2.0f > 1.0f"); if (result != 0) return result;
|
||||||
|
result = Check(one > pz, "cgt.un: 1.0f > 0.0f"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan <= one), "cgt.un: NaN > 1.0f"); if (result != 0) return result;
|
||||||
|
result = Check(!(one <= nan), "cgt.un: 1.0f > NaN"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan <= nan), "cgt.un: NaN > NaN"); if (result != 0) return result;
|
||||||
|
|
||||||
|
|
||||||
|
// --- Clt Tests (<) ---
|
||||||
|
result = Check(one < two, "1.0f < 2.0f"); if (result != 0) return result;
|
||||||
|
result = Check(!(two < one), "!(2.0f < 1.0f)"); if (result != 0) return result;
|
||||||
|
result = Check(!(one < one), "!(1.0f < 1.0f)"); if (result != 0) return result;
|
||||||
|
result = Check(one < pInf, "1.0f < +Inf"); if (result != 0) return result;
|
||||||
|
result = Check(nInf < one, "-Inf < 1.0f"); if (result != 0) return result;
|
||||||
|
result = Check(nInf < pInf, "-Inf < +Inf"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan < one), "!(NaN < 1.0f)"); if (result != 0) return result;
|
||||||
|
result = Check(!(one < nan), "!(1.0f < NaN)"); if (result != 0) return result;
|
||||||
|
result = Check(subnormal < one, "subnormal < 1.0f"); if (result != 0) return result;
|
||||||
|
|
||||||
|
// --- Clt.un Tests (unordered <) ---
|
||||||
|
// clt.un is equivalent to !(a >= b) for floats
|
||||||
|
result = Check(one < two, "clt.un: 1.0f < 2.0f"); if (result != 0) return result;
|
||||||
|
result = Check(!(one >= nan), "clt.un: 1.0f < NaN"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan >= one), "clt.un: NaN < 1.0f"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan >= nan), "clt.un: NaN < NaN"); if (result != 0) return result;
|
||||||
|
|
||||||
|
// --- C# >= (bge) and <= (ble) ---
|
||||||
|
result = Check(one >= one, "1.0f >= 1.0f"); if (result != 0) return result;
|
||||||
|
result = Check(two >= one, "2.0f >= 1.0f"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan >= one), "!(NaN >= 1.0f)"); if (result != 0) return result;
|
||||||
|
result = Check(one <= one, "1.0f <= 1.0f"); if (result != 0) return result;
|
||||||
|
result = Check(one <= two, "1.0f <= 2.0f"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan <= one), "!(NaN <= 1.0f)"); if (result != 0) return result;
|
||||||
|
result = Check(pz >= nz, "0.0f >= -0.0f"); if (result != 0) return result;
|
||||||
|
result = Check(pz <= nz, "0.0f <= -0.0f"); if (result != 0) return result;
|
||||||
|
|
||||||
|
return 0; // Success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains a suite of tests for System.Double comparisons.
|
||||||
|
/// </summary>
|
||||||
|
public class DoubleCompareTests
|
||||||
|
{
|
||||||
|
private static int testCounter = 200; // Start error codes at 200 for this suite
|
||||||
|
|
||||||
|
private static int Check(bool condition, string testName)
|
||||||
|
{
|
||||||
|
testCounter++;
|
||||||
|
if (!condition)
|
||||||
|
{
|
||||||
|
return testCounter;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int RunTests()
|
||||||
|
{
|
||||||
|
double pz = 0.0;
|
||||||
|
double nz = -0.0;
|
||||||
|
double one = 1.0;
|
||||||
|
double negOne = -1.0;
|
||||||
|
double two = 2.0;
|
||||||
|
double pInf = double.PositiveInfinity;
|
||||||
|
double nInf = double.NegativeInfinity;
|
||||||
|
double nan = double.NaN;
|
||||||
|
double subnormal = BitConverter.Int64BitsToDouble(1); // Smallest positive subnormal
|
||||||
|
|
||||||
|
int result;
|
||||||
|
|
||||||
|
// --- Ceq Tests (==) ---
|
||||||
|
result = Check(one == one, "1.0 == 1.0"); if (result != 0) return result;
|
||||||
|
result = Check(!(one == two), "!(1.0 == 2.0)"); if (result != 0) return result;
|
||||||
|
result = Check(pz == nz, "0.0 == -0.0"); if (result != 0) return result;
|
||||||
|
result = Check(pInf == pInf, "+Inf == +Inf"); if (result != 0) return result;
|
||||||
|
result = Check(nInf == nInf, "-Inf == -Inf"); if (result != 0) return result;
|
||||||
|
result = Check(!(pInf == nInf), "!(+Inf == -Inf)"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan == nan), "!(NaN == NaN)"); if (result != 0) return result;
|
||||||
|
|
||||||
|
// --- Cgt Tests (>) ---
|
||||||
|
result = Check(two > one, "2.0 > 1.0"); if (result != 0) return result;
|
||||||
|
result = Check(!(one > one), "!(1.0 > 1.0)"); if (result != 0) return result;
|
||||||
|
result = Check(pInf > one, "+Inf > 1.0"); if (result != 0) return result;
|
||||||
|
result = Check(!(nInf > one), "!(-Inf > 1.0)"); if (result != 0) return result;
|
||||||
|
result = Check(pInf > nInf, "+Inf > -Inf"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan > one), "!(NaN > 1.0)"); if (result != 0) return result;
|
||||||
|
result = Check(one > subnormal, "1.0 > subnormal"); if (result != 0) return result;
|
||||||
|
|
||||||
|
// --- Cgt.un Tests (unordered >) ---
|
||||||
|
result = Check(one > pz, "cgt.un: 1.0 > 0.0"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan <= one), "cgt.un: NaN > 1.0"); if (result != 0) return result;
|
||||||
|
result = Check(!(one <= nan), "cgt.un: 1.0 > NaN"); if (result != 0) return result;
|
||||||
|
|
||||||
|
// --- Clt Tests (<) ---
|
||||||
|
result = Check(one < two, "1.0 < 2.0"); if (result != 0) return result;
|
||||||
|
result = Check(!(one < one), "!(1.0 < 1.0)"); if (result != 0) return result;
|
||||||
|
result = Check(nInf < one, "-Inf < 1.0"); if (result != 0) return result;
|
||||||
|
result = Check(!(pInf < one), "!(+Inf < 1.0)"); if (result != 0) return result;
|
||||||
|
result = Check(nInf < pInf, "-Inf < +Inf"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan < one), "!(NaN < 1.0)"); if (result != 0) return result;
|
||||||
|
result = Check(subnormal < one, "subnormal < 1.0"); if (result != 0) return result;
|
||||||
|
|
||||||
|
// --- Clt.un Tests (unordered <) ---
|
||||||
|
result = Check(one < two, "clt.un: 1.0 < 2.0"); if (result != 0) return result;
|
||||||
|
result = Check(!(one >= nan), "clt.un: 1.0 < NaN"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan >= one), "clt.un: NaN < 1.0"); if (result != 0) return result;
|
||||||
|
|
||||||
|
// --- C# >= (bge) and <= (ble) ---
|
||||||
|
result = Check(one >= one, "1.0 >= 1.0"); if (result != 0) return result;
|
||||||
|
result = Check(two >= one, "2.0 >= 1.0"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan >= one), "!(NaN >= 1.0)"); if (result != 0) return result;
|
||||||
|
result = Check(one <= one, "1.0 <= 1.0"); if (result != 0) return result;
|
||||||
|
result = Check(one <= two, "1.0 <= 2.0"); if (result != 0) return result;
|
||||||
|
result = Check(!(nan <= one), "!(NaN <= 1.0)"); if (result != 0) return result;
|
||||||
|
result = Check(pz >= nz, "0.0 >= -0.0"); if (result != 0) return result;
|
||||||
|
|
||||||
|
return 0; // Success
|
||||||
|
}
|
||||||
|
}
|
142
WoofWare.PawPrint.Test/sourcesPure/GenericEdgeCases.cs
Normal file
142
WoofWare.PawPrint.Test/sourcesPure/GenericEdgeCases.cs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
// Test edge cases with generic parameters as mentioned in the diff
|
||||||
|
public class GenericParameterEdgeCases
|
||||||
|
{
|
||||||
|
// Test method with multiple generic parameters
|
||||||
|
public static T2 Convert<T1, T2>(T1 input, Func<T1, T2> converter)
|
||||||
|
{
|
||||||
|
return converter(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test nested generic method calls
|
||||||
|
public static List<T> WrapInList<T>(T item)
|
||||||
|
{
|
||||||
|
var list = new List<T>();
|
||||||
|
list.Add(item);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int TestMultipleGenericParameters()
|
||||||
|
{
|
||||||
|
// Test Convert method with different type combinations
|
||||||
|
string result1 = Convert<int, string>(42, x => x.ToString());
|
||||||
|
if (result1 != "42") return 1;
|
||||||
|
|
||||||
|
int result2 = Convert<string, int>("123", x => int.Parse(x));
|
||||||
|
if (result2 != 123) return 2;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int TestNestedGenericMethodCalls()
|
||||||
|
{
|
||||||
|
// Test calling generic method from within another generic method
|
||||||
|
var intList = WrapInList<int>(42);
|
||||||
|
if (intList.Count != 1) return 1;
|
||||||
|
if (intList[0] != 42) return 2;
|
||||||
|
|
||||||
|
var stringList = WrapInList<string>("test");
|
||||||
|
if (stringList.Count != 1) return 3;
|
||||||
|
if (stringList[0] != "test") return 4;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test deeply nested generic types
|
||||||
|
public class DeepNestingTest
|
||||||
|
{
|
||||||
|
public static int TestDeeplyNestedGenerics()
|
||||||
|
{
|
||||||
|
// Test Dictionary<string, List<Dictionary<int, string>>>
|
||||||
|
var complexType = new Dictionary<string, List<Dictionary<int, string>>>();
|
||||||
|
|
||||||
|
var innerDict = new Dictionary<int, string>();
|
||||||
|
innerDict[1] = "one";
|
||||||
|
innerDict[2] = "two";
|
||||||
|
|
||||||
|
var listOfDicts = new List<Dictionary<int, string>>();
|
||||||
|
listOfDicts.Add(innerDict);
|
||||||
|
|
||||||
|
complexType["test"] = listOfDicts;
|
||||||
|
|
||||||
|
if (complexType["test"].Count != 1) return 1;
|
||||||
|
if (complexType["test"][0][1] != "one") return 2;
|
||||||
|
if (complexType["test"][0][2] != "two") return 3;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test generic constraints and inheritance scenarios
|
||||||
|
public class GenericConstraintTest<T> where T : class
|
||||||
|
{
|
||||||
|
private T value;
|
||||||
|
|
||||||
|
public GenericConstraintTest(T val)
|
||||||
|
{
|
||||||
|
value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsNull()
|
||||||
|
{
|
||||||
|
return value == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int TestGenericConstraints()
|
||||||
|
{
|
||||||
|
var test = new GenericConstraintTest<string>("hello");
|
||||||
|
if (test.IsNull()) return 1;
|
||||||
|
|
||||||
|
var nullTest = new GenericConstraintTest<string>(null);
|
||||||
|
if (!nullTest.IsNull()) return 2;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test generic field access scenarios mentioned in the diff
|
||||||
|
public class GenericFieldAccess<T>
|
||||||
|
{
|
||||||
|
public static T DefaultValue = default(T);
|
||||||
|
|
||||||
|
public static int TestStaticGenericField()
|
||||||
|
{
|
||||||
|
// Test that static fields work correctly with generics
|
||||||
|
if (GenericFieldAccess<int>.DefaultValue != 0) return 1;
|
||||||
|
|
||||||
|
// Test that different instantiations have different static fields
|
||||||
|
GenericFieldAccess<int>.DefaultValue = 42;
|
||||||
|
if (GenericFieldAccess<int>.DefaultValue != 42) return 2;
|
||||||
|
if (GenericFieldAccess<string>.DefaultValue != null) return 3;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static int Main(string[] args)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
|
||||||
|
result = GenericParameterEdgeCases.TestMultipleGenericParameters();
|
||||||
|
if (result != 0) return 100 + result;
|
||||||
|
|
||||||
|
result = GenericParameterEdgeCases.TestNestedGenericMethodCalls();
|
||||||
|
if (result != 0) return 200 + result;
|
||||||
|
|
||||||
|
result = DeepNestingTest.TestDeeplyNestedGenerics();
|
||||||
|
if (result != 0) return 300 + result;
|
||||||
|
|
||||||
|
result = GenericConstraintTest<string>.TestGenericConstraints();
|
||||||
|
if (result != 0) return 400 + result;
|
||||||
|
|
||||||
|
result = GenericFieldAccess<int>.TestStaticGenericField();
|
||||||
|
if (result != 0) return 500 + result;
|
||||||
|
|
||||||
|
return 0; // All tests passed
|
||||||
|
}
|
||||||
|
}
|
19
WoofWare.PawPrint.Test/sourcesPure/InitializeArray.cs
Normal file
19
WoofWare.PawPrint.Test/sourcesPure/InitializeArray.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace HelloWorldApp
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static int Main(string[] args)
|
||||||
|
{
|
||||||
|
int[] array = new[] { 1, 2, 3 };
|
||||||
|
|
||||||
|
if (array.Sum() != 60)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
339
WoofWare.PawPrint.Test/sourcesPure/InterfaceDispatch.cs
Normal file
339
WoofWare.PawPrint.Test/sourcesPure/InterfaceDispatch.cs
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
public class InterfaceDispatchTests
|
||||||
|
{
|
||||||
|
public static int Main(string[] argv)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
result |= TestBasicInterface();
|
||||||
|
result |= TestExplicitImplementation() << 1;
|
||||||
|
result |= TestMultipleInterfaces() << 2;
|
||||||
|
result |= TestInterfaceInheritance() << 3;
|
||||||
|
result |= TestDiamondInheritance() << 4;
|
||||||
|
result |= TestGenericInterface() << 5;
|
||||||
|
result |= TestCovariantInterface() << 6;
|
||||||
|
result |= TestReimplementation() << 7;
|
||||||
|
// TODO
|
||||||
|
/*
|
||||||
|
result |= TestStructInterface() << 8;
|
||||||
|
result |= TestNullDispatch() << 9;
|
||||||
|
*/
|
||||||
|
result |= TestSharedMethodSignature() << 10;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 1: Basic interface dispatch
|
||||||
|
static int TestBasicInterface()
|
||||||
|
{
|
||||||
|
ISimple obj = new SimpleImpl();
|
||||||
|
return obj.GetValue() == 42 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Explicit interface implementation
|
||||||
|
static int TestExplicitImplementation()
|
||||||
|
{
|
||||||
|
var obj = new ExplicitImpl();
|
||||||
|
IExplicit iface = obj;
|
||||||
|
|
||||||
|
// Direct call should return 10, interface call should return 20
|
||||||
|
if (obj.GetValue() != 10) return 1;
|
||||||
|
if (iface.GetValue() != 20) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Multiple interfaces
|
||||||
|
static int TestMultipleInterfaces()
|
||||||
|
{
|
||||||
|
var obj = new MultiImpl();
|
||||||
|
IFirst first = obj;
|
||||||
|
ISecond second = obj;
|
||||||
|
|
||||||
|
if (first.GetFirst() != 1) return 1;
|
||||||
|
if (second.GetSecond() != 2) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: Interface inheritance
|
||||||
|
static int TestInterfaceInheritance()
|
||||||
|
{
|
||||||
|
IDerived obj = new DerivedImpl();
|
||||||
|
IBase baseIface = obj;
|
||||||
|
|
||||||
|
if (baseIface.GetBase() != 100) return 1;
|
||||||
|
if (obj.GetDerived() != 200) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 5: Diamond inheritance pattern
|
||||||
|
static int TestDiamondInheritance()
|
||||||
|
{
|
||||||
|
var obj = new DiamondImpl();
|
||||||
|
ILeft left = obj;
|
||||||
|
IRight right = obj;
|
||||||
|
IDiamond diamond = obj;
|
||||||
|
|
||||||
|
if (left.GetValue() != 300) return 1;
|
||||||
|
if (right.GetValue() != 300) return 1;
|
||||||
|
if (diamond.GetDiamondValue() != 400) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 6: Generic interface dispatch
|
||||||
|
static int TestGenericInterface()
|
||||||
|
{
|
||||||
|
IGeneric<int> intObj = new GenericImpl<int>();
|
||||||
|
IGeneric<string> strObj = new GenericImpl<string>();
|
||||||
|
|
||||||
|
if (intObj.Process(5) != 3) return 1;
|
||||||
|
if (strObj.Process("test") != 5) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 7: Covariant interface dispatch
|
||||||
|
static int TestCovariantInterface()
|
||||||
|
{
|
||||||
|
ICovariant<string> strCov = new CovariantImpl();
|
||||||
|
ICovariant<object> objCov = strCov; // Covariance allows this
|
||||||
|
|
||||||
|
object result = objCov.Get();
|
||||||
|
if (!(result is string s && s == "covariant")) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 8: Interface reimplementation in derived class
|
||||||
|
static int TestReimplementation()
|
||||||
|
{
|
||||||
|
BaseClass baseObj = new DerivedClass();
|
||||||
|
IReimpl iface = baseObj;
|
||||||
|
|
||||||
|
// Should call derived implementation
|
||||||
|
if (iface.Method() != 500) return 1;
|
||||||
|
|
||||||
|
// Now test with base reference
|
||||||
|
BaseClass pureBase = new BaseClass();
|
||||||
|
IReimpl baseIface = pureBase;
|
||||||
|
if (baseIface.Method() != 600) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 9: Struct implementing interface
|
||||||
|
static int TestStructInterface()
|
||||||
|
{
|
||||||
|
StructImpl s = new StructImpl { Value = 700 };
|
||||||
|
ISimple boxed = s; // Boxing happens here
|
||||||
|
|
||||||
|
if (boxed.GetValue() != 700) return 1;
|
||||||
|
|
||||||
|
// Verify boxing created a copy
|
||||||
|
s.Value = 800;
|
||||||
|
if (boxed.GetValue() != 700) return 1; // Should still be 700
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 10: Null dispatch (should throw)
|
||||||
|
static int TestNullDispatch()
|
||||||
|
{
|
||||||
|
ISimple nullRef = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
nullRef.GetValue();
|
||||||
|
return 1; // Should have thrown
|
||||||
|
}
|
||||||
|
catch (NullReferenceException)
|
||||||
|
{
|
||||||
|
return 0; // Expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 11: Same method signature on multiple unrelated interfaces
|
||||||
|
static int TestSharedMethodSignature()
|
||||||
|
{
|
||||||
|
var obj = new SharedMethodImpl();
|
||||||
|
IReader reader = obj;
|
||||||
|
IScanner scanner = obj;
|
||||||
|
|
||||||
|
// Both interfaces should be satisfied by the single implementation
|
||||||
|
if (reader.Read() != "shared") return 1;
|
||||||
|
if (scanner.Read() != "shared") return 1;
|
||||||
|
|
||||||
|
// Also test with explicit + implicit combination
|
||||||
|
var mixed = new MixedSharedImpl();
|
||||||
|
IReader readerMixed = mixed;
|
||||||
|
IScanner scannerMixed = mixed;
|
||||||
|
|
||||||
|
if (readerMixed.Read() != "explicit-reader") return 1;
|
||||||
|
if (scannerMixed.Read() != "implicit-scanner") return 1;
|
||||||
|
if (mixed.Read() != "implicit-scanner") return 1; // Public method
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test interfaces and implementations
|
||||||
|
|
||||||
|
interface ISimple
|
||||||
|
{
|
||||||
|
int GetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleImpl : ISimple
|
||||||
|
{
|
||||||
|
public int GetValue() => 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IExplicit
|
||||||
|
{
|
||||||
|
int GetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExplicitImpl : IExplicit
|
||||||
|
{
|
||||||
|
public int GetValue() => 10;
|
||||||
|
int IExplicit.GetValue() => 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFirst
|
||||||
|
{
|
||||||
|
int GetFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISecond
|
||||||
|
{
|
||||||
|
int GetSecond();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MultiImpl : IFirst, ISecond
|
||||||
|
{
|
||||||
|
public int GetFirst() => 1;
|
||||||
|
public int GetSecond() => 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IBase
|
||||||
|
{
|
||||||
|
int GetBase();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDerived : IBase
|
||||||
|
{
|
||||||
|
int GetDerived();
|
||||||
|
}
|
||||||
|
|
||||||
|
class DerivedImpl : IDerived
|
||||||
|
{
|
||||||
|
public int GetBase() => 100;
|
||||||
|
public int GetDerived() => 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICommon
|
||||||
|
{
|
||||||
|
int GetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ILeft : ICommon
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IRight : ICommon
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDiamond : ILeft, IRight
|
||||||
|
{
|
||||||
|
int GetDiamondValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
class DiamondImpl : IDiamond
|
||||||
|
{
|
||||||
|
public int GetValue() => 300;
|
||||||
|
public int GetDiamondValue() => 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IGeneric<T>
|
||||||
|
{
|
||||||
|
int Process(T value);
|
||||||
|
}
|
||||||
|
|
||||||
|
class GenericImpl<T> : IGeneric<T>
|
||||||
|
{
|
||||||
|
public int Process(T value)
|
||||||
|
{
|
||||||
|
if (typeof(T) == typeof(int))
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(T) == typeof(string))
|
||||||
|
{
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICovariant<out T>
|
||||||
|
{
|
||||||
|
T Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
class CovariantImpl : ICovariant<string>
|
||||||
|
{
|
||||||
|
public string Get() => "covariant";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IReimpl
|
||||||
|
{
|
||||||
|
int Method();
|
||||||
|
}
|
||||||
|
|
||||||
|
class BaseClass : IReimpl
|
||||||
|
{
|
||||||
|
public virtual int Method() => 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DerivedClass : BaseClass, IReimpl
|
||||||
|
{
|
||||||
|
public override int Method() => 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StructImpl : ISimple
|
||||||
|
{
|
||||||
|
public int Value;
|
||||||
|
public int GetValue() => Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IReader
|
||||||
|
{
|
||||||
|
string Read();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IScanner
|
||||||
|
{
|
||||||
|
string Read(); // Same signature as IReader.Read()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single implicit implementation satisfies both interfaces
|
||||||
|
class SharedMethodImpl : IReader, IScanner
|
||||||
|
{
|
||||||
|
public string Read() => "shared";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixed: explicit for one, implicit for the other
|
||||||
|
class MixedSharedImpl : IReader, IScanner
|
||||||
|
{
|
||||||
|
// Explicit implementation for IReader
|
||||||
|
string IReader.Read() => "explicit-reader";
|
||||||
|
|
||||||
|
// Implicit implementation (public) - satisfies IScanner
|
||||||
|
public string Read() => "implicit-scanner";
|
||||||
|
}
|
||||||
|
}
|
66
WoofWare.PawPrint.Test/sourcesPure/Ldelema.cs
Normal file
66
WoofWare.PawPrint.Test/sourcesPure/Ldelema.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A simple value type used for testing ldelema.
|
||||||
|
/// </summary>
|
||||||
|
public struct TestStruct
|
||||||
|
{
|
||||||
|
public int Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Modifies a TestStruct instance by reference. Calling this with an array element
|
||||||
|
/// (e.g., `ModifyStruct(ref array[i], ...)` ) will cause the C# compiler to
|
||||||
|
/// generate an `ldelema` instruction.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="s">A reference to the TestStruct to modify.</param>
|
||||||
|
/// <param name="newValue">The new value to assign.</param>
|
||||||
|
public static void ModifyStruct(ref TestStruct s, int newValue)
|
||||||
|
{
|
||||||
|
s.Value = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Modifies a string reference.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="s">A reference to a string variable.</param>
|
||||||
|
/// <param name="newValue">The new string to assign.</param>
|
||||||
|
public static void ModifyStringRef(ref string s, string newValue)
|
||||||
|
{
|
||||||
|
s = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Main entry point for the ldelema test.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>0 if all tests pass, otherwise a non-zero error code.</returns>
|
||||||
|
public static int Main(string[] args)
|
||||||
|
{
|
||||||
|
// --- Test 1: Modifying a value type element in an array ---
|
||||||
|
TestStruct[] structArray = new TestStruct[5];
|
||||||
|
structArray[2].Value = 100;
|
||||||
|
|
||||||
|
// This call should generate an `ldelema` instruction to get the address of structArray[2].
|
||||||
|
ModifyStruct(ref structArray[2], 999);
|
||||||
|
|
||||||
|
if (structArray[2].Value != 999)
|
||||||
|
{
|
||||||
|
return 301; // Unique error code for this test
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Test 2: Modifying a reference type element in an array ---
|
||||||
|
string[] stringArray = new string[] { "alpha", "beta", "gamma" };
|
||||||
|
|
||||||
|
// This call should also generate an `ldelema` instruction.
|
||||||
|
ModifyStringRef(ref stringArray[1], "zeta");
|
||||||
|
|
||||||
|
if (stringArray[1] != "zeta")
|
||||||
|
{
|
||||||
|
return 302; // Unique error code for this test
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; // Success
|
||||||
|
}
|
||||||
|
}
|
@@ -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)
|
||||||
{
|
{
|
128
WoofWare.PawPrint.Test/sourcesPure/LdtokenField.cs
Normal file
128
WoofWare.PawPrint.Test/sourcesPure/LdtokenField.cs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace LdtokenFieldTest
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
// Various field types to test ldtoken with
|
||||||
|
public static int StaticIntField = 42;
|
||||||
|
public string InstanceStringField = "test";
|
||||||
|
private readonly double PrivateReadonlyField = 3.14;
|
||||||
|
internal decimal InternalField;
|
||||||
|
protected bool ProtectedField;
|
||||||
|
public static readonly DateTime StaticReadonlyField = DateTime.MinValue;
|
||||||
|
|
||||||
|
// Generic type fields
|
||||||
|
public GenericClass<int>.NestedClass<string> GenericField;
|
||||||
|
|
||||||
|
static int Main(string[] args)
|
||||||
|
{
|
||||||
|
int testsFailed = 0;
|
||||||
|
|
||||||
|
// Test 1: Static field via FieldInfo
|
||||||
|
FieldInfo staticField = typeof(Program).GetField(nameof(StaticIntField));
|
||||||
|
if (staticField == null || staticField.FieldType != typeof(int))
|
||||||
|
{
|
||||||
|
testsFailed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Instance field via FieldInfo
|
||||||
|
FieldInfo instanceField = typeof(Program).GetField(nameof(InstanceStringField));
|
||||||
|
if (instanceField == null || instanceField.FieldType != typeof(string))
|
||||||
|
{
|
||||||
|
testsFailed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Private field via FieldInfo with binding flags
|
||||||
|
FieldInfo privateField = typeof(Program).GetField("PrivateReadonlyField",
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
if (privateField == null || privateField.FieldType != typeof(double))
|
||||||
|
{
|
||||||
|
testsFailed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: Using RuntimeFieldHandle directly
|
||||||
|
RuntimeFieldHandle handle = staticField.FieldHandle;
|
||||||
|
FieldInfo fieldFromHandle = FieldInfo.GetFieldFromHandle(handle);
|
||||||
|
if (!ReferenceEquals(fieldFromHandle, staticField))
|
||||||
|
{
|
||||||
|
testsFailed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 5: Field from generic type
|
||||||
|
Type genericType = typeof(GenericClass<>);
|
||||||
|
FieldInfo genericFieldInfo = genericType.GetField("GenericField");
|
||||||
|
if (genericFieldInfo == null)
|
||||||
|
{
|
||||||
|
testsFailed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 6: Field from nested type
|
||||||
|
Type nestedType = typeof(OuterClass.InnerClass);
|
||||||
|
FieldInfo nestedField = nestedType.GetField("NestedField");
|
||||||
|
if (nestedField == null || nestedField.FieldType != typeof(int))
|
||||||
|
{
|
||||||
|
testsFailed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 7: Field handle with generic context
|
||||||
|
Type constructedGeneric = typeof(GenericClass<int>);
|
||||||
|
FieldInfo constructedField = constructedGeneric.GetField("GenericField");
|
||||||
|
RuntimeFieldHandle genericHandle = constructedField.FieldHandle;
|
||||||
|
FieldInfo reconstructed = FieldInfo.GetFieldFromHandle(genericHandle, constructedGeneric.TypeHandle);
|
||||||
|
if (reconstructed.DeclaringType != constructedGeneric)
|
||||||
|
{
|
||||||
|
testsFailed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 8: Struct field
|
||||||
|
Type structType = typeof(TestStruct);
|
||||||
|
FieldInfo structField = structType.GetField("StructField");
|
||||||
|
if (structField == null || structField.FieldType != typeof(long))
|
||||||
|
{
|
||||||
|
testsFailed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 9: Volatile field
|
||||||
|
FieldInfo volatileField = typeof(VolatileFieldClass).GetField("VolatileField");
|
||||||
|
if (volatileField == null || !volatileField.GetRequiredCustomModifiers().Any(t => t == typeof(IsVolatile)))
|
||||||
|
{
|
||||||
|
testsFailed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return testsFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supporting types for testing
|
||||||
|
public class GenericClass<T>
|
||||||
|
{
|
||||||
|
public T GenericField;
|
||||||
|
|
||||||
|
public class NestedClass<U>
|
||||||
|
{
|
||||||
|
public U NestedGenericField;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OuterClass
|
||||||
|
{
|
||||||
|
public class InnerClass
|
||||||
|
{
|
||||||
|
public int NestedField = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct TestStruct
|
||||||
|
{
|
||||||
|
public long StructField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VolatileFieldClass
|
||||||
|
{
|
||||||
|
public volatile int VolatileField;
|
||||||
|
}
|
||||||
|
}
|
118
WoofWare.PawPrint.Test/sourcesPure/Sizeof.cs
Normal file
118
WoofWare.PawPrint.Test/sourcesPure/Sizeof.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
unsafe public class Program
|
||||||
|
{
|
||||||
|
public struct SmallStruct
|
||||||
|
{
|
||||||
|
public byte Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct MediumStruct
|
||||||
|
{
|
||||||
|
public int 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;
|
||||||
|
}
|
||||||
|
}
|
54
WoofWare.PawPrint.Test/sourcesPure/StaticVariables.cs
Normal file
54
WoofWare.PawPrint.Test/sourcesPure/StaticVariables.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
public class GenericCounter<T>
|
||||||
|
{
|
||||||
|
private static int count = 0;
|
||||||
|
|
||||||
|
public static void Increment()
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int GetCount()
|
||||||
|
{
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Reset()
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static int Main(string[] argv)
|
||||||
|
{
|
||||||
|
// Test that different generic instantiations have separate static variables
|
||||||
|
|
||||||
|
// Initial state should be 0 for all
|
||||||
|
if (GenericCounter<int>.GetCount() != 0) return 1;
|
||||||
|
if (GenericCounter<string>.GetCount() != 0) return 2;
|
||||||
|
|
||||||
|
// Increment int version 3 times
|
||||||
|
GenericCounter<int>.Increment();
|
||||||
|
GenericCounter<int>.Increment();
|
||||||
|
GenericCounter<int>.Increment();
|
||||||
|
|
||||||
|
// Increment string version 2 times
|
||||||
|
GenericCounter<string>.Increment();
|
||||||
|
GenericCounter<string>.Increment();
|
||||||
|
|
||||||
|
// Verify counts are independent
|
||||||
|
if (GenericCounter<int>.GetCount() != 3) return 3;
|
||||||
|
if (GenericCounter<string>.GetCount() != 2) return 4;
|
||||||
|
|
||||||
|
// Reset int version only
|
||||||
|
GenericCounter<int>.Reset();
|
||||||
|
|
||||||
|
// Verify reset only affected int version
|
||||||
|
if (GenericCounter<int>.GetCount() != 0) return 5;
|
||||||
|
if (GenericCounter<string>.GetCount() != 2) return 6;
|
||||||
|
|
||||||
|
// Test passes - static variables are isolated per generic instantiation
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
41
WoofWare.PawPrint.Test/sourcesPure/TestOr.cs
Normal file
41
WoofWare.PawPrint.Test/sourcesPure/TestOr.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
public class TestOr
|
||||||
|
{
|
||||||
|
public static int Main(string[] args)
|
||||||
|
{
|
||||||
|
// Test 1: Bitwise OR with Int32
|
||||||
|
int a32 = 12; // Binary: 1100
|
||||||
|
int b32 = 10; // Binary: 1010
|
||||||
|
int result32 = a32 | b32; // Should be 14 (Binary: 1110)
|
||||||
|
if (result32 != 14) return 1;
|
||||||
|
|
||||||
|
// Test 2: Bitwise OR with Int64
|
||||||
|
long a64 = 0x00FF00FFL;
|
||||||
|
long b64 = 0xFF00FF00L;
|
||||||
|
long result64 = a64 | b64;
|
||||||
|
if (result64 != 0xFFFFFFFFL) return 2;
|
||||||
|
|
||||||
|
// Test 3: Mixed bitwise OR (Int32 and native int)
|
||||||
|
int aMixed = 15; // Binary: 1111
|
||||||
|
nint bMixed = 240; // Binary: 11110000
|
||||||
|
nint resultMixed = aMixed | bMixed; // Should be 255 (Binary: 11111111)
|
||||||
|
if (resultMixed != 255) return 3;
|
||||||
|
|
||||||
|
// Test 4: OR with itself
|
||||||
|
int self = 42;
|
||||||
|
int selfResult = self | self;
|
||||||
|
if (selfResult != 42) return 4;
|
||||||
|
|
||||||
|
// Test 5: OR with 0
|
||||||
|
int withZero = 123;
|
||||||
|
int zeroResult = withZero | 0;
|
||||||
|
if (zeroResult != 123) return 5;
|
||||||
|
|
||||||
|
// Test 6: Native int OR native int
|
||||||
|
nint nativeA = 0x0F;
|
||||||
|
nint nativeB = 0xF0;
|
||||||
|
nint nativeResult = nativeA | nativeB; // Should be 0xFF
|
||||||
|
if (nativeResult != 0xFF) return 6;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
34
WoofWare.PawPrint.Test/sourcesPure/TestShl.cs
Normal file
34
WoofWare.PawPrint.Test/sourcesPure/TestShl.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
public class TestShl
|
||||||
|
{
|
||||||
|
public static int Main(string[] args)
|
||||||
|
{
|
||||||
|
// Test 1: Shift Left with Int32
|
||||||
|
int value32 = 5; // Binary: 0101
|
||||||
|
int shift32 = 2;
|
||||||
|
int result32 = value32 << shift32; // Should be 20 (Binary: 10100)
|
||||||
|
if (result32 != 20) return 1;
|
||||||
|
|
||||||
|
// Test 2: Shift Left with Int64
|
||||||
|
long value64 = 7L; // Binary: 0111
|
||||||
|
int shift64 = 3;
|
||||||
|
long result64 = value64 << shift64; // Should be 56 (Binary: 111000)
|
||||||
|
if (result64 != 56L) return 2;
|
||||||
|
|
||||||
|
// Test 3: Shift by 0
|
||||||
|
int noShiftValue = 42;
|
||||||
|
int noShiftResult = noShiftValue << 0;
|
||||||
|
if (noShiftResult != 42) return 3;
|
||||||
|
|
||||||
|
// Test 4: Shift by 1
|
||||||
|
int singleShiftResult = noShiftValue << 1;
|
||||||
|
if (singleShiftResult != 84) return 4;
|
||||||
|
|
||||||
|
// Test 5: Shift with native int
|
||||||
|
nint nativeValue = 3;
|
||||||
|
int nativeShift = 4;
|
||||||
|
nint nativeResult = nativeValue << nativeShift; // Should be 48
|
||||||
|
if (nativeResult != 48) return 5;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
35
WoofWare.PawPrint.Test/sourcesPure/TestShr.cs
Normal file
35
WoofWare.PawPrint.Test/sourcesPure/TestShr.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
public class TestShr
|
||||||
|
{
|
||||||
|
public static int Main(string[] args)
|
||||||
|
{
|
||||||
|
// Test 1: Shift Right with Int32
|
||||||
|
int value32 = 20; // Binary: 10100
|
||||||
|
int shift32 = 2;
|
||||||
|
int result32 = value32 >> shift32; // Should be 5 (Binary: 0101)
|
||||||
|
if (result32 != 5) return 1;
|
||||||
|
|
||||||
|
// Test 2: Shift Right with Int64
|
||||||
|
long value64 = 56L; // Binary: 111000
|
||||||
|
int shift64 = 3;
|
||||||
|
long result64 = value64 >> shift64; // Should be 7 (Binary: 0111)
|
||||||
|
if (result64 != 7L) return 2;
|
||||||
|
|
||||||
|
// Test 3: Right shift preserving sign (negative number)
|
||||||
|
int negative = -16;
|
||||||
|
int negativeResult = negative >> 2; // Should be -4
|
||||||
|
if (negativeResult != -4) return 3;
|
||||||
|
|
||||||
|
// Test 4: Shift by 0
|
||||||
|
int noShiftValue = 99;
|
||||||
|
int noShiftResult = noShiftValue >> 0;
|
||||||
|
if (noShiftResult != 99) return 4;
|
||||||
|
|
||||||
|
// Test 5: Shift with native int
|
||||||
|
nint nativeValue = 48;
|
||||||
|
int nativeShift = 4;
|
||||||
|
nint nativeResult = nativeValue >> nativeShift; // Should be 3
|
||||||
|
if (nativeResult != 3) return 5;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
112
WoofWare.PawPrint.Test/sourcesPure/TypeConcretization.cs
Normal file
112
WoofWare.PawPrint.Test/sourcesPure/TypeConcretization.cs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
// Test basic type concretization
|
||||||
|
public class BasicTypeTest
|
||||||
|
{
|
||||||
|
public static int TestBasicTypes()
|
||||||
|
{
|
||||||
|
// Test primitive types
|
||||||
|
int i = 42;
|
||||||
|
string s = "hello";
|
||||||
|
bool b = true;
|
||||||
|
|
||||||
|
if (i != 42) return 1;
|
||||||
|
if (s != "hello") return 2;
|
||||||
|
if (!b) return 3;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test generic type instantiation
|
||||||
|
public class GenericTypeTest<T>
|
||||||
|
{
|
||||||
|
private T value;
|
||||||
|
|
||||||
|
public GenericTypeTest(T val)
|
||||||
|
{
|
||||||
|
value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T GetValue()
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int TestGenericInstantiation()
|
||||||
|
{
|
||||||
|
var intTest = new GenericTypeTest<int>(123);
|
||||||
|
var stringTest = new GenericTypeTest<string>("test");
|
||||||
|
|
||||||
|
if (intTest.GetValue() != 123) return 1;
|
||||||
|
if (stringTest.GetValue() != "test") return 2;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test nested generic types
|
||||||
|
public class NestedGenericTest
|
||||||
|
{
|
||||||
|
public static int TestNestedGenerics()
|
||||||
|
{
|
||||||
|
var listOfInts = new List<int>();
|
||||||
|
listOfInts.Add(1);
|
||||||
|
listOfInts.Add(2);
|
||||||
|
|
||||||
|
if (listOfInts.Count != 2) return 1;
|
||||||
|
if (listOfInts[0] != 1) return 2;
|
||||||
|
if (listOfInts[1] != 2) return 3;
|
||||||
|
|
||||||
|
var listOfLists = new List<List<int>>();
|
||||||
|
listOfLists.Add(listOfInts);
|
||||||
|
|
||||||
|
if (listOfLists.Count != 1) return 4;
|
||||||
|
if (listOfLists[0].Count != 2) return 5;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test generic methods
|
||||||
|
public class GenericMethodTest
|
||||||
|
{
|
||||||
|
public static T Identity<T>(T input)
|
||||||
|
{
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int TestGenericMethods()
|
||||||
|
{
|
||||||
|
int intResult = Identity<int>(42);
|
||||||
|
string stringResult = Identity<string>("hello");
|
||||||
|
|
||||||
|
if (intResult != 42) return 1;
|
||||||
|
if (stringResult != "hello") return 2;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static int Main(string[] args)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
|
||||||
|
result = BasicTypeTest.TestBasicTypes();
|
||||||
|
if (result != 0) return 100 + result;
|
||||||
|
|
||||||
|
result = GenericTypeTest<int>.TestGenericInstantiation();
|
||||||
|
if (result != 0) return 200 + result;
|
||||||
|
|
||||||
|
result = NestedGenericTest.TestNestedGenerics();
|
||||||
|
if (result != 0) return 300 + result;
|
||||||
|
|
||||||
|
result = GenericMethodTest.TestGenericMethods();
|
||||||
|
if (result != 0) return 400 + result;
|
||||||
|
|
||||||
|
return 0; // All tests passed
|
||||||
|
}
|
||||||
|
}
|
@@ -60,22 +60,18 @@ module AbstractMachine =
|
|||||||
|
|
||||||
let delegateToRun = state.ManagedHeap.NonArrayObjects.[delegateToRunAddr]
|
let delegateToRun = state.ManagedHeap.NonArrayObjects.[delegateToRunAddr]
|
||||||
|
|
||||||
if delegateToRun.Fields.["_target"] <> CliType.ObjectRef None then
|
let target =
|
||||||
failwith "TODO: delegate target wasn't None"
|
match delegateToRun.Fields.["_target"] with
|
||||||
|
| CliType.ObjectRef addr -> addr
|
||||||
|
| x -> failwith $"TODO: delegate target wasn't an object ref: %O{x}"
|
||||||
|
|
||||||
let methodPtr =
|
let methodPtr =
|
||||||
match delegateToRun.Fields.["_methodPtr"] with
|
match delegateToRun.Fields.["_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}"
|
||||||
|
|
||||||
let typeGenerics =
|
|
||||||
instruction.ExecutingMethod.DeclaringType.Generics |> ImmutableArray.CreateRange
|
|
||||||
|
|
||||||
let methodGenerics = instruction.ExecutingMethod.Generics
|
let methodGenerics = instruction.ExecutingMethod.Generics
|
||||||
|
|
||||||
let methodPtr =
|
|
||||||
methodPtr |> MethodInfo.mapTypeGenerics (fun i _ -> typeGenerics.[i])
|
|
||||||
|
|
||||||
// When we return, we need to go back up the stack
|
// When we return, we need to go back up the stack
|
||||||
match state |> IlMachineState.returnStackFrame loggerFactory baseClassTypes thread with
|
match state |> IlMachineState.returnStackFrame loggerFactory baseClassTypes thread with
|
||||||
| None -> failwith "unexpectedly nowhere to return from delegate"
|
| None -> failwith "unexpectedly nowhere to return from delegate"
|
||||||
@@ -86,23 +82,42 @@ module AbstractMachine =
|
|||||||
(state, instruction.Arguments)
|
(state, instruction.Arguments)
|
||||||
||> Seq.fold (fun state arg -> IlMachineState.pushToEvalStack arg thread state)
|
||> Seq.fold (fun state arg -> IlMachineState.pushToEvalStack arg thread state)
|
||||||
|
|
||||||
|
// The odd little calling convention strikes again: we push the `target` parameter on top of the
|
||||||
|
// stack, although that doesn't actually happen in the CLR.
|
||||||
|
// We'll pretend we're constructing an object, so that the calling convention gets respected in
|
||||||
|
// `callMethod`.
|
||||||
|
let state, constructing =
|
||||||
|
match target with
|
||||||
|
| None -> state, None
|
||||||
|
| Some target ->
|
||||||
|
let state =
|
||||||
|
IlMachineState.pushToEvalStack (CliType.ObjectRef (Some target)) thread state
|
||||||
|
|
||||||
|
state, Some target
|
||||||
|
|
||||||
let state, _ =
|
let state, _ =
|
||||||
state.WithThreadSwitchedToAssembly methodPtr.DeclaringType.Assembly thread
|
state.WithThreadSwitchedToAssembly methodPtr.DeclaringType.Assembly thread
|
||||||
|
|
||||||
// Don't advance the program counter again on return; that was already done by the Callvirt that
|
// Don't advance the program counter again on return; that was already done by the Callvirt that
|
||||||
// caused this delegate to be invoked.
|
// caused this delegate to be invoked.
|
||||||
let state, result =
|
let currentThreadState = state.ThreadState.[thread]
|
||||||
state
|
|
||||||
|> IlMachineState.callMethodInActiveAssembly
|
let state =
|
||||||
|
IlMachineStateExecution.callMethod
|
||||||
loggerFactory
|
loggerFactory
|
||||||
baseClassTypes
|
baseClassTypes
|
||||||
thread
|
|
||||||
false
|
|
||||||
(Some methodGenerics)
|
|
||||||
methodPtr
|
|
||||||
None
|
None
|
||||||
|
constructing
|
||||||
|
false
|
||||||
|
false
|
||||||
|
false
|
||||||
|
methodGenerics
|
||||||
|
methodPtr
|
||||||
|
thread
|
||||||
|
currentThreadState
|
||||||
|
state
|
||||||
|
|
||||||
ExecutionResult.Stepped (state, result)
|
ExecutionResult.Stepped (state, WhatWeDid.Executed)
|
||||||
| _ ->
|
| _ ->
|
||||||
|
|
||||||
let outcome =
|
let outcome =
|
||||||
@@ -119,34 +134,47 @@ module AbstractMachine =
|
|||||||
"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}"
|
||||||
|
@@ -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
|
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
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
|
||||||
@@ -41,6 +40,7 @@ type ManagedPointerSource =
|
|||||||
| 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
|
||||||
| Heap of ManagedHeapAddress
|
| Heap of ManagedHeapAddress
|
||||||
|
| ArrayIndex of arr : ManagedHeapAddress * index : int
|
||||||
| Null
|
| Null
|
||||||
|
|
||||||
override this.ToString () =
|
override this.ToString () =
|
||||||
@@ -51,6 +51,7 @@ type ManagedPointerSource =
|
|||||||
$"<variable %i{var} in method frame %i{method} of thread %O{source}>"
|
$"<variable %i{var} in method frame %i{method} of thread %O{source}>"
|
||||||
| 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}>"
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
type UnsignedNativeIntSource =
|
type UnsignedNativeIntSource =
|
||||||
@@ -61,8 +62,8 @@ type UnsignedNativeIntSource =
|
|||||||
type NativeIntSource =
|
type NativeIntSource =
|
||||||
| Verbatim of int64
|
| Verbatim of int64
|
||||||
| ManagedPointer of ManagedPointerSource
|
| ManagedPointer of ManagedPointerSource
|
||||||
| FunctionPointer of MethodInfo<FakeUnit, WoofWare.PawPrint.GenericParameter>
|
| 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
|
||||||
@@ -70,7 +71,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 =
|
||||||
@@ -112,23 +113,12 @@ 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
|
||||||
| Heap of ManagedHeapAddress
|
| Heap of ManagedHeapAddress
|
||||||
|
| ArrayIndex of arr : ManagedHeapAddress * index : int
|
||||||
| Null
|
| Null
|
||||||
|
|
||||||
type CliRuntimePointer =
|
type CliRuntimePointer =
|
||||||
@@ -149,15 +139,12 @@ 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 CliType list
|
| ValueType of CliValueType
|
||||||
|
|
||||||
/// In fact any non-zero value will do for True, but we'll use 1
|
and CliValueType =
|
||||||
static member OfBool (b : bool) = CliType.Bool (if b then 1uy else 0uy)
|
{
|
||||||
|
Fields : (string * CliType) list
|
||||||
static member OfChar (c : char) =
|
}
|
||||||
CliType.Char (byte (int c / 256), byte (int c % 256))
|
|
||||||
|
|
||||||
static member OfManagedObject (ptr : ManagedHeapAddress) = CliType.ObjectRef (Some ptr)
|
|
||||||
|
|
||||||
type CliTypeResolutionResult =
|
type CliTypeResolutionResult =
|
||||||
| Resolved of CliType
|
| Resolved of CliType
|
||||||
@@ -165,6 +152,37 @@ 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"
|
||||||
|
| [ _, f ] -> sizeOf f
|
||||||
|
| _ -> failwith $"TODO: %O{vt.Fields} (need to consider struct layout)"
|
||||||
|
|
||||||
let zeroOfPrimitive (primitiveType : PrimitiveType) : CliType =
|
let zeroOfPrimitive (primitiveType : PrimitiveType) : CliType =
|
||||||
match primitiveType with
|
match primitiveType with
|
||||||
@@ -191,92 +209,212 @@ module CliType =
|
|||||||
| PrimitiveType.Object -> CliType.ObjectRef None
|
| PrimitiveType.Object -> CliType.ObjectRef None
|
||||||
|
|
||||||
let rec zeroOf
|
let rec zeroOf
|
||||||
|
(concreteTypes : AllConcreteTypes)
|
||||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||||
(assy : DumpedAssembly)
|
(handle : ConcreteTypeHandle)
|
||||||
(typeGenerics : TypeDefn ImmutableArray option)
|
: CliType * AllConcreteTypes
|
||||||
(methodGenerics : TypeDefn ImmutableArray option)
|
|
||||||
(ty : TypeDefn)
|
|
||||||
: CliTypeResolutionResult
|
|
||||||
=
|
=
|
||||||
match ty with
|
zeroOfWithVisited concreteTypes assemblies corelib handle Set.empty
|
||||||
| TypeDefn.PrimitiveType primitiveType -> CliTypeResolutionResult.Resolved (zeroOfPrimitive primitiveType)
|
|
||||||
| TypeDefn.Array _ -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
|
|
||||||
| TypeDefn.Pinned typeDefn -> failwith "todo"
|
|
||||||
| TypeDefn.Pointer _ ->
|
|
||||||
CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
|
|
||||||
|> CliTypeResolutionResult.Resolved
|
|
||||||
| TypeDefn.Byref _ -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
|
|
||||||
| TypeDefn.OneDimensionalArrayLowerBoundZero _ -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
|
|
||||||
| TypeDefn.Modified (original, afterMod, modificationRequired) -> failwith "todo"
|
|
||||||
| TypeDefn.FromReference (typeRef, signatureTypeKind) ->
|
|
||||||
match signatureTypeKind with
|
|
||||||
| SignatureTypeKind.Unknown -> failwith "todo"
|
|
||||||
| SignatureTypeKind.ValueType ->
|
|
||||||
match Assembly.resolveTypeRef assemblies assy typeRef typeGenerics with
|
|
||||||
| TypeResolutionResult.Resolved (sourceAssy, ty) ->
|
|
||||||
let fields =
|
|
||||||
ty.Fields
|
|
||||||
|> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static))
|
|
||||||
|> List.map (fun fi ->
|
|
||||||
match zeroOf assemblies corelib sourceAssy typeGenerics methodGenerics fi.Signature with
|
|
||||||
| CliTypeResolutionResult.Resolved ty -> Ok ty
|
|
||||||
| CliTypeResolutionResult.FirstLoad a -> Error a
|
|
||||||
)
|
|
||||||
|> Result.allOkOrError
|
|
||||||
|
|
||||||
match fields with
|
and zeroOfWithVisited
|
||||||
| Error (_, []) -> failwith "logic error"
|
(concreteTypes : AllConcreteTypes)
|
||||||
| Error (_, f :: _) -> CliTypeResolutionResult.FirstLoad f
|
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||||
| Ok fields -> CliType.ValueType fields |> CliTypeResolutionResult.Resolved
|
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||||
| TypeResolutionResult.FirstLoadAssy assy -> CliTypeResolutionResult.FirstLoad assy
|
(handle : ConcreteTypeHandle)
|
||||||
| SignatureTypeKind.Class -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
|
(visited : Set<ConcreteTypeHandle>)
|
||||||
| _ -> raise (ArgumentOutOfRangeException ())
|
: CliType * AllConcreteTypes
|
||||||
| TypeDefn.FromDefinition (typeDefinitionHandle, _, signatureTypeKind) ->
|
=
|
||||||
let typeDef = assy.TypeDefs.[typeDefinitionHandle.Get]
|
|
||||||
|
|
||||||
if typeDef = corelib.Int32 then
|
// Handle constructed types first
|
||||||
zeroOfPrimitive PrimitiveType.Int32 |> CliTypeResolutionResult.Resolved
|
match handle with
|
||||||
elif typeDef = corelib.Int64 then
|
| ConcreteTypeHandle.Byref _ ->
|
||||||
zeroOfPrimitive PrimitiveType.Int64 |> CliTypeResolutionResult.Resolved
|
// Byref types are managed references - the zero value is a null reference
|
||||||
elif typeDef = corelib.UInt32 then
|
CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null), concreteTypes
|
||||||
zeroOfPrimitive PrimitiveType.UInt32 |> CliTypeResolutionResult.Resolved
|
|
||||||
elif typeDef = corelib.UInt64 then
|
| ConcreteTypeHandle.Pointer _ ->
|
||||||
zeroOfPrimitive PrimitiveType.UInt64 |> CliTypeResolutionResult.Resolved
|
// Pointer types are unmanaged pointers - the zero value is a null pointer
|
||||||
|
CliType.RuntimePointer (CliRuntimePointer.Unmanaged 0L), concreteTypes
|
||||||
|
|
||||||
|
| ConcreteTypeHandle.Concrete _ ->
|
||||||
|
// This is a concrete type - look it up in the mapping
|
||||||
|
let concreteType =
|
||||||
|
match AllConcreteTypes.lookup handle concreteTypes with
|
||||||
|
| Some ct -> ct
|
||||||
|
| None -> failwithf "ConcreteTypeHandle %A not found in AllConcreteTypes" handle
|
||||||
|
|
||||||
|
// Get the type definition from the assembly
|
||||||
|
let assembly = assemblies.[concreteType.Assembly.FullName]
|
||||||
|
let typeDef = assembly.TypeDefs.[concreteType.Definition.Get]
|
||||||
|
|
||||||
|
// Check if it's a primitive type by comparing with corelib types FIRST
|
||||||
|
if concreteType.Assembly = corelib.Corelib.Name && concreteType.Generics.IsEmpty then
|
||||||
|
// Check against known primitive types
|
||||||
|
if TypeInfo.NominallyEqual typeDef corelib.Boolean then
|
||||||
|
zeroOfPrimitive PrimitiveType.Boolean, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.Char then
|
||||||
|
zeroOfPrimitive PrimitiveType.Char, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.SByte then
|
||||||
|
zeroOfPrimitive PrimitiveType.SByte, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.Byte then
|
||||||
|
zeroOfPrimitive PrimitiveType.Byte, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.Int16 then
|
||||||
|
zeroOfPrimitive PrimitiveType.Int16, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.UInt16 then
|
||||||
|
zeroOfPrimitive PrimitiveType.UInt16, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.Int32 then
|
||||||
|
zeroOfPrimitive PrimitiveType.Int32, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.UInt32 then
|
||||||
|
zeroOfPrimitive PrimitiveType.UInt32, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.Int64 then
|
||||||
|
zeroOfPrimitive PrimitiveType.Int64, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.UInt64 then
|
||||||
|
zeroOfPrimitive PrimitiveType.UInt64, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.Single then
|
||||||
|
zeroOfPrimitive PrimitiveType.Single, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.Double then
|
||||||
|
zeroOfPrimitive PrimitiveType.Double, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.String then
|
||||||
|
zeroOfPrimitive PrimitiveType.String, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.Object then
|
||||||
|
zeroOfPrimitive PrimitiveType.Object, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.IntPtr then
|
||||||
|
zeroOfPrimitive PrimitiveType.IntPtr, concreteTypes
|
||||||
|
elif TypeInfo.NominallyEqual typeDef corelib.UIntPtr then
|
||||||
|
zeroOfPrimitive PrimitiveType.UIntPtr, concreteTypes
|
||||||
|
else if
|
||||||
|
// Check if it's an array type
|
||||||
|
typeDef = corelib.Array
|
||||||
|
then
|
||||||
|
CliType.ObjectRef None, concreteTypes // Arrays are reference types
|
||||||
|
else if
|
||||||
|
// Not a known primitive, now check for cycles
|
||||||
|
Set.contains handle visited
|
||||||
|
then
|
||||||
|
// We're in a cycle - return a default zero value for the type
|
||||||
|
// For value types in cycles, we'll return a null reference as a safe fallback
|
||||||
|
// This should only happen with self-referential types
|
||||||
|
CliType.ObjectRef None, concreteTypes
|
||||||
|
else
|
||||||
|
let visited = Set.add handle visited
|
||||||
|
// Not a known primitive, check if it's a value type or reference type
|
||||||
|
determineZeroForCustomType concreteTypes assemblies corelib handle concreteType typeDef visited
|
||||||
|
else if
|
||||||
|
// Not from corelib or has generics
|
||||||
|
concreteType.Assembly = corelib.Corelib.Name
|
||||||
|
&& typeDef = corelib.Array
|
||||||
|
&& concreteType.Generics.Length = 1
|
||||||
|
then
|
||||||
|
// This is an array type
|
||||||
|
CliType.ObjectRef None, concreteTypes
|
||||||
|
else if
|
||||||
|
// Custom type - now check for cycles
|
||||||
|
Set.contains handle visited
|
||||||
|
then
|
||||||
|
// We're in a cycle - return a default zero value for the type
|
||||||
|
// For value types in cycles, we'll return a null reference as a safe fallback
|
||||||
|
// This should only happen with self-referential types
|
||||||
|
CliType.ObjectRef None, concreteTypes
|
||||||
else
|
else
|
||||||
// TODO: the rest
|
let visited = Set.add handle visited
|
||||||
match signatureTypeKind with
|
// Custom type - need to determine if it's a value type or reference type
|
||||||
| SignatureTypeKind.Unknown -> failwith "todo"
|
determineZeroForCustomType concreteTypes assemblies corelib handle concreteType typeDef visited
|
||||||
| SignatureTypeKind.ValueType ->
|
|
||||||
let fields =
|
|
||||||
typeDef.Fields
|
|
||||||
// oh lord, this is awfully ominous - I really don't want to store the statics here
|
|
||||||
|> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static))
|
|
||||||
|> List.map (fun fi ->
|
|
||||||
match zeroOf assemblies corelib assy typeGenerics methodGenerics fi.Signature with
|
|
||||||
| CliTypeResolutionResult.Resolved ty -> Ok ty
|
|
||||||
| CliTypeResolutionResult.FirstLoad a -> Error a
|
|
||||||
)
|
|
||||||
|> Result.allOkOrError
|
|
||||||
|
|
||||||
match fields with
|
and private determineZeroForCustomType
|
||||||
| Error (_, []) -> failwith "logic error"
|
(concreteTypes : AllConcreteTypes)
|
||||||
| Error (_, f :: _) -> CliTypeResolutionResult.FirstLoad f
|
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||||
| Ok fields ->
|
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||||
|
(handle : ConcreteTypeHandle)
|
||||||
|
(concreteType : ConcreteType<ConcreteTypeHandle>)
|
||||||
|
(typeDef : WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn>)
|
||||||
|
(visited : Set<ConcreteTypeHandle>)
|
||||||
|
: CliType * AllConcreteTypes
|
||||||
|
=
|
||||||
|
|
||||||
CliType.ValueType fields |> CliTypeResolutionResult.Resolved
|
// Determine if this is a value type by checking inheritance
|
||||||
| SignatureTypeKind.Class -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
|
let isValueType =
|
||||||
| _ -> raise (ArgumentOutOfRangeException ())
|
match DumpedAssembly.resolveBaseType corelib assemblies typeDef.Assembly typeDef.BaseType with
|
||||||
| TypeDefn.GenericInstantiation (generic, args) ->
|
| ResolvedBaseType.ValueType
|
||||||
zeroOf assemblies corelib assy (Some args) methodGenerics generic
|
| ResolvedBaseType.Enum -> true
|
||||||
| TypeDefn.FunctionPointer typeMethodSignature -> failwith "todo"
|
| ResolvedBaseType.Delegate -> false // Delegates are reference types
|
||||||
| TypeDefn.GenericTypeParameter index ->
|
| ResolvedBaseType.Object -> false
|
||||||
// TODO: can generics depend on other generics? presumably, so we pass the array down again
|
|
||||||
match typeGenerics with
|
if isValueType then
|
||||||
| None -> failwith "asked for a type parameter of generic type, but no generics in scope"
|
// It's a value type - need to create zero values for all non-static fields
|
||||||
| Some generics -> zeroOf assemblies corelib assy (Some generics) methodGenerics generics.[index]
|
let mutable currentConcreteTypes = concreteTypes
|
||||||
| TypeDefn.GenericMethodParameter index ->
|
|
||||||
match methodGenerics with
|
let fieldZeros =
|
||||||
| None -> failwith "asked for a method parameter of generic type, but no generics in scope"
|
typeDef.Fields
|
||||||
| Some generics -> zeroOf assemblies corelib assy typeGenerics (Some generics) generics.[index]
|
|> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static))
|
||||||
| TypeDefn.Void -> failwith "should never construct an element of type Void"
|
|> List.map (fun field ->
|
||||||
|
// Need to concretize the field type with the concrete type's generics
|
||||||
|
let fieldTypeDefn = field.Signature
|
||||||
|
|
||||||
|
let fieldHandle, updatedConcreteTypes =
|
||||||
|
concretizeFieldType currentConcreteTypes assemblies corelib concreteType fieldTypeDefn
|
||||||
|
|
||||||
|
currentConcreteTypes <- updatedConcreteTypes
|
||||||
|
|
||||||
|
let fieldZero, updatedConcreteTypes2 =
|
||||||
|
zeroOfWithVisited currentConcreteTypes assemblies corelib fieldHandle visited
|
||||||
|
|
||||||
|
currentConcreteTypes <- updatedConcreteTypes2
|
||||||
|
(field.Name, fieldZero)
|
||||||
|
)
|
||||||
|
|
||||||
|
let vt =
|
||||||
|
{
|
||||||
|
Fields = fieldZeros
|
||||||
|
}
|
||||||
|
|
||||||
|
CliType.ValueType vt, currentConcreteTypes
|
||||||
|
else
|
||||||
|
// It's a reference type
|
||||||
|
CliType.ObjectRef None, concreteTypes
|
||||||
|
|
||||||
|
and private concretizeFieldType
|
||||||
|
(concreteTypes : AllConcreteTypes)
|
||||||
|
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||||
|
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||||
|
(declaringType : ConcreteType<ConcreteTypeHandle>)
|
||||||
|
(fieldType : TypeDefn)
|
||||||
|
: ConcreteTypeHandle * AllConcreteTypes
|
||||||
|
=
|
||||||
|
|
||||||
|
// Create a concretization context
|
||||||
|
let ctx =
|
||||||
|
{
|
||||||
|
TypeConcretization.ConcretizationContext.InProgress = ImmutableDictionary.Empty
|
||||||
|
TypeConcretization.ConcretizationContext.ConcreteTypes = concreteTypes
|
||||||
|
TypeConcretization.ConcretizationContext.LoadedAssemblies = assemblies
|
||||||
|
TypeConcretization.ConcretizationContext.BaseTypes = corelib
|
||||||
|
}
|
||||||
|
|
||||||
|
// The field type might reference generic parameters of the declaring type
|
||||||
|
let methodGenerics = ImmutableArray.Empty // Fields don't have method generics
|
||||||
|
|
||||||
|
let loadAssembly
|
||||||
|
(assyName : AssemblyName)
|
||||||
|
(ref : AssemblyReferenceHandle)
|
||||||
|
: ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly
|
||||||
|
=
|
||||||
|
match assemblies.TryGetValue assyName.FullName with
|
||||||
|
| true, currentAssy ->
|
||||||
|
let targetAssyRef = currentAssy.AssemblyReferences.[ref]
|
||||||
|
|
||||||
|
match assemblies.TryGetValue targetAssyRef.Name.FullName with
|
||||||
|
| true, targetAssy -> assemblies, targetAssy
|
||||||
|
| false, _ ->
|
||||||
|
failwithf "Assembly %s not loaded when trying to resolve reference" targetAssyRef.Name.FullName
|
||||||
|
| false, _ -> failwithf "Current assembly %s not loaded when trying to resolve reference" assyName.FullName
|
||||||
|
|
||||||
|
let handle, newCtx =
|
||||||
|
TypeConcretization.concretizeType
|
||||||
|
ctx
|
||||||
|
loadAssembly
|
||||||
|
declaringType.Assembly
|
||||||
|
declaringType.Generics
|
||||||
|
methodGenerics
|
||||||
|
fieldType
|
||||||
|
|
||||||
|
handle, newCtx.ConcreteTypes
|
||||||
|
58
WoofWare.PawPrint/BinaryArithmetic.fs
Normal file
58
WoofWare.PawPrint/BinaryArithmetic.fs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
namespace WoofWare.PawPrint
|
||||||
|
|
||||||
|
#nowarn "42"
|
||||||
|
|
||||||
|
type IArithmeticOperation =
|
||||||
|
abstract Int32Int32 : int32 -> int32 -> int32
|
||||||
|
abstract Int64Int64 : int64 -> int64 -> int64
|
||||||
|
abstract FloatFloat : float -> float -> float
|
||||||
|
abstract Name : string
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module ArithmeticOperation =
|
||||||
|
let add =
|
||||||
|
{ new IArithmeticOperation with
|
||||||
|
member _.Int32Int32 a b = (# "add" a b : int32 #)
|
||||||
|
member _.Int64Int64 a b = (# "add" a b : int64 #)
|
||||||
|
member _.FloatFloat a b = (# "add" a b : float #)
|
||||||
|
member _.Name = "add"
|
||||||
|
}
|
||||||
|
|
||||||
|
let sub =
|
||||||
|
{ new IArithmeticOperation with
|
||||||
|
member _.Int32Int32 a b = (# "sub" a b : int32 #)
|
||||||
|
member _.Int64Int64 a b = (# "sub" a b : int64 #)
|
||||||
|
member _.FloatFloat a b = (# "sub" a b : float #)
|
||||||
|
member _.Name = "sub"
|
||||||
|
}
|
||||||
|
|
||||||
|
let mul =
|
||||||
|
{ new IArithmeticOperation with
|
||||||
|
member _.Int32Int32 a b = (# "mul" a b : int32 #)
|
||||||
|
member _.Int64Int64 a b = (# "mul" a b : int64 #)
|
||||||
|
member _.FloatFloat a b = (# "mul" a b : float #)
|
||||||
|
member _.Name = "mul"
|
||||||
|
}
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module BinaryArithmetic =
|
||||||
|
let execute (op : IArithmeticOperation) (val1 : EvalStackValue) (val2 : EvalStackValue) : EvalStackValue =
|
||||||
|
// see table at https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.add?view=net-9.0
|
||||||
|
match val1, val2 with
|
||||||
|
| EvalStackValue.Int32 val1, EvalStackValue.Int32 val2 -> op.Int32Int32 val1 val2 |> EvalStackValue.Int32
|
||||||
|
| EvalStackValue.Int32 val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.NativeInt
|
||||||
|
| EvalStackValue.Int32 val1, EvalStackValue.ManagedPointer val2 -> failwith "" |> EvalStackValue.ManagedPointer
|
||||||
|
| EvalStackValue.Int32 val1, EvalStackValue.ObjectRef val2 -> failwith "" |> EvalStackValue.ObjectRef
|
||||||
|
| EvalStackValue.Int64 val1, EvalStackValue.Int64 val2 -> op.Int64Int64 val1 val2 |> EvalStackValue.Int64
|
||||||
|
| EvalStackValue.NativeInt val1, EvalStackValue.Int32 val2 -> failwith "" |> EvalStackValue.NativeInt
|
||||||
|
| EvalStackValue.NativeInt val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.NativeInt
|
||||||
|
| EvalStackValue.NativeInt val1, EvalStackValue.ManagedPointer val2 ->
|
||||||
|
failwith "" |> EvalStackValue.ManagedPointer
|
||||||
|
| EvalStackValue.NativeInt val1, EvalStackValue.ObjectRef val2 -> failwith "" |> EvalStackValue.ObjectRef
|
||||||
|
| EvalStackValue.Float val1, EvalStackValue.Float val2 -> op.FloatFloat val1 val2 |> EvalStackValue.Float
|
||||||
|
| EvalStackValue.ManagedPointer val1, EvalStackValue.NativeInt val2 ->
|
||||||
|
failwith "" |> EvalStackValue.ManagedPointer
|
||||||
|
| EvalStackValue.ObjectRef val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.ObjectRef
|
||||||
|
| EvalStackValue.ManagedPointer val1, EvalStackValue.Int32 val2 -> failwith "" |> EvalStackValue.ManagedPointer
|
||||||
|
| EvalStackValue.ObjectRef val1, EvalStackValue.Int32 val2 -> failwith "" |> EvalStackValue.ObjectRef
|
||||||
|
| val1, val2 -> failwith $"invalid %s{op.Name} operation: {val1} and {val2}"
|
@@ -114,6 +114,41 @@ module Corelib =
|
|||||||
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "RuntimeFieldHandle" then Some v else None)
|
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "RuntimeFieldHandle" then Some v else None)
|
||||||
|> Seq.exactlyOne
|
|> Seq.exactlyOne
|
||||||
|
|
||||||
|
let voidType =
|
||||||
|
corelib.TypeDefs
|
||||||
|
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "Void" then Some v else None)
|
||||||
|
|> Seq.exactlyOne
|
||||||
|
|
||||||
|
let typedReferenceType =
|
||||||
|
corelib.TypeDefs
|
||||||
|
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "TypedReference" then Some v else None)
|
||||||
|
|> Seq.exactlyOne
|
||||||
|
|
||||||
|
let intPtrType =
|
||||||
|
corelib.TypeDefs
|
||||||
|
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "IntPtr" then Some v else None)
|
||||||
|
|> Seq.exactlyOne
|
||||||
|
|
||||||
|
let uintPtrType =
|
||||||
|
corelib.TypeDefs
|
||||||
|
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "UIntPtr" then Some v else None)
|
||||||
|
|> Seq.exactlyOne
|
||||||
|
|
||||||
|
let runtimeFieldInfoStubType =
|
||||||
|
corelib.TypeDefs
|
||||||
|
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "RuntimeFieldInfoStub" then Some v else None)
|
||||||
|
|> Seq.exactlyOne
|
||||||
|
|
||||||
|
let runtimeFieldHandleInternalType =
|
||||||
|
corelib.TypeDefs
|
||||||
|
|> Seq.choose (fun (KeyValue (_, v)) ->
|
||||||
|
if v.Name = "RuntimeFieldHandleInternal" then
|
||||||
|
Some v
|
||||||
|
else
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|> Seq.exactlyOne
|
||||||
|
|
||||||
{
|
{
|
||||||
Corelib = corelib
|
Corelib = corelib
|
||||||
String = stringType
|
String = stringType
|
||||||
@@ -137,5 +172,11 @@ module Corelib =
|
|||||||
RuntimeTypeHandle = runtimeTypeHandleType
|
RuntimeTypeHandle = runtimeTypeHandleType
|
||||||
RuntimeMethodHandle = runtimeMethodHandleType
|
RuntimeMethodHandle = runtimeMethodHandleType
|
||||||
RuntimeFieldHandle = runtimeFieldHandleType
|
RuntimeFieldHandle = runtimeFieldHandleType
|
||||||
|
RuntimeFieldInfoStub = runtimeFieldInfoStubType
|
||||||
|
RuntimeFieldHandleInternal = runtimeFieldHandleInternalType
|
||||||
RuntimeType = runtimeTypeType
|
RuntimeType = runtimeTypeType
|
||||||
|
Void = voidType
|
||||||
|
TypedReference = typedReferenceType
|
||||||
|
IntPtr = intPtrType
|
||||||
|
UIntPtr = uintPtrType
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,8 @@ 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
|
||||||
| UserDefinedValueType of EvalStackValue list
|
/// Mapping of field name to value
|
||||||
|
| UserDefinedValueType of (string * EvalStackValue) list
|
||||||
|
|
||||||
override this.ToString () =
|
override this.ToString () =
|
||||||
match this with
|
match this with
|
||||||
@@ -21,7 +22,11 @@ type EvalStackValue =
|
|||||||
| EvalStackValue.ManagedPointer managedPointerSource -> $"Pointer(%O{managedPointerSource})"
|
| EvalStackValue.ManagedPointer managedPointerSource -> $"Pointer(%O{managedPointerSource})"
|
||||||
| EvalStackValue.ObjectRef managedHeapAddress -> $"ObjectRef(%O{managedHeapAddress})"
|
| EvalStackValue.ObjectRef managedHeapAddress -> $"ObjectRef(%O{managedHeapAddress})"
|
||||||
| EvalStackValue.UserDefinedValueType evalStackValues ->
|
| EvalStackValue.UserDefinedValueType evalStackValues ->
|
||||||
let desc = evalStackValues |> List.map string<EvalStackValue> |> String.concat " | "
|
let desc =
|
||||||
|
evalStackValues
|
||||||
|
|> List.map (snd >> string<EvalStackValue>)
|
||||||
|
|> String.concat " | "
|
||||||
|
|
||||||
$"Struct(%s{desc})"
|
$"Struct(%s{desc})"
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -87,8 +92,8 @@ module EvalStackValue =
|
|||||||
/// Then truncates to int64.
|
/// Then truncates to int64.
|
||||||
let convToUInt64 (value : EvalStackValue) : int64 option =
|
let convToUInt64 (value : EvalStackValue) : int64 option =
|
||||||
match value with
|
match value with
|
||||||
| EvalStackValue.Int32 i -> if i >= 0 then Some (int64 i) else failwith "TODO"
|
| EvalStackValue.Int32 i -> Some (int64 (uint32 i))
|
||||||
| EvalStackValue.Int64 int64 -> failwith "todo"
|
| EvalStackValue.Int64 int64 -> Some int64
|
||||||
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
|
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
|
||||||
| EvalStackValue.Float f -> failwith "todo"
|
| EvalStackValue.Float f -> failwith "todo"
|
||||||
| EvalStackValue.ManagedPointer managedPointerSource -> failwith "todo"
|
| EvalStackValue.ManagedPointer managedPointerSource -> failwith "todo"
|
||||||
@@ -102,7 +107,7 @@ 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 popped
|
| EvalStackValue.UserDefinedValueType [ popped ] -> toCliTypeCoerced target (snd popped)
|
||||||
| i -> failwith $"TODO: %O{i}"
|
| i -> failwith $"TODO: %O{i}"
|
||||||
| CliNumericType.Int64 _ ->
|
| CliNumericType.Int64 _ ->
|
||||||
match popped with
|
match popped with
|
||||||
@@ -162,6 +167,8 @@ module EvalStackValue =
|
|||||||
|> CliType.RuntimePointer
|
|> CliType.RuntimePointer
|
||||||
| ManagedPointerSource.Heap managedHeapAddress -> CliType.ObjectRef (Some managedHeapAddress)
|
| ManagedPointerSource.Heap managedHeapAddress -> CliType.ObjectRef (Some managedHeapAddress)
|
||||||
| ManagedPointerSource.Null -> CliType.ObjectRef None
|
| 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
|
||||||
@@ -175,7 +182,7 @@ module EvalStackValue =
|
|||||||
| _ -> failwith "TODO"
|
| _ -> failwith "TODO"
|
||||||
| EvalStackValue.UserDefinedValueType fields ->
|
| EvalStackValue.UserDefinedValueType fields ->
|
||||||
match fields with
|
match fields with
|
||||||
| [ esv ] -> toCliTypeCoerced target esv
|
| [ esv ] -> toCliTypeCoerced target (snd esv)
|
||||||
| 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 _ ->
|
||||||
@@ -190,7 +197,7 @@ module EvalStackValue =
|
|||||||
match popped with
|
match popped with
|
||||||
| EvalStackValue.ManagedPointer src ->
|
| EvalStackValue.ManagedPointer src ->
|
||||||
match src with
|
match src with
|
||||||
| ManagedPointerSource.Heap addr -> CliType.OfManagedObject addr
|
| ManagedPointerSource.Heap addr -> CliType.ofManagedObject addr
|
||||||
| ManagedPointerSource.Null -> CliType.ObjectRef None
|
| ManagedPointerSource.Null -> CliType.ObjectRef None
|
||||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, var) ->
|
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, var) ->
|
||||||
CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, var)
|
CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, var)
|
||||||
@@ -200,6 +207,10 @@ module EvalStackValue =
|
|||||||
CliRuntimePointerSource.Argument (sourceThread, methodFrame, var)
|
CliRuntimePointerSource.Argument (sourceThread, methodFrame, var)
|
||||||
|> CliRuntimePointer.Managed
|
|> CliRuntimePointer.Managed
|
||||||
|> CliType.RuntimePointer
|
|> 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)
|
||||||
@@ -215,6 +226,7 @@ module EvalStackValue =
|
|||||||
)
|
)
|
||||||
| ManagedPointerSource.Argument (a, b, c) ->
|
| ManagedPointerSource.Argument (a, b, c) ->
|
||||||
CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.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"
|
||||||
@@ -226,16 +238,31 @@ 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.Length then
|
||||||
failwith "mismatch"
|
failwith
|
||||||
|
$"mismatch: popped value type {popped} (length %i{popped.Length}) into {vt} (length %i{vt.Fields.Length})"
|
||||||
|
|
||||||
List.map2 toCliTypeCoerced fields popped |> CliType.ValueType
|
let fields =
|
||||||
|
List.map2
|
||||||
|
(fun (name1, v1) (name2, v2) ->
|
||||||
|
if name1 <> name2 then
|
||||||
|
failwith $"TODO: name mismatch, {name1} vs {name2}"
|
||||||
|
|
||||||
|
name1, toCliTypeCoerced v1 v2
|
||||||
|
)
|
||||||
|
vt.Fields
|
||||||
|
popped
|
||||||
|
|
||||||
|
{
|
||||||
|
Fields = fields
|
||||||
|
}
|
||||||
|
|> CliType.ValueType
|
||||||
| popped ->
|
| popped ->
|
||||||
match fields with
|
match vt.Fields with
|
||||||
| [ target ] -> toCliTypeCoerced target popped
|
| [ _, target ] -> toCliTypeCoerced target 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 =
|
||||||
@@ -269,12 +296,17 @@ module EvalStackValue =
|
|||||||
| CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, var) ->
|
| CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, var) ->
|
||||||
ManagedPointerSource.LocalVariable (sourceThread, methodFrame, var)
|
ManagedPointerSource.LocalVariable (sourceThread, methodFrame, var)
|
||||||
|> EvalStackValue.ManagedPointer
|
|> EvalStackValue.ManagedPointer
|
||||||
|
| CliRuntimePointerSource.ArrayIndex (arr, ind) ->
|
||||||
|
ManagedPointerSource.ArrayIndex (arr, ind) |> EvalStackValue.ManagedPointer
|
||||||
| CliRuntimePointerSource.Argument (sourceThread, methodFrame, var) ->
|
| CliRuntimePointerSource.Argument (sourceThread, methodFrame, var) ->
|
||||||
ManagedPointerSource.Argument (sourceThread, methodFrame, var)
|
ManagedPointerSource.Argument (sourceThread, methodFrame, var)
|
||||||
|> 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
|
||||||
| CliType.ValueType fields -> fields |> List.map ofCliType |> EvalStackValue.UserDefinedValueType
|
| CliType.ValueType fields ->
|
||||||
|
fields.Fields
|
||||||
|
|> List.map (fun (name, f) -> name, ofCliType f)
|
||||||
|
|> EvalStackValue.UserDefinedValueType
|
||||||
|
|
||||||
type EvalStack =
|
type EvalStack =
|
||||||
{
|
{
|
||||||
@@ -308,3 +340,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
|
||||||
|
158
WoofWare.PawPrint/EvalStackValueComparisons.fs
Normal file
158
WoofWare.PawPrint/EvalStackValueComparisons.fs
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
namespace WoofWare.PawPrint
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module EvalStackValueComparisons =
|
||||||
|
|
||||||
|
let clt (var1 : EvalStackValue) (var2 : EvalStackValue) : bool =
|
||||||
|
match var1, var2 with
|
||||||
|
| EvalStackValue.Int64 var1, EvalStackValue.Int64 var2 -> var1 < var2
|
||||||
|
| EvalStackValue.Float var1, EvalStackValue.Float var2 -> var1 < var2
|
||||||
|
| EvalStackValue.ObjectRef var1, EvalStackValue.ObjectRef var2 ->
|
||||||
|
failwith $"Clt instruction invalid for comparing object refs, {var1} vs {var2}"
|
||||||
|
| EvalStackValue.ObjectRef var1, other -> failwith $"invalid comparison, ref %O{var1} vs %O{other}"
|
||||||
|
| other, EvalStackValue.ObjectRef var2 -> failwith $"invalid comparison, %O{other} vs ref %O{var2}"
|
||||||
|
| EvalStackValue.Float i, other -> failwith $"invalid comparison, float %f{i} vs %O{other}"
|
||||||
|
| other, EvalStackValue.Float i -> failwith $"invalid comparison, %O{other} vs float %f{i}"
|
||||||
|
| EvalStackValue.Int64 i, other -> failwith $"invalid comparison, int64 %i{i} vs %O{other}"
|
||||||
|
| other, EvalStackValue.Int64 i -> failwith $"invalid comparison, %O{other} vs int64 %i{i}"
|
||||||
|
| EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> var1 < var2
|
||||||
|
| EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 ->
|
||||||
|
failwith "TODO: Clt Int32 vs NativeInt comparison unimplemented"
|
||||||
|
| EvalStackValue.Int32 i, other -> failwith $"invalid comparison, int32 %i{i} vs %O{other}"
|
||||||
|
| EvalStackValue.NativeInt var1, EvalStackValue.Int32 var2 ->
|
||||||
|
failwith "TODO: Clt NativeInt vs Int32 comparison unimplemented"
|
||||||
|
| other, EvalStackValue.Int32 var2 -> failwith $"invalid comparison, {other} vs int32 {var2}"
|
||||||
|
| EvalStackValue.NativeInt var1, EvalStackValue.NativeInt var2 -> NativeIntSource.isLess var1 var2
|
||||||
|
| EvalStackValue.NativeInt var1, other -> failwith $"invalid comparison, nativeint {var1} vs %O{other}"
|
||||||
|
| EvalStackValue.ManagedPointer managedPointerSource, NativeInt int64 ->
|
||||||
|
failwith "TODO: Clt ManagedPointer vs NativeInt comparison unimplemented"
|
||||||
|
| EvalStackValue.ManagedPointer managedPointerSource, ManagedPointer pointerSource ->
|
||||||
|
failwith "TODO: Clt ManagedPointer vs ManagedPointer comparison unimplemented"
|
||||||
|
| EvalStackValue.ManagedPointer managedPointerSource, UserDefinedValueType _ ->
|
||||||
|
failwith "TODO: Clt ManagedPointer vs UserDefinedValueType comparison unimplemented"
|
||||||
|
| EvalStackValue.UserDefinedValueType _, NativeInt int64 ->
|
||||||
|
failwith "TODO: Clt UserDefinedValueType vs NativeInt comparison unimplemented"
|
||||||
|
| EvalStackValue.UserDefinedValueType _, ManagedPointer managedPointerSource ->
|
||||||
|
failwith "TODO: Clt UserDefinedValueType vs ManagedPointer comparison unimplemented"
|
||||||
|
| EvalStackValue.UserDefinedValueType _, UserDefinedValueType _ ->
|
||||||
|
failwith "TODO: Clt UserDefinedValueType vs UserDefinedValueType comparison unimplemented"
|
||||||
|
|
||||||
|
let cgt (var1 : EvalStackValue) (var2 : EvalStackValue) : bool =
|
||||||
|
match var1, var2 with
|
||||||
|
| EvalStackValue.Int64 var1, EvalStackValue.Int64 var2 -> var1 > var2
|
||||||
|
| EvalStackValue.Float var1, EvalStackValue.Float var2 -> var1 > var2
|
||||||
|
| EvalStackValue.ObjectRef var1, EvalStackValue.ObjectRef var2 ->
|
||||||
|
failwith $"Cgt instruction invalid for comparing object refs, {var1} vs {var2}"
|
||||||
|
| EvalStackValue.ObjectRef var1, other -> failwith $"invalid comparison, ref %O{var1} vs %O{other}"
|
||||||
|
| other, EvalStackValue.ObjectRef var2 -> failwith $"invalid comparison, %O{other} vs ref %O{var2}"
|
||||||
|
| EvalStackValue.Float i, other -> failwith $"invalid comparison, float %f{i} vs %O{other}"
|
||||||
|
| other, EvalStackValue.Float i -> failwith $"invalid comparison, %O{other} vs float %f{i}"
|
||||||
|
| EvalStackValue.Int64 i, other -> failwith $"invalid comparison, int64 %i{i} vs %O{other}"
|
||||||
|
| other, EvalStackValue.Int64 i -> failwith $"invalid comparison, %O{other} vs int64 %i{i}"
|
||||||
|
| EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> var1 > var2
|
||||||
|
| EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 ->
|
||||||
|
failwith "TODO: Cgt Int32 vs NativeInt comparison unimplemented"
|
||||||
|
| EvalStackValue.Int32 i, other -> failwith $"invalid comparison, int32 %i{i} vs %O{other}"
|
||||||
|
| EvalStackValue.NativeInt var1, EvalStackValue.Int32 var2 ->
|
||||||
|
failwith "TODO: Cgt NativeInt vs Int32 comparison unimplemented"
|
||||||
|
| other, EvalStackValue.Int32 var2 -> failwith $"invalid comparison, {other} vs int32 {var2}"
|
||||||
|
| EvalStackValue.NativeInt var1, EvalStackValue.NativeInt var2 -> NativeIntSource.isLess var1 var2
|
||||||
|
| EvalStackValue.NativeInt var1, other -> failwith $"invalid comparison, nativeint {var1} vs %O{other}"
|
||||||
|
| EvalStackValue.ManagedPointer managedPointerSource, NativeInt int64 ->
|
||||||
|
failwith "TODO: Cgt ManagedPointer vs NativeInt comparison unimplemented"
|
||||||
|
| EvalStackValue.ManagedPointer managedPointerSource, ManagedPointer pointerSource ->
|
||||||
|
failwith "TODO: Cgt ManagedPointer vs ManagedPointer comparison unimplemented"
|
||||||
|
| EvalStackValue.ManagedPointer managedPointerSource, UserDefinedValueType _ ->
|
||||||
|
failwith "TODO: Cgt ManagedPointer vs UserDefinedValueType comparison unimplemented"
|
||||||
|
| EvalStackValue.UserDefinedValueType _, NativeInt int64 ->
|
||||||
|
failwith "TODO: Cgt UserDefinedValueType vs NativeInt comparison unimplemented"
|
||||||
|
| EvalStackValue.UserDefinedValueType _, ManagedPointer managedPointerSource ->
|
||||||
|
failwith "TODO: Cgt UserDefinedValueType vs ManagedPointer comparison unimplemented"
|
||||||
|
| EvalStackValue.UserDefinedValueType _, UserDefinedValueType _ ->
|
||||||
|
failwith "TODO: Cgt UserDefinedValueType vs UserDefinedValueType comparison unimplemented"
|
||||||
|
|
||||||
|
let cgtUn (var1 : EvalStackValue) (var2 : EvalStackValue) : bool =
|
||||||
|
match var1, var2 with
|
||||||
|
| EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> uint32 var1 > uint32 var2
|
||||||
|
| EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 ->
|
||||||
|
failwith "TODO: comparison of unsigned int32 with nativeint"
|
||||||
|
| EvalStackValue.Int32 _, _ -> failwith $"Cgt.un invalid for comparing %O{var1} with %O{var2}"
|
||||||
|
| EvalStackValue.Int64 var1, EvalStackValue.Int64 var2 -> uint64 var1 > uint64 var2
|
||||||
|
| EvalStackValue.Int64 _, _ -> failwith $"Cgt.un invalid for comparing %O{var1} with %O{var2}"
|
||||||
|
| EvalStackValue.NativeInt var1, EvalStackValue.NativeInt var2 ->
|
||||||
|
failwith "TODO: comparison of unsigned nativeints"
|
||||||
|
| EvalStackValue.NativeInt var1, EvalStackValue.Int32 var2 ->
|
||||||
|
failwith "TODO: comparison of unsigned nativeint with int32"
|
||||||
|
| EvalStackValue.Float var1, EvalStackValue.Float var2 -> not (var1 <= var2)
|
||||||
|
| EvalStackValue.Float _, _ -> failwith $"Cgt.un invalid for comparing %O{var1} with %O{var2}"
|
||||||
|
| EvalStackValue.ManagedPointer var1, EvalStackValue.ManagedPointer var2 ->
|
||||||
|
// I'm going to be stricter than the spec and simply ban every pointer comparison except those with null,
|
||||||
|
// pending a strong argument to fully support this.
|
||||||
|
match var1, var2 with
|
||||||
|
| ManagedPointerSource.Null, ManagedPointerSource.Null -> false
|
||||||
|
| ManagedPointerSource.Null, _ -> true
|
||||||
|
| _, ManagedPointerSource.Null -> true
|
||||||
|
| _, _ -> failwith $"I've banned this case: {var1} vs {var2}"
|
||||||
|
| EvalStackValue.ObjectRef var1, EvalStackValue.ObjectRef var2 ->
|
||||||
|
// According to the spec, cgt.un is verifiable on ObjectRefs and is used to compare with null.
|
||||||
|
// A direct comparison between two object refs is not specified, so we treat it as a pointer comparison.
|
||||||
|
failwith "TODO"
|
||||||
|
| other1, other2 -> failwith $"Cgt.un instruction invalid for comparing {other1} vs {other2}"
|
||||||
|
|
||||||
|
let cltUn (var1 : EvalStackValue) (var2 : EvalStackValue) : bool =
|
||||||
|
match var1, var2 with
|
||||||
|
| EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> uint32 var1 < uint32 var2
|
||||||
|
| EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 ->
|
||||||
|
failwith "TODO: comparison of unsigned int32 with nativeint"
|
||||||
|
| EvalStackValue.Int32 _, _ -> failwith $"Cgt.un invalid for comparing %O{var1} with %O{var2}"
|
||||||
|
| EvalStackValue.Int64 var1, EvalStackValue.Int64 var2 -> uint64 var1 < uint64 var2
|
||||||
|
| EvalStackValue.Int64 _, _ -> failwith $"Cgt.un invalid for comparing %O{var1} with %O{var2}"
|
||||||
|
| EvalStackValue.NativeInt var1, EvalStackValue.NativeInt var2 ->
|
||||||
|
failwith "TODO: comparison of unsigned nativeints"
|
||||||
|
| EvalStackValue.NativeInt var1, EvalStackValue.Int32 var2 ->
|
||||||
|
failwith "TODO: comparison of unsigned nativeint with int32"
|
||||||
|
| EvalStackValue.Float var1, EvalStackValue.Float var2 -> not (var1 >= var2)
|
||||||
|
| EvalStackValue.Float _, _ -> failwith $"Cgt.un invalid for comparing %O{var1} with %O{var2}"
|
||||||
|
| EvalStackValue.ManagedPointer var1, EvalStackValue.ManagedPointer var2 -> failwith "TODO"
|
||||||
|
| EvalStackValue.ObjectRef var1, EvalStackValue.ObjectRef var2 ->
|
||||||
|
// According to the spec, cgt.un is verifiable on ObjectRefs and is used to compare with null.
|
||||||
|
// A direct comparison between two object refs is not specified, so we treat it as a pointer comparison.
|
||||||
|
failwith "TODO"
|
||||||
|
| other1, other2 -> failwith $"Cgt.un instruction invalid for comparing {other1} vs {other2}"
|
||||||
|
|
||||||
|
let rec ceq (var1 : EvalStackValue) (var2 : EvalStackValue) : bool =
|
||||||
|
// Table III.4
|
||||||
|
match var1, var2 with
|
||||||
|
| EvalStackValue.UserDefinedValueType [ _, u ], v -> ceq u v
|
||||||
|
| u, EvalStackValue.UserDefinedValueType [ _, v ] -> ceq u v
|
||||||
|
| EvalStackValue.UserDefinedValueType [], EvalStackValue.UserDefinedValueType [] -> true
|
||||||
|
| EvalStackValue.UserDefinedValueType _, _
|
||||||
|
| _, EvalStackValue.UserDefinedValueType _ -> failwith $"bad ceq: {var1} vs {var2}"
|
||||||
|
| EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> var1 = var2
|
||||||
|
| EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 -> failwith "TODO: int32 CEQ nativeint"
|
||||||
|
| EvalStackValue.Int32 _, _ -> failwith $"bad ceq: Int32 vs {var2}"
|
||||||
|
| EvalStackValue.Int64 var1, EvalStackValue.Int64 var2 -> var1 = var2
|
||||||
|
| EvalStackValue.Int64 _, _ -> failwith $"bad ceq: Int64 vs {var2}"
|
||||||
|
| EvalStackValue.Float var1, EvalStackValue.Float var2 -> var1 = var2
|
||||||
|
| EvalStackValue.Float _, _ -> failwith $"bad ceq: Float vs {var2}"
|
||||||
|
| EvalStackValue.NativeInt var1, EvalStackValue.NativeInt var2 ->
|
||||||
|
match var1, var2 with
|
||||||
|
| NativeIntSource.FunctionPointer f1, NativeIntSource.FunctionPointer f2 ->
|
||||||
|
if f1 = f2 then
|
||||||
|
true
|
||||||
|
else
|
||||||
|
failwith $"TODO(CEQ): nativeint vs nativeint, {f1} vs {f2}"
|
||||||
|
| NativeIntSource.TypeHandlePtr f1, NativeIntSource.TypeHandlePtr f2 -> f1 = f2
|
||||||
|
| NativeIntSource.Verbatim f1, NativeIntSource.Verbatim f2 -> f1 = f2
|
||||||
|
| NativeIntSource.ManagedPointer f1, NativeIntSource.ManagedPointer f2 -> f1 = f2
|
||||||
|
| _, _ -> failwith $"TODO (CEQ): nativeint vs nativeint, {var1} vs {var2}"
|
||||||
|
| EvalStackValue.NativeInt var1, EvalStackValue.Int32 var2 -> failwith $"TODO (CEQ): nativeint vs int32"
|
||||||
|
| EvalStackValue.NativeInt var1, EvalStackValue.ManagedPointer var2 ->
|
||||||
|
failwith $"TODO (CEQ): nativeint vs managed pointer"
|
||||||
|
| EvalStackValue.NativeInt _, _ -> failwith $"bad ceq: NativeInt vs {var2}"
|
||||||
|
| EvalStackValue.ObjectRef var1, EvalStackValue.ObjectRef var2 -> var1 = var2
|
||||||
|
| EvalStackValue.ObjectRef _, _ -> failwith $"bad ceq: ObjectRef vs {var2}"
|
||||||
|
| EvalStackValue.ManagedPointer var1, EvalStackValue.ManagedPointer var2 -> var1 = var2
|
||||||
|
| EvalStackValue.ManagedPointer var1, EvalStackValue.NativeInt var2 ->
|
||||||
|
failwith $"TODO (CEQ): managed pointer vs nativeint"
|
||||||
|
| EvalStackValue.ManagedPointer _, _ -> failwith $"bad ceq: ManagedPointer vs {var2}"
|
@@ -1,29 +1,32 @@
|
|||||||
namespace WoofWare.PawPrint
|
namespace WoofWare.PawPrint
|
||||||
|
|
||||||
|
open System
|
||||||
open System.Collections.Immutable
|
open System.Collections.Immutable
|
||||||
|
|
||||||
/// Represents a location in the code where an exception occurred
|
/// Represents a location in the code where an exception occurred
|
||||||
type ExceptionStackFrame =
|
type ExceptionStackFrame<'typeGen, 'methodGen, 'methodVar
|
||||||
|
when 'typeGen : comparison and 'typeGen :> IComparable<'typeGen>> =
|
||||||
{
|
{
|
||||||
Method : WoofWare.PawPrint.MethodInfo<TypeDefn, TypeDefn>
|
Method : WoofWare.PawPrint.MethodInfo<'typeGen, 'methodGen, 'methodVar>
|
||||||
/// The number of bytes into the IL of the method we were in
|
/// The number of bytes into the IL of the method we were in
|
||||||
IlOffset : int
|
IlOffset : int
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a CLI exception being propagated
|
/// Represents a CLI exception being propagated
|
||||||
type CliException =
|
type CliException<'typeGen, 'methodGen, 'methodVar when 'typeGen : comparison and 'typeGen :> IComparable<'typeGen>> =
|
||||||
{
|
{
|
||||||
/// The exception object allocated on the heap
|
/// The exception object allocated on the heap
|
||||||
ExceptionObject : ManagedHeapAddress
|
ExceptionObject : ManagedHeapAddress
|
||||||
/// Stack trace built during unwinding
|
/// Stack trace built during unwinding
|
||||||
StackTrace : ExceptionStackFrame list
|
StackTrace : ExceptionStackFrame<'typeGen, 'methodGen, 'methodVar> list
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents what to do after executing a finally/filter block
|
/// Represents what to do after executing a finally/filter block
|
||||||
type ExceptionContinuation =
|
type ExceptionContinuation<'typeGen, 'methodGen, 'methodVar
|
||||||
|
when 'typeGen : comparison and 'typeGen :> IComparable<'typeGen>> =
|
||||||
| ResumeAfterFinally of targetPC : int
|
| ResumeAfterFinally of targetPC : int
|
||||||
| PropagatingException of exn : CliException
|
| PropagatingException of exn : CliException<'typeGen, 'methodGen, 'methodVar>
|
||||||
| ResumeAfterFilter of handlerPC : int * exn : CliException
|
| ResumeAfterFilter of handlerPC : int * exn : CliException<'typeGen, 'methodGen, 'methodVar>
|
||||||
|
|
||||||
/// Helper functions for exception handling
|
/// Helper functions for exception handling
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -31,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
|
||||||
@@ -43,8 +46,8 @@ 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<TypeDefn, 'methodGeneric>)
|
(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
|
||||||
=
|
=
|
||||||
@@ -59,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
|
||||||
@@ -92,7 +95,7 @@ module ExceptionHandling =
|
|||||||
let findFinallyBlocksToRun
|
let findFinallyBlocksToRun
|
||||||
(currentPC : int)
|
(currentPC : int)
|
||||||
(targetPC : int)
|
(targetPC : int)
|
||||||
(method : WoofWare.PawPrint.MethodInfo<TypeDefn, 'methodGeneric>)
|
(method : WoofWare.PawPrint.MethodInfo<'typeGeneric, 'methodGeneric, 'methodVar>)
|
||||||
: ExceptionOffset list
|
: ExceptionOffset list
|
||||||
=
|
=
|
||||||
match method.Instructions with
|
match method.Instructions with
|
||||||
@@ -122,7 +125,7 @@ module ExceptionHandling =
|
|||||||
/// Get the active exception regions at a given offset
|
/// Get the active exception regions at a given offset
|
||||||
let getActiveRegionsAtOffset
|
let getActiveRegionsAtOffset
|
||||||
(offset : int)
|
(offset : int)
|
||||||
(method : WoofWare.PawPrint.MethodInfo<TypeDefn, 'methodGeneric>)
|
(method : WoofWare.PawPrint.MethodInfo<'a, 'b, 'c>)
|
||||||
: WoofWare.PawPrint.ExceptionRegion list
|
: WoofWare.PawPrint.ExceptionRegion list
|
||||||
=
|
=
|
||||||
match method.Instructions with
|
match method.Instructions with
|
||||||
|
@@ -82,10 +82,11 @@ 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"
|
||||||
|
|
||||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||||
|
|
||||||
|
111
WoofWare.PawPrint/FieldHandleRegistry.fs
Normal file
111
WoofWare.PawPrint/FieldHandleRegistry.fs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
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 : (string * CliType) 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"
|
||||||
|
|
||||||
|
{
|
||||||
|
Fields = [ "m_ptr", CliType.ofManagedObject runtimeFieldInfoStub ]
|
||||||
|
}
|
||||||
|
|> 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}"
|
||||||
|
|
||||||
|
{
|
||||||
|
Fields = [ "m_handle", CliType.RuntimePointer (CliRuntimePointer.Unmanaged newHandle) ]
|
||||||
|
}
|
||||||
|
|> CliType.ValueType
|
||||||
|
|
||||||
|
let runtimeFieldInfoStub =
|
||||||
|
[
|
||||||
|
// If we ever implement a GC, something should change here
|
||||||
|
"m_keepalive", CliType.ObjectRef None
|
||||||
|
"m_c", CliType.ObjectRef None
|
||||||
|
"m_d", CliType.ObjectRef None
|
||||||
|
"m_b", CliType.Numeric (CliNumericType.Int32 0)
|
||||||
|
"m_e", CliType.ObjectRef None
|
||||||
|
// RuntimeFieldHandleInternal: https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1048
|
||||||
|
"m_fieldHandle", runtimeFieldHandleInternal
|
||||||
|
]
|
||||||
|
|
||||||
|
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
849
WoofWare.PawPrint/IlMachineStateExecution.fs
Normal file
849
WoofWare.PawPrint/IlMachineStateExecution.fs
Normal file
@@ -0,0 +1,849 @@
|
|||||||
|
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
|
||||||
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||||
|
(state : IlMachineState)
|
||||||
|
(esv : EvalStackValue)
|
||||||
|
: IlMachineState * ConcreteTypeHandle
|
||||||
|
=
|
||||||
|
match esv with
|
||||||
|
| EvalStackValue.Int32 _ ->
|
||||||
|
TypeDefn.FromDefinition (
|
||||||
|
ComparableTypeDefinitionHandle.Make baseClassTypes.Int32.TypeDefHandle,
|
||||||
|
baseClassTypes.Corelib.Name.FullName,
|
||||||
|
SignatureTypeKind.ValueType
|
||||||
|
)
|
||||||
|
|> IlMachineState.concretizeType
|
||||||
|
baseClassTypes
|
||||||
|
state
|
||||||
|
baseClassTypes.Corelib.Name
|
||||||
|
ImmutableArray.Empty
|
||||||
|
ImmutableArray.Empty
|
||||||
|
| EvalStackValue.Int64 _ ->
|
||||||
|
TypeDefn.FromDefinition (
|
||||||
|
ComparableTypeDefinitionHandle.Make baseClassTypes.Int64.TypeDefHandle,
|
||||||
|
baseClassTypes.Corelib.Name.FullName,
|
||||||
|
SignatureTypeKind.ValueType
|
||||||
|
)
|
||||||
|
|> IlMachineState.concretizeType
|
||||||
|
baseClassTypes
|
||||||
|
state
|
||||||
|
baseClassTypes.Corelib.Name
|
||||||
|
ImmutableArray.Empty
|
||||||
|
ImmutableArray.Empty
|
||||||
|
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
|
||||||
|
| EvalStackValue.Float _ ->
|
||||||
|
TypeDefn.FromDefinition (
|
||||||
|
ComparableTypeDefinitionHandle.Make baseClassTypes.Double.TypeDefHandle,
|
||||||
|
baseClassTypes.Corelib.Name.FullName,
|
||||||
|
SignatureTypeKind.ValueType
|
||||||
|
)
|
||||||
|
|> IlMachineState.concretizeType
|
||||||
|
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"
|
||||||
|
| 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 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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
// TypeDef won't have any generics; it would be a TypeSpec if it did
|
||||||
|
// Create a TypeDefn from the TypeDef handle
|
||||||
|
let baseTypeDefn =
|
||||||
|
let baseTypeDef = sourceAssembly.TypeDefs.[typeDefinitionHandle]
|
||||||
|
|
||||||
|
let baseType =
|
||||||
|
baseTypeDef.BaseType
|
||||||
|
|> DumpedAssembly.resolveBaseType
|
||||||
|
baseClassTypes
|
||||||
|
state._LoadedAssemblies
|
||||||
|
sourceAssembly.Name
|
||||||
|
|
||||||
|
let signatureTypeKind =
|
||||||
|
match baseType with
|
||||||
|
| ResolvedBaseType.Enum
|
||||||
|
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
||||||
|
| ResolvedBaseType.Object
|
||||||
|
| ResolvedBaseType.Delegate -> SignatureTypeKind.Class
|
||||||
|
|
||||||
|
TypeDefn.FromDefinition (
|
||||||
|
ComparableTypeDefinitionHandle.Make typeDefinitionHandle,
|
||||||
|
sourceAssembly.Name.FullName,
|
||||||
|
signatureTypeKind
|
||||||
|
)
|
||||||
|
|
||||||
|
// Concretize the base type
|
||||||
|
let state, baseTypeHandle =
|
||||||
|
IlMachineState.concretizeType
|
||||||
|
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 =
|
||||||
|
let baseType =
|
||||||
|
targetType.BaseType
|
||||||
|
|> DumpedAssembly.resolveBaseType baseClassTypes state._LoadedAssemblies assy.Name
|
||||||
|
|
||||||
|
let signatureTypeKind =
|
||||||
|
match baseType with
|
||||||
|
| ResolvedBaseType.Enum
|
||||||
|
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
||||||
|
| ResolvedBaseType.Object
|
||||||
|
| ResolvedBaseType.Delegate -> SignatureTypeKind.Class
|
||||||
|
|
||||||
|
TypeDefn.FromDefinition (
|
||||||
|
ComparableTypeDefinitionHandle.Make targetType.TypeDefHandle,
|
||||||
|
assy.Name.FullName,
|
||||||
|
signatureTypeKind
|
||||||
|
)
|
||||||
|
|
||||||
|
// Concretize the base type
|
||||||
|
let state, baseTypeHandle =
|
||||||
|
IlMachineState.concretizeType
|
||||||
|
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
|
||||||
|
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
|
||||||
|
baseClassTypes
|
||||||
|
state
|
||||||
|
concreteType.Assembly
|
||||||
|
concreteType.Generics
|
||||||
|
ImmutableArray.Empty // no method generics for cctor
|
||||||
|
typeDefn
|
||||||
|
|
||||||
|
state, handle :: acc
|
||||||
|
)
|
||||||
|
|> Tuple.rmap ImmutableArray.CreateRange
|
||||||
|
|
||||||
|
state, Some convertedVars
|
||||||
|
|
||||||
|
state, Some (MethodInstructions.setLocalVars convertedLocalVars methodInstr)
|
||||||
|
|
||||||
|
let fullyConvertedMethod =
|
||||||
|
MethodInfo.setMethodVars convertedInstructions convertedSignature cctorMethodWithMethodGenerics
|
||||||
|
|
||||||
|
callMethod
|
||||||
|
loggerFactory
|
||||||
|
baseClassTypes
|
||||||
|
(Some ty)
|
||||||
|
None
|
||||||
|
true
|
||||||
|
true
|
||||||
|
false
|
||||||
|
// constructor is surely not generic
|
||||||
|
ImmutableArray.Empty
|
||||||
|
fullyConvertedMethod
|
||||||
|
currentThread
|
||||||
|
currentThreadState
|
||||||
|
state
|
||||||
|
|> FirstLoadThis
|
||||||
|
| None ->
|
||||||
|
// No constructor, just continue.
|
||||||
|
// Mark the type as initialized.
|
||||||
|
let state = state.WithTypeEndInit currentThread ty
|
||||||
|
|
||||||
|
// Restore original assembly context if needed
|
||||||
|
state.WithThreadSwitchedToAssembly origAssyName currentThread
|
||||||
|
|> fst
|
||||||
|
|> NothingToDo
|
||||||
|
|
||||||
|
let ensureTypeInitialised
|
||||||
|
(loggerFactory : ILoggerFactory)
|
||||||
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||||
|
(thread : ThreadId)
|
||||||
|
(ty : ConcreteTypeHandle)
|
||||||
|
(state : IlMachineState)
|
||||||
|
: IlMachineState * WhatWeDid
|
||||||
|
=
|
||||||
|
match TypeInitTable.tryGet ty state.TypeInitTable with
|
||||||
|
| None ->
|
||||||
|
match loadClass loggerFactory baseClassTypes ty thread state with
|
||||||
|
| NothingToDo state -> state, WhatWeDid.Executed
|
||||||
|
| FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit
|
||||||
|
| Some TypeInitState.Initialized -> state, WhatWeDid.Executed
|
||||||
|
| Some (InProgress threadId) ->
|
||||||
|
if threadId = thread then
|
||||||
|
// II.10.5.3.2: avoid the deadlock by simply proceeding.
|
||||||
|
state, WhatWeDid.Executed
|
||||||
|
else
|
||||||
|
state, WhatWeDid.BlockedOnClassInit threadId
|
||||||
|
|
||||||
|
/// It may be useful to *not* advance the program counter of the caller, e.g. if you're using `callMethodInActiveAssembly`
|
||||||
|
/// as a convenient way to move to a different method body rather than to genuinely perform a call.
|
||||||
|
/// (Delegates do this, for example: we get a call to invoke the delegate, and then we implement the delegate as
|
||||||
|
/// another call to its function pointer.)
|
||||||
|
let callMethodInActiveAssembly
|
||||||
|
(loggerFactory : ILoggerFactory)
|
||||||
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||||
|
(thread : ThreadId)
|
||||||
|
(performInterfaceResolution : bool)
|
||||||
|
(advanceProgramCounterOfCaller : bool)
|
||||||
|
(methodGenerics : TypeDefn ImmutableArray option)
|
||||||
|
(methodToCall : WoofWare.PawPrint.MethodInfo<TypeDefn, GenericParamFromMetadata, TypeDefn>)
|
||||||
|
(weAreConstructingObj : ManagedHeapAddress option)
|
||||||
|
(typeArgsFromMetadata : TypeDefn ImmutableArray option)
|
||||||
|
(state : IlMachineState)
|
||||||
|
: IlMachineState * WhatWeDid
|
||||||
|
=
|
||||||
|
let threadState = state.ThreadState.[thread]
|
||||||
|
|
||||||
|
let state, concretizedMethod, declaringTypeHandle =
|
||||||
|
IlMachineState.concretizeMethodForExecution
|
||||||
|
loggerFactory
|
||||||
|
baseClassTypes
|
||||||
|
thread
|
||||||
|
methodToCall
|
||||||
|
methodGenerics
|
||||||
|
typeArgsFromMetadata
|
||||||
|
state
|
||||||
|
|
||||||
|
let state, typeInit =
|
||||||
|
ensureTypeInitialised loggerFactory baseClassTypes thread declaringTypeHandle state
|
||||||
|
|
||||||
|
match typeInit with
|
||||||
|
| WhatWeDid.Executed ->
|
||||||
|
callMethod
|
||||||
|
loggerFactory
|
||||||
|
baseClassTypes
|
||||||
|
None
|
||||||
|
weAreConstructingObj
|
||||||
|
performInterfaceResolution
|
||||||
|
false
|
||||||
|
advanceProgramCounterOfCaller
|
||||||
|
concretizedMethod.Generics
|
||||||
|
concretizedMethod
|
||||||
|
thread
|
||||||
|
threadState
|
||||||
|
state,
|
||||||
|
WhatWeDid.Executed
|
||||||
|
| _ -> state, typeInit
|
14
WoofWare.PawPrint/ImmutableArray.fs
Normal file
14
WoofWare.PawPrint/ImmutableArray.fs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace WoofWare.PawPrint
|
||||||
|
|
||||||
|
open System.Collections.Immutable
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module internal ImmutableArray =
|
||||||
|
|
||||||
|
let map (f : 'a -> 'b) (arr : ImmutableArray<'a>) : ImmutableArray<'b> =
|
||||||
|
let b = ImmutableArray.CreateBuilder ()
|
||||||
|
|
||||||
|
for i in arr do
|
||||||
|
b.Add (f i)
|
||||||
|
|
||||||
|
b.ToImmutable ()
|
307
WoofWare.PawPrint/Intrinsics.fs
Normal file
307
WoofWare.PawPrint/Intrinsics.fs
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
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"
|
||||||
|
]
|
||||||
|
|> 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
|
||||||
|
|
||||||
|
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
|
||||||
|
// no args, returns RuntimeTypeHandle, a struct with a single field (a RuntimeType class)
|
||||||
|
|
||||||
|
// 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 [ _, s ] -> go s
|
||||||
|
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE"
|
||||||
|
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> Some addr
|
||||||
|
| s -> failwith $"TODO: called with unrecognised arg %O{s}"
|
||||||
|
|
||||||
|
go arg
|
||||||
|
|
||||||
|
let state =
|
||||||
|
let vt =
|
||||||
|
{
|
||||||
|
Fields = [ "m_type", CliType.ObjectRef arg ]
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
| 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 arg1.Fields.["_firstChar"] <> arg2.Fields.["_firstChar"] 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 ->
|
||||||
|
match src with
|
||||||
|
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> failwith "todo"
|
||||||
|
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> failwith "todo"
|
||||||
|
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
|
||||||
|
| ManagedPointerSource.ArrayIndex (arr, index) ->
|
||||||
|
state |> IlMachineState.getArrayValue arr index
|
||||||
|
| ManagedPointerSource.Null -> failwith "TODO: throw NRE"
|
||||||
|
| EvalStackValue.NativeInt src -> failwith "TODO"
|
||||||
|
| EvalStackValue.ObjectRef ptr -> failwith "TODO"
|
||||||
|
| EvalStackValue.UserDefinedValueType [ _, field ] -> go field
|
||||||
|
| EvalStackValue.UserDefinedValueType []
|
||||||
|
| EvalStackValue.UserDefinedValueType (_ :: _ :: _)
|
||||||
|
| 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 generic =
|
||||||
|
AllConcreteTypes.lookup (Seq.exactlyOne methodToCall.Generics) state.ConcreteTypes
|
||||||
|
|
||||||
|
let generic =
|
||||||
|
match generic with
|
||||||
|
| None -> failwith "somehow have not already concretised type in IsReferenceOrContainsReferences"
|
||||||
|
| Some generic -> generic
|
||||||
|
|
||||||
|
failwith $"TODO: do the thing on %O{generic}"
|
||||||
|
| "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", "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)
|
@@ -9,7 +9,7 @@ type SyncBlock =
|
|||||||
type AllocatedNonArrayObject =
|
type AllocatedNonArrayObject =
|
||||||
{
|
{
|
||||||
Fields : Map<string, CliType>
|
Fields : Map<string, CliType>
|
||||||
Type : WoofWare.PawPrint.TypeInfoCrate
|
ConcreteType : ConcreteTypeHandle
|
||||||
SyncBlock : SyncBlock
|
SyncBlock : SyncBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +109,20 @@ type ManagedHeap =
|
|||||||
|
|
||||||
ManagedHeapAddress addr, heap
|
ManagedHeapAddress addr, heap
|
||||||
|
|
||||||
|
static member GetArrayValue (alloc : ManagedHeapAddress) (offset : int) (heap : ManagedHeap) : CliType =
|
||||||
|
match heap.Arrays.TryGetValue alloc with
|
||||||
|
| false, _ -> failwith "TODO: array not on heap"
|
||||||
|
| true, arr ->
|
||||||
|
|
||||||
|
if offset < 0 || offset >= arr.Length then
|
||||||
|
failwith "TODO: raise IndexOutOfBoundsException"
|
||||||
|
|
||||||
|
arr.Elements.[offset]
|
||||||
|
|
||||||
|
static member Get (alloc : ManagedHeapAddress) (heap : ManagedHeap) : AllocatedNonArrayObject =
|
||||||
|
// TODO: arrays too
|
||||||
|
heap.NonArrayObjects.[alloc]
|
||||||
|
|
||||||
static member SetArrayValue
|
static member SetArrayValue
|
||||||
(alloc : ManagedHeapAddress)
|
(alloc : ManagedHeapAddress)
|
||||||
(offset : int)
|
(offset : int)
|
||||||
@@ -124,6 +138,9 @@ type ManagedHeap =
|
|||||||
match arr with
|
match arr with
|
||||||
| None -> failwith "tried to change element of nonexistent array"
|
| None -> failwith "tried to change element of nonexistent array"
|
||||||
| Some arr ->
|
| Some arr ->
|
||||||
|
if offset < 0 || offset >= arr.Elements.Length then
|
||||||
|
failwith "TODO: throw somehow"
|
||||||
|
|
||||||
{ arr with
|
{ arr with
|
||||||
Elements = arr.Elements.SetItem (offset, v)
|
Elements = arr.Elements.SetItem (offset, v)
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ type MethodReturnState =
|
|||||||
{
|
{
|
||||||
/// Index in the MethodStates array of a ThreadState
|
/// Index in the MethodStates array of a ThreadState
|
||||||
JumpTo : int
|
JumpTo : int
|
||||||
WasInitialisingType : RuntimeConcreteType option
|
WasInitialisingType : ConcreteTypeHandle option
|
||||||
/// The Newobj instruction means we need to push a reference immediately after Ret.
|
/// The Newobj instruction means we need to push a reference immediately after Ret.
|
||||||
WasConstructingObj : ManagedHeapAddress option
|
WasConstructingObj : ManagedHeapAddress option
|
||||||
}
|
}
|
||||||
@@ -19,16 +19,16 @@ and MethodState =
|
|||||||
_IlOpIndex : int
|
_IlOpIndex : int
|
||||||
EvaluationStack : EvalStack
|
EvaluationStack : EvalStack
|
||||||
Arguments : CliType ImmutableArray
|
Arguments : CliType ImmutableArray
|
||||||
ExecutingMethod : WoofWare.PawPrint.MethodInfo<TypeDefn, TypeDefn>
|
ExecutingMethod : WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>
|
||||||
/// We don't implement the local memory pool right now
|
/// We don't implement the local memory pool right now
|
||||||
LocalMemoryPool : unit
|
LocalMemoryPool : unit
|
||||||
/// On return, we restore this state. This should be Some almost always; an exception is the entry point.
|
/// On return, we restore this state. This should be Some almost always; an exception is the entry point.
|
||||||
ReturnState : MethodReturnState option
|
ReturnState : MethodReturnState option
|
||||||
Generics : ImmutableArray<TypeDefn> option
|
Generics : ImmutableArray<ConcreteTypeHandle>
|
||||||
/// Track which exception regions are currently active (innermost first)
|
/// Track which exception regions are currently active (innermost first)
|
||||||
ActiveExceptionRegions : ExceptionRegion list
|
ActiveExceptionRegions : ExceptionRegion list
|
||||||
/// When executing a finally/fault/filter, we need to know where to return
|
/// When executing a finally/fault/filter, we need to know where to return
|
||||||
ExceptionContinuation : ExceptionContinuation option
|
ExceptionContinuation : ExceptionContinuation<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle> option
|
||||||
}
|
}
|
||||||
|
|
||||||
member this.IlOpIndex = this._IlOpIndex
|
member this.IlOpIndex = this._IlOpIndex
|
||||||
@@ -64,7 +64,7 @@ and MethodState =
|
|||||||
EvaluationStack = EvalStack.Empty
|
EvaluationStack = EvalStack.Empty
|
||||||
}
|
}
|
||||||
|
|
||||||
static member setExceptionContinuation (cont : ExceptionContinuation) (state : MethodState) : MethodState =
|
static member setExceptionContinuation (cont : ExceptionContinuation<_, _, _>) (state : MethodState) : MethodState =
|
||||||
{ state with
|
{ state with
|
||||||
ExceptionContinuation = Some cont
|
ExceptionContinuation = Some cont
|
||||||
}
|
}
|
||||||
@@ -136,11 +136,12 @@ and MethodState =
|
|||||||
/// If `method` is an instance method, `args` must be of length 1+numParams.
|
/// If `method` is an instance method, `args` must be of length 1+numParams.
|
||||||
/// 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
|
||||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
(concreteTypes : AllConcreteTypes)
|
||||||
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||||
(loadedAssemblies : ImmutableDictionary<string, DumpedAssembly>)
|
(loadedAssemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||||
(containingAssembly : DumpedAssembly)
|
(containingAssembly : DumpedAssembly)
|
||||||
(method : WoofWare.PawPrint.MethodInfo<TypeDefn, TypeDefn>)
|
(method : WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>)
|
||||||
(methodGenerics : ImmutableArray<TypeDefn> option)
|
(methodGenerics : ImmutableArray<ConcreteTypeHandle>)
|
||||||
(args : ImmutableArray<CliType>)
|
(args : ImmutableArray<CliType>)
|
||||||
(returnState : MethodReturnState option)
|
(returnState : MethodReturnState option)
|
||||||
: Result<MethodState, WoofWare.PawPrint.AssemblyReference list>
|
: Result<MethodState, WoofWare.PawPrint.AssemblyReference list>
|
||||||
@@ -164,28 +165,18 @@ and MethodState =
|
|||||||
// I think valid code should remain valid if we unconditionally localsInit - it should be undefined
|
// I think valid code should remain valid if we unconditionally localsInit - it should be undefined
|
||||||
// to use an uninitialised value? Not checked this; TODO.
|
// to use an uninitialised value? Not checked this; TODO.
|
||||||
|
|
||||||
let requiredAssemblies = ResizeArray<WoofWare.PawPrint.AssemblyReference> ()
|
|
||||||
|
|
||||||
let typeGenerics =
|
|
||||||
match method.DeclaringType.Generics with
|
|
||||||
| [] -> None
|
|
||||||
| x -> ImmutableArray.CreateRange x |> Some
|
|
||||||
|
|
||||||
let localVars =
|
let localVars =
|
||||||
let result = ImmutableArray.CreateBuilder ()
|
let result = ImmutableArray.CreateBuilder ()
|
||||||
|
|
||||||
for var in localVariableSig do
|
for var in localVariableSig do
|
||||||
match CliType.zeroOf loadedAssemblies corelib containingAssembly typeGenerics methodGenerics var with
|
// Note: This assumes all types have already been concretized
|
||||||
| CliTypeResolutionResult.Resolved t -> result.Add t
|
// If this fails with "ConcreteTypeHandle not found", it means
|
||||||
| CliTypeResolutionResult.FirstLoad (assy : WoofWare.PawPrint.AssemblyReference) ->
|
// we need to ensure types are concretized before creating the MethodState
|
||||||
requiredAssemblies.Add assy
|
let zero, _ = CliType.zeroOf concreteTypes loadedAssemblies baseClassTypes var
|
||||||
|
result.Add zero
|
||||||
|
|
||||||
result.ToImmutable ()
|
result.ToImmutable ()
|
||||||
|
|
||||||
if requiredAssemblies.Count > 0 then
|
|
||||||
Error (requiredAssemblies |> Seq.toList)
|
|
||||||
else
|
|
||||||
|
|
||||||
let activeRegions = ExceptionHandling.getActiveRegionsAtOffset 0 method
|
let activeRegions = ExceptionHandling.getActiveRegionsAtOffset 0 method
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@@ -1,33 +1,7 @@
|
|||||||
namespace WoofWare.PawPrint
|
namespace WoofWare.PawPrint
|
||||||
|
|
||||||
#nowarn "42"
|
|
||||||
|
|
||||||
open Microsoft.Extensions.Logging
|
open Microsoft.Extensions.Logging
|
||||||
|
|
||||||
type private IArithmeticOperation =
|
|
||||||
abstract Int32Int32 : int32 -> int32 -> int32
|
|
||||||
abstract Int64Int64 : int64 -> int64 -> int64
|
|
||||||
abstract FloatFloat : float -> float -> float
|
|
||||||
abstract Name : string
|
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
|
||||||
module private ArithmeticOperation =
|
|
||||||
let add =
|
|
||||||
{ new IArithmeticOperation with
|
|
||||||
member _.Int32Int32 a b = (# "add" a b : int32 #)
|
|
||||||
member _.Int64Int64 a b = (# "add" a b : int64 #)
|
|
||||||
member _.FloatFloat a b = (# "add" a b : float #)
|
|
||||||
member _.Name = "add"
|
|
||||||
}
|
|
||||||
|
|
||||||
let mul =
|
|
||||||
{ new IArithmeticOperation with
|
|
||||||
member _.Int32Int32 a b = (# "mul" a b : int32 #)
|
|
||||||
member _.Int64Int64 a b = (# "mul" a b : int64 #)
|
|
||||||
member _.FloatFloat a b = (# "mul" a b : float #)
|
|
||||||
member _.Name = "mul"
|
|
||||||
}
|
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||||
module NullaryIlOp =
|
module NullaryIlOp =
|
||||||
@@ -72,6 +46,9 @@ module NullaryIlOp =
|
|||||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
||||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables.[int<uint16> whichVar]
|
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables.[int<uint16> whichVar]
|
||||||
| ManagedPointerSource.Heap managedHeapAddress -> failwith "TODO: Heap pointer dereferencing not implemented"
|
| ManagedPointerSource.Heap managedHeapAddress -> failwith "TODO: Heap pointer dereferencing not implemented"
|
||||||
|
| ManagedPointerSource.ArrayIndex (arr, index) ->
|
||||||
|
let arr = state.ManagedHeap.Arrays.[arr]
|
||||||
|
arr.Elements.[index]
|
||||||
|
|
||||||
// Unified Ldind implementation
|
// Unified Ldind implementation
|
||||||
let private executeLdind
|
let private executeLdind
|
||||||
@@ -103,41 +80,6 @@ module NullaryIlOp =
|
|||||||
|
|
||||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||||
|
|
||||||
let private binaryArithmeticOperation
|
|
||||||
(op : IArithmeticOperation)
|
|
||||||
(currentThread : ThreadId)
|
|
||||||
(state : IlMachineState)
|
|
||||||
=
|
|
||||||
let val1, state = IlMachineState.popEvalStack currentThread state
|
|
||||||
let val2, state = IlMachineState.popEvalStack currentThread state
|
|
||||||
// see table at https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.add?view=net-9.0
|
|
||||||
let result =
|
|
||||||
match val1, val2 with
|
|
||||||
| EvalStackValue.Int32 val1, EvalStackValue.Int32 val2 ->
|
|
||||||
(# "add" val1 val2 : int32 #) |> EvalStackValue.Int32
|
|
||||||
| EvalStackValue.Int32 val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.NativeInt
|
|
||||||
| EvalStackValue.Int32 val1, EvalStackValue.ManagedPointer val2 ->
|
|
||||||
failwith "" |> EvalStackValue.ManagedPointer
|
|
||||||
| EvalStackValue.Int32 val1, EvalStackValue.ObjectRef val2 -> failwith "" |> EvalStackValue.ObjectRef
|
|
||||||
| EvalStackValue.Int64 val1, EvalStackValue.Int64 val2 ->
|
|
||||||
(# "add" val1 val2 : int64 #) |> EvalStackValue.Int64
|
|
||||||
| EvalStackValue.NativeInt val1, EvalStackValue.Int32 val2 -> failwith "" |> EvalStackValue.NativeInt
|
|
||||||
| EvalStackValue.NativeInt val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.NativeInt
|
|
||||||
| EvalStackValue.NativeInt val1, EvalStackValue.ManagedPointer val2 ->
|
|
||||||
failwith "" |> EvalStackValue.ManagedPointer
|
|
||||||
| EvalStackValue.NativeInt val1, EvalStackValue.ObjectRef val2 -> failwith "" |> EvalStackValue.ObjectRef
|
|
||||||
| EvalStackValue.Float val1, EvalStackValue.Float val2 ->
|
|
||||||
(# "add" val1 val2 : float #) |> EvalStackValue.Float
|
|
||||||
| EvalStackValue.ManagedPointer val1, EvalStackValue.NativeInt val2 ->
|
|
||||||
failwith "" |> EvalStackValue.ManagedPointer
|
|
||||||
| EvalStackValue.ObjectRef val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.ObjectRef
|
|
||||||
| EvalStackValue.ManagedPointer val1, EvalStackValue.Int32 val2 ->
|
|
||||||
failwith "" |> EvalStackValue.ManagedPointer
|
|
||||||
| EvalStackValue.ObjectRef val1, EvalStackValue.Int32 val2 -> failwith "" |> EvalStackValue.ObjectRef
|
|
||||||
| val1, val2 -> failwith $"invalid %s{op.Name} operation: {val1} and {val2}"
|
|
||||||
|
|
||||||
result, state
|
|
||||||
|
|
||||||
let private stind (varType : CliType) (currentThread : ThreadId) (state : IlMachineState) : IlMachineState =
|
let private stind (varType : CliType) (currentThread : ThreadId) (state : IlMachineState) : IlMachineState =
|
||||||
// TODO: throw NullReferenceException if unaligned target
|
// TODO: throw NullReferenceException if unaligned target
|
||||||
let valueToStore, state = IlMachineState.popEvalStack currentThread state
|
let valueToStore, state = IlMachineState.popEvalStack currentThread state
|
||||||
@@ -182,8 +124,84 @@ module NullaryIlOp =
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
|
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
|
||||||
|
| ManagedPointerSource.ArrayIndex _ -> failwith "todo"
|
||||||
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
|
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
|
||||||
|
|
||||||
|
let internal ldElem
|
||||||
|
(targetCliTypeZero : CliType)
|
||||||
|
(index : EvalStackValue)
|
||||||
|
(arr : EvalStackValue)
|
||||||
|
(currentThread : ThreadId)
|
||||||
|
(state : IlMachineState)
|
||||||
|
: ExecutionResult
|
||||||
|
=
|
||||||
|
let index =
|
||||||
|
match index with
|
||||||
|
| EvalStackValue.NativeInt src ->
|
||||||
|
match src with
|
||||||
|
| NativeIntSource.FunctionPointer _
|
||||||
|
| NativeIntSource.TypeHandlePtr _
|
||||||
|
| NativeIntSource.ManagedPointer _ -> failwith "Refusing to treat a pointer as an array index"
|
||||||
|
| NativeIntSource.Verbatim i -> i |> int32
|
||||||
|
| EvalStackValue.Int32 i -> i
|
||||||
|
| _ -> failwith $"Invalid index: {index}"
|
||||||
|
|
||||||
|
let arrAddr =
|
||||||
|
match arr with
|
||||||
|
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr)
|
||||||
|
| EvalStackValue.ObjectRef addr -> addr
|
||||||
|
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE"
|
||||||
|
| _ -> failwith $"Invalid array: %O{arr}"
|
||||||
|
|
||||||
|
let value = IlMachineState.getArrayValue arrAddr index state
|
||||||
|
|
||||||
|
let state =
|
||||||
|
state
|
||||||
|
|> IlMachineState.pushToEvalStack value currentThread
|
||||||
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
|
|
||||||
|
ExecutionResult.Stepped (state, WhatWeDid.Executed)
|
||||||
|
|
||||||
|
let internal stElem
|
||||||
|
(targetCliTypeZero : CliType)
|
||||||
|
(value : EvalStackValue)
|
||||||
|
(index : EvalStackValue)
|
||||||
|
(arr : EvalStackValue)
|
||||||
|
(currentThread : ThreadId)
|
||||||
|
(state : IlMachineState)
|
||||||
|
: ExecutionResult
|
||||||
|
=
|
||||||
|
let index =
|
||||||
|
match index with
|
||||||
|
| EvalStackValue.NativeInt src ->
|
||||||
|
match src with
|
||||||
|
| NativeIntSource.FunctionPointer _
|
||||||
|
| NativeIntSource.TypeHandlePtr _
|
||||||
|
| NativeIntSource.ManagedPointer _ -> failwith "Refusing to treat a pointer as an array index"
|
||||||
|
| NativeIntSource.Verbatim i -> i |> int32
|
||||||
|
| EvalStackValue.Int32 i -> i
|
||||||
|
| _ -> failwith $"Invalid index: {index}"
|
||||||
|
|
||||||
|
let arrAddr =
|
||||||
|
match arr with
|
||||||
|
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr)
|
||||||
|
| EvalStackValue.ObjectRef addr -> addr
|
||||||
|
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE"
|
||||||
|
| _ -> failwith $"Invalid array: %O{arr}"
|
||||||
|
// TODO: throw ArrayTypeMismatchException if incorrect types
|
||||||
|
|
||||||
|
let arr = state.ManagedHeap.Arrays.[arrAddr]
|
||||||
|
|
||||||
|
if index < 0 || index >= arr.Length then
|
||||||
|
failwith "TODO: throw IndexOutOfRangeException"
|
||||||
|
|
||||||
|
let state =
|
||||||
|
state
|
||||||
|
|> IlMachineState.setArrayValue arrAddr (EvalStackValue.toCliTypeCoerced targetCliTypeZero value) index
|
||||||
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
|
|
||||||
|
ExecutionResult.Stepped (state, WhatWeDid.Executed)
|
||||||
|
|
||||||
let internal execute
|
let internal execute
|
||||||
(loggerFactory : ILoggerFactory)
|
(loggerFactory : ILoggerFactory)
|
||||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||||
@@ -346,92 +364,57 @@ module NullaryIlOp =
|
|||||||
let var2, state = state |> IlMachineState.popEvalStack currentThread
|
let var2, state = state |> IlMachineState.popEvalStack currentThread
|
||||||
let var1, state = state |> IlMachineState.popEvalStack currentThread
|
let var1, state = state |> IlMachineState.popEvalStack currentThread
|
||||||
|
|
||||||
let comparisonResult =
|
let comparisonResult = if EvalStackValueComparisons.ceq var1 var2 then 1 else 0
|
||||||
// Table III.4
|
|
||||||
match var1, var2 with
|
state
|
||||||
| EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> if var1 = var2 then 1 else 0
|
|> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 comparisonResult) currentThread
|
||||||
| EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 -> failwith "TODO: int32 CEQ nativeint"
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
| EvalStackValue.Int32 _, _ -> failwith $"bad ceq: Int32 vs {var2}"
|
|> Tuple.withRight WhatWeDid.Executed
|
||||||
| EvalStackValue.Int64 var1, EvalStackValue.Int64 var2 -> if var1 = var2 then 1 else 0
|
|> ExecutionResult.Stepped
|
||||||
| EvalStackValue.Int64 _, _ -> failwith $"bad ceq: Int64 vs {var2}"
|
| Cgt ->
|
||||||
| EvalStackValue.Float var1, EvalStackValue.Float var2 -> failwith "TODO: float CEQ float"
|
let var2, state = state |> IlMachineState.popEvalStack currentThread
|
||||||
| EvalStackValue.Float _, _ -> failwith $"bad ceq: Float vs {var2}"
|
let var1, state = state |> IlMachineState.popEvalStack currentThread
|
||||||
| EvalStackValue.NativeInt var1, EvalStackValue.NativeInt var2 ->
|
|
||||||
match var1, var2 with
|
let comparisonResult = if EvalStackValueComparisons.cgt var1 var2 then 1 else 0
|
||||||
| NativeIntSource.FunctionPointer f1, NativeIntSource.FunctionPointer f2 ->
|
|
||||||
if f1 = f2 then
|
state
|
||||||
1
|
|> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 comparisonResult) currentThread
|
||||||
else
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
failwith $"TODO(CEQ): nativeint vs nativeint, {f1} vs {f2}"
|
|> Tuple.withRight WhatWeDid.Executed
|
||||||
| NativeIntSource.TypeHandlePtr f1, NativeIntSource.TypeHandlePtr f2 -> if f1 = f2 then 1 else 0
|
|> ExecutionResult.Stepped
|
||||||
| NativeIntSource.Verbatim f1, NativeIntSource.Verbatim f2 -> if f1 = f2 then 1 else 0
|
| Cgt_un ->
|
||||||
| NativeIntSource.ManagedPointer f1, NativeIntSource.ManagedPointer f2 -> if f1 = f2 then 1 else 0
|
let var2, state = state |> IlMachineState.popEvalStack currentThread
|
||||||
| _, _ -> failwith $"TODO (CEQ): nativeint vs nativeint, {var1} vs {var2}"
|
let var1, state = state |> IlMachineState.popEvalStack currentThread
|
||||||
| EvalStackValue.NativeInt var1, EvalStackValue.Int32 var2 -> failwith $"TODO (CEQ): nativeint vs int32"
|
|
||||||
| EvalStackValue.NativeInt var1, EvalStackValue.ManagedPointer var2 ->
|
let comparisonResult = if EvalStackValueComparisons.cgtUn var1 var2 then 1 else 0
|
||||||
failwith $"TODO (CEQ): nativeint vs managed pointer"
|
|
||||||
| EvalStackValue.NativeInt _, _ -> failwith $"bad ceq: NativeInt vs {var2}"
|
|
||||||
| EvalStackValue.ObjectRef var1, EvalStackValue.ObjectRef var2 -> if var1 = var2 then 1 else 0
|
|
||||||
| EvalStackValue.ObjectRef _, _ -> failwith $"bad ceq: ObjectRef vs {var2}"
|
|
||||||
| EvalStackValue.ManagedPointer var1, EvalStackValue.ManagedPointer var2 -> if var1 = var2 then 1 else 0
|
|
||||||
| EvalStackValue.ManagedPointer var1, EvalStackValue.NativeInt var2 ->
|
|
||||||
failwith $"TODO (CEQ): managed pointer vs nativeint"
|
|
||||||
| EvalStackValue.ManagedPointer _, _ -> failwith $"bad ceq: ManagedPointer vs {var2}"
|
|
||||||
| EvalStackValue.UserDefinedValueType _, _ -> failwith $"bad ceq: UserDefinedValueType vs {var2}"
|
|
||||||
|
|
||||||
state
|
state
|
||||||
|> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 comparisonResult) currentThread
|
|> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 comparisonResult) currentThread
|
||||||
|> IlMachineState.advanceProgramCounter currentThread
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
|> Tuple.withRight WhatWeDid.Executed
|
|> Tuple.withRight WhatWeDid.Executed
|
||||||
|> ExecutionResult.Stepped
|
|> ExecutionResult.Stepped
|
||||||
| Cgt -> failwith "TODO: Cgt unimplemented"
|
|
||||||
| Cgt_un -> failwith "TODO: Cgt_un unimplemented"
|
|
||||||
| Clt ->
|
| Clt ->
|
||||||
let var2, state = state |> IlMachineState.popEvalStack currentThread
|
let var2, state = state |> IlMachineState.popEvalStack currentThread
|
||||||
let var1, state = state |> IlMachineState.popEvalStack currentThread
|
let var1, state = state |> IlMachineState.popEvalStack currentThread
|
||||||
|
|
||||||
let comparisonResult =
|
let comparisonResult = if EvalStackValueComparisons.clt var1 var2 then 1 else 0
|
||||||
match var1, var2 with
|
|
||||||
| EvalStackValue.Int64 var1, EvalStackValue.Int64 var2 -> if var1 < var2 then 1 else 0
|
state
|
||||||
| EvalStackValue.Float var1, EvalStackValue.Float var2 ->
|
|> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 comparisonResult) currentThread
|
||||||
failwith "TODO: Clt float comparison unimplemented"
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
| EvalStackValue.ObjectRef var1, EvalStackValue.ObjectRef var2 ->
|
|> Tuple.withRight WhatWeDid.Executed
|
||||||
failwith $"Clt instruction invalid for comparing object refs, {var1} vs {var2}"
|
|> ExecutionResult.Stepped
|
||||||
| EvalStackValue.ObjectRef var1, other -> failwith $"invalid comparison, ref %O{var1} vs %O{other}"
|
| Clt_un ->
|
||||||
| other, EvalStackValue.ObjectRef var2 -> failwith $"invalid comparison, %O{other} vs ref %O{var2}"
|
let var2, state = state |> IlMachineState.popEvalStack currentThread
|
||||||
| EvalStackValue.Float i, other -> failwith $"invalid comparison, float %f{i} vs %O{other}"
|
let var1, state = state |> IlMachineState.popEvalStack currentThread
|
||||||
| other, EvalStackValue.Float i -> failwith $"invalid comparison, %O{other} vs float %f{i}"
|
|
||||||
| EvalStackValue.Int64 i, other -> failwith $"invalid comparison, int64 %i{i} vs %O{other}"
|
let comparisonResult = if EvalStackValueComparisons.cltUn var1 var2 then 1 else 0
|
||||||
| other, EvalStackValue.Int64 i -> failwith $"invalid comparison, %O{other} vs int64 %i{i}"
|
|
||||||
| EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> if var1 < var2 then 1 else 0
|
|
||||||
| EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 ->
|
|
||||||
failwith "TODO: Clt Int32 vs NativeInt comparison unimplemented"
|
|
||||||
| EvalStackValue.Int32 i, other -> failwith $"invalid comparison, int32 %i{i} vs %O{other}"
|
|
||||||
| EvalStackValue.NativeInt var1, EvalStackValue.Int32 var2 ->
|
|
||||||
failwith "TODO: Clt NativeInt vs Int32 comparison unimplemented"
|
|
||||||
| other, EvalStackValue.Int32 var2 -> failwith $"invalid comparison, {other} vs int32 {var2}"
|
|
||||||
| EvalStackValue.NativeInt var1, EvalStackValue.NativeInt var2 ->
|
|
||||||
if NativeIntSource.isLess var1 var2 then 1 else 0
|
|
||||||
| EvalStackValue.NativeInt var1, other -> failwith $"invalid comparison, nativeint {var1} vs %O{other}"
|
|
||||||
| EvalStackValue.ManagedPointer managedPointerSource, NativeInt int64 ->
|
|
||||||
failwith "TODO: Clt ManagedPointer vs NativeInt comparison unimplemented"
|
|
||||||
| EvalStackValue.ManagedPointer managedPointerSource, ManagedPointer pointerSource ->
|
|
||||||
failwith "TODO: Clt ManagedPointer vs ManagedPointer comparison unimplemented"
|
|
||||||
| EvalStackValue.ManagedPointer managedPointerSource, UserDefinedValueType _ ->
|
|
||||||
failwith "TODO: Clt ManagedPointer vs UserDefinedValueType comparison unimplemented"
|
|
||||||
| EvalStackValue.UserDefinedValueType _, NativeInt int64 ->
|
|
||||||
failwith "TODO: Clt UserDefinedValueType vs NativeInt comparison unimplemented"
|
|
||||||
| EvalStackValue.UserDefinedValueType _, ManagedPointer managedPointerSource ->
|
|
||||||
failwith "TODO: Clt UserDefinedValueType vs ManagedPointer comparison unimplemented"
|
|
||||||
| EvalStackValue.UserDefinedValueType _, UserDefinedValueType _ ->
|
|
||||||
failwith "TODO: Clt UserDefinedValueType vs UserDefinedValueType comparison unimplemented"
|
|
||||||
|
|
||||||
state
|
state
|
||||||
|> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 comparisonResult) currentThread
|
|> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 comparisonResult) currentThread
|
||||||
|> IlMachineState.advanceProgramCounter currentThread
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
|> Tuple.withRight WhatWeDid.Executed
|
|> Tuple.withRight WhatWeDid.Executed
|
||||||
|> ExecutionResult.Stepped
|
|> ExecutionResult.Stepped
|
||||||
| Clt_un -> failwith "TODO: Clt_un unimplemented"
|
|
||||||
| Stloc_0 ->
|
| Stloc_0 ->
|
||||||
state
|
state
|
||||||
|> IlMachineState.popFromStackToLocalVariable currentThread 0
|
|> IlMachineState.popFromStackToLocalVariable currentThread 0
|
||||||
@@ -456,12 +439,22 @@ module NullaryIlOp =
|
|||||||
|> IlMachineState.advanceProgramCounter currentThread
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
|> Tuple.withRight WhatWeDid.Executed
|
|> Tuple.withRight WhatWeDid.Executed
|
||||||
|> ExecutionResult.Stepped
|
|> ExecutionResult.Stepped
|
||||||
| Sub -> failwith "TODO: Sub unimplemented"
|
| Sub ->
|
||||||
|
let val2, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let val1, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let result = BinaryArithmetic.execute ArithmeticOperation.sub val1 val2
|
||||||
|
|
||||||
|
state
|
||||||
|
|> IlMachineState.pushToEvalStack' result currentThread
|
||||||
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
|
|> Tuple.withRight WhatWeDid.Executed
|
||||||
|
|> ExecutionResult.Stepped
|
||||||
| Sub_ovf -> failwith "TODO: Sub_ovf unimplemented"
|
| Sub_ovf -> failwith "TODO: Sub_ovf unimplemented"
|
||||||
| Sub_ovf_un -> failwith "TODO: Sub_ovf_un unimplemented"
|
| Sub_ovf_un -> failwith "TODO: Sub_ovf_un unimplemented"
|
||||||
| Add ->
|
| Add ->
|
||||||
let result, state =
|
let val1, state = IlMachineState.popEvalStack currentThread state
|
||||||
binaryArithmeticOperation ArithmeticOperation.add currentThread state
|
let val2, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let result = BinaryArithmetic.execute ArithmeticOperation.add val1 val2
|
||||||
|
|
||||||
state
|
state
|
||||||
|> IlMachineState.pushToEvalStack' result currentThread
|
|> IlMachineState.pushToEvalStack' result currentThread
|
||||||
@@ -471,8 +464,9 @@ module NullaryIlOp =
|
|||||||
| Add_ovf -> failwith "TODO: Add_ovf unimplemented"
|
| Add_ovf -> failwith "TODO: Add_ovf unimplemented"
|
||||||
| Add_ovf_un -> failwith "TODO: Add_ovf_un unimplemented"
|
| Add_ovf_un -> failwith "TODO: Add_ovf_un unimplemented"
|
||||||
| Mul ->
|
| Mul ->
|
||||||
let result, state =
|
let val1, state = IlMachineState.popEvalStack currentThread state
|
||||||
binaryArithmeticOperation ArithmeticOperation.mul currentThread state
|
let val2, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let result = BinaryArithmetic.execute ArithmeticOperation.mul val1 val2
|
||||||
|
|
||||||
state
|
state
|
||||||
|> IlMachineState.pushToEvalStack' result currentThread
|
|> IlMachineState.pushToEvalStack' result currentThread
|
||||||
@@ -483,11 +477,119 @@ module NullaryIlOp =
|
|||||||
| Mul_ovf_un -> failwith "TODO: Mul_ovf_un unimplemented"
|
| Mul_ovf_un -> failwith "TODO: Mul_ovf_un unimplemented"
|
||||||
| Div -> failwith "TODO: Div unimplemented"
|
| Div -> failwith "TODO: Div unimplemented"
|
||||||
| Div_un -> failwith "TODO: Div_un unimplemented"
|
| Div_un -> failwith "TODO: Div_un unimplemented"
|
||||||
| Shr -> failwith "TODO: Shr unimplemented"
|
| Shr ->
|
||||||
|
let shift, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let number, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
|
||||||
|
let shift =
|
||||||
|
match shift with
|
||||||
|
| EvalStackValue.Int32 i -> i
|
||||||
|
| EvalStackValue.NativeInt (NativeIntSource.Verbatim i) -> int<int64> i
|
||||||
|
| _ -> failwith $"Not allowed shift of {shift}"
|
||||||
|
|
||||||
|
let result =
|
||||||
|
// See table III.6
|
||||||
|
match number with
|
||||||
|
| EvalStackValue.Int32 i -> i >>> shift |> EvalStackValue.Int32
|
||||||
|
| EvalStackValue.Int64 i -> i >>> shift |> EvalStackValue.Int64
|
||||||
|
| EvalStackValue.NativeInt (NativeIntSource.Verbatim i) ->
|
||||||
|
(i >>> shift) |> NativeIntSource.Verbatim |> EvalStackValue.NativeInt
|
||||||
|
| _ -> failwith $"Not allowed to shift {number}"
|
||||||
|
|
||||||
|
let state =
|
||||||
|
state
|
||||||
|
|> IlMachineState.pushToEvalStack' result currentThread
|
||||||
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
|
|
||||||
|
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||||
| Shr_un -> failwith "TODO: Shr_un unimplemented"
|
| Shr_un -> failwith "TODO: Shr_un unimplemented"
|
||||||
| Shl -> failwith "TODO: Shl unimplemented"
|
| Shl ->
|
||||||
| And -> failwith "TODO: And unimplemented"
|
let shift, state = IlMachineState.popEvalStack currentThread state
|
||||||
| Or -> failwith "TODO: Or unimplemented"
|
let number, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
|
||||||
|
let shift =
|
||||||
|
match shift with
|
||||||
|
| EvalStackValue.Int32 i -> i
|
||||||
|
| EvalStackValue.NativeInt (NativeIntSource.Verbatim i) -> int<int64> i
|
||||||
|
| _ -> failwith $"Not allowed shift of {shift}"
|
||||||
|
|
||||||
|
let result =
|
||||||
|
// See table III.6
|
||||||
|
match number with
|
||||||
|
| EvalStackValue.Int32 i -> i <<< shift |> EvalStackValue.Int32
|
||||||
|
| EvalStackValue.Int64 i -> i <<< shift |> EvalStackValue.Int64
|
||||||
|
| EvalStackValue.NativeInt (NativeIntSource.Verbatim i) ->
|
||||||
|
(i <<< shift) |> NativeIntSource.Verbatim |> EvalStackValue.NativeInt
|
||||||
|
| _ -> failwith $"Not allowed to shift {number}"
|
||||||
|
|
||||||
|
let state =
|
||||||
|
state
|
||||||
|
|> IlMachineState.pushToEvalStack' result currentThread
|
||||||
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
|
|
||||||
|
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||||
|
| And ->
|
||||||
|
let v2, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let v1, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
|
||||||
|
let result =
|
||||||
|
match v1, v2 with
|
||||||
|
| EvalStackValue.Int32 v1, EvalStackValue.Int32 v2 -> v1 &&& v2 |> EvalStackValue.Int32
|
||||||
|
| EvalStackValue.Int32 v1, EvalStackValue.NativeInt (NativeIntSource.Verbatim v2) ->
|
||||||
|
int64<int32> v1 &&& v2 |> NativeIntSource.Verbatim |> EvalStackValue.NativeInt
|
||||||
|
| EvalStackValue.Int32 _, EvalStackValue.NativeInt _ ->
|
||||||
|
failwith $"can't do binary operation on non-verbatim native int {v2}"
|
||||||
|
| EvalStackValue.Int64 v1, EvalStackValue.Int64 v2 -> v1 &&& v2 |> EvalStackValue.Int64
|
||||||
|
| EvalStackValue.NativeInt (NativeIntSource.Verbatim v1), EvalStackValue.Int32 v2 ->
|
||||||
|
v1 &&& int64<int32> v2 |> NativeIntSource.Verbatim |> EvalStackValue.NativeInt
|
||||||
|
| EvalStackValue.NativeInt _, EvalStackValue.Int32 _ ->
|
||||||
|
failwith $"can't do binary operation on non-verbatim native int {v1}"
|
||||||
|
| EvalStackValue.NativeInt (NativeIntSource.Verbatim v1),
|
||||||
|
EvalStackValue.NativeInt (NativeIntSource.Verbatim v2) ->
|
||||||
|
v1 &&& v2 |> NativeIntSource.Verbatim |> EvalStackValue.NativeInt
|
||||||
|
| EvalStackValue.NativeInt (NativeIntSource.Verbatim _), EvalStackValue.NativeInt _ ->
|
||||||
|
failwith $"can't do binary operation on non-verbatim native int {v2}"
|
||||||
|
| EvalStackValue.NativeInt _, EvalStackValue.NativeInt (NativeIntSource.Verbatim _) ->
|
||||||
|
failwith $"can't do binary operation on non-verbatim native int {v1}"
|
||||||
|
| _, _ -> failwith $"refusing to do binary operation on {v1} and {v2}"
|
||||||
|
|
||||||
|
let state =
|
||||||
|
state
|
||||||
|
|> IlMachineState.pushToEvalStack' result currentThread
|
||||||
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
|
|
||||||
|
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||||
|
| Or ->
|
||||||
|
let v2, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let v1, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
|
||||||
|
let result =
|
||||||
|
match v1, v2 with
|
||||||
|
| EvalStackValue.Int32 v1, EvalStackValue.Int32 v2 -> v1 ||| v2 |> EvalStackValue.Int32
|
||||||
|
| EvalStackValue.Int32 v1, EvalStackValue.NativeInt (NativeIntSource.Verbatim v2) ->
|
||||||
|
int64<int32> v1 ||| v2 |> NativeIntSource.Verbatim |> EvalStackValue.NativeInt
|
||||||
|
| EvalStackValue.Int32 _, EvalStackValue.NativeInt _ ->
|
||||||
|
failwith $"can't do binary operation on non-verbatim native int {v2}"
|
||||||
|
| EvalStackValue.Int64 v1, EvalStackValue.Int64 v2 -> v1 ||| v2 |> EvalStackValue.Int64
|
||||||
|
| EvalStackValue.NativeInt (NativeIntSource.Verbatim v1), EvalStackValue.Int32 v2 ->
|
||||||
|
v1 ||| int64<int32> v2 |> NativeIntSource.Verbatim |> EvalStackValue.NativeInt
|
||||||
|
| EvalStackValue.NativeInt _, EvalStackValue.Int32 _ ->
|
||||||
|
failwith $"can't do binary operation on non-verbatim native int {v1}"
|
||||||
|
| EvalStackValue.NativeInt (NativeIntSource.Verbatim v1),
|
||||||
|
EvalStackValue.NativeInt (NativeIntSource.Verbatim v2) ->
|
||||||
|
v1 ||| v2 |> NativeIntSource.Verbatim |> EvalStackValue.NativeInt
|
||||||
|
| EvalStackValue.NativeInt (NativeIntSource.Verbatim _), EvalStackValue.NativeInt _ ->
|
||||||
|
failwith $"can't do binary operation on non-verbatim native int {v2}"
|
||||||
|
| EvalStackValue.NativeInt _, EvalStackValue.NativeInt (NativeIntSource.Verbatim _) ->
|
||||||
|
failwith $"can't do binary operation on non-verbatim native int {v1}"
|
||||||
|
| _, _ -> failwith $"refusing to do binary operation on {v1} and {v2}"
|
||||||
|
|
||||||
|
let state =
|
||||||
|
state
|
||||||
|
|> IlMachineState.pushToEvalStack' result currentThread
|
||||||
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
|
|
||||||
|
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||||
| Xor -> failwith "TODO: Xor unimplemented"
|
| Xor -> failwith "TODO: Xor unimplemented"
|
||||||
| Conv_I ->
|
| Conv_I ->
|
||||||
let popped, state = IlMachineState.popEvalStack currentThread state
|
let popped, state = IlMachineState.popEvalStack currentThread state
|
||||||
@@ -675,7 +777,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
|
||||||
@@ -798,6 +900,7 @@ module NullaryIlOp =
|
|||||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
|
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
|
||||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
|
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
|
||||||
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
|
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
|
||||||
|
| ManagedPointerSource.ArrayIndex _ -> failwith "todo"
|
||||||
| a -> failwith $"TODO: {a}"
|
| a -> failwith $"TODO: {a}"
|
||||||
|
|
||||||
let state =
|
let state =
|
||||||
@@ -807,7 +910,29 @@ module NullaryIlOp =
|
|||||||
|> IlMachineState.advanceProgramCounter currentThread
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
|
|
||||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||||
| Stind_ref -> failwith "TODO: Stind_ref unimplemented"
|
| Stind_ref ->
|
||||||
|
let value, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let addr, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
|
||||||
|
let state =
|
||||||
|
match addr with
|
||||||
|
| EvalStackValue.ManagedPointer src ->
|
||||||
|
match src with
|
||||||
|
| ManagedPointerSource.Null -> failwith "TODO: throw NRE"
|
||||||
|
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> failwith "todo"
|
||||||
|
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> failwith "todo"
|
||||||
|
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
|
||||||
|
| ManagedPointerSource.ArrayIndex (arr, index) ->
|
||||||
|
state
|
||||||
|
|> IlMachineState.setArrayValue
|
||||||
|
arr
|
||||||
|
(EvalStackValue.toCliTypeCoerced (CliType.ObjectRef None) value)
|
||||||
|
index
|
||||||
|
| addr -> failwith $"TODO: {addr}"
|
||||||
|
|
||||||
|
let state = state |> IlMachineState.advanceProgramCounter currentThread
|
||||||
|
|
||||||
|
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||||
| Ldelem_i -> failwith "TODO: Ldelem_i unimplemented"
|
| Ldelem_i -> failwith "TODO: Ldelem_i unimplemented"
|
||||||
| Ldelem_i1 -> failwith "TODO: Ldelem_i1 unimplemented"
|
| Ldelem_i1 -> failwith "TODO: Ldelem_i1 unimplemented"
|
||||||
| Ldelem_u1 -> failwith "TODO: Ldelem_u1 unimplemented"
|
| Ldelem_u1 -> failwith "TODO: Ldelem_u1 unimplemented"
|
||||||
@@ -819,19 +944,54 @@ module NullaryIlOp =
|
|||||||
| Ldelem_u8 -> failwith "TODO: Ldelem_u8 unimplemented"
|
| Ldelem_u8 -> failwith "TODO: Ldelem_u8 unimplemented"
|
||||||
| Ldelem_r4 -> failwith "TODO: Ldelem_r4 unimplemented"
|
| Ldelem_r4 -> failwith "TODO: Ldelem_r4 unimplemented"
|
||||||
| Ldelem_r8 -> failwith "TODO: Ldelem_r8 unimplemented"
|
| Ldelem_r8 -> failwith "TODO: Ldelem_r8 unimplemented"
|
||||||
| Ldelem_ref -> failwith "TODO: Ldelem_ref unimplemented"
|
| Ldelem_ref ->
|
||||||
| Stelem_i -> failwith "TODO: Stelem_i unimplemented"
|
let index, state = IlMachineState.popEvalStack currentThread state
|
||||||
| Stelem_i1 -> failwith "TODO: Stelem_i1 unimplemented"
|
let arr, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
|
||||||
|
ldElem (CliType.ObjectRef None) index arr currentThread state
|
||||||
|
| Stelem_i ->
|
||||||
|
let value, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let index, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let arr, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
|
||||||
|
stElem
|
||||||
|
(CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.Verbatim 0L)))
|
||||||
|
value
|
||||||
|
index
|
||||||
|
arr
|
||||||
|
currentThread
|
||||||
|
state
|
||||||
|
| Stelem_i1 ->
|
||||||
|
let value, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let index, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let arr, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
stElem (CliType.Numeric (CliNumericType.Int8 0y)) value index arr currentThread state
|
||||||
| Stelem_u1 -> failwith "TODO: Stelem_u1 unimplemented"
|
| Stelem_u1 -> failwith "TODO: Stelem_u1 unimplemented"
|
||||||
| Stelem_i2 -> failwith "TODO: Stelem_i2 unimplemented"
|
| Stelem_i2 ->
|
||||||
|
let value, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let index, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let arr, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
stElem (CliType.Numeric (CliNumericType.Int16 0s)) value index arr currentThread state
|
||||||
| Stelem_u2 -> failwith "TODO: Stelem_u2 unimplemented"
|
| Stelem_u2 -> failwith "TODO: Stelem_u2 unimplemented"
|
||||||
| Stelem_i4 -> failwith "TODO: Stelem_i4 unimplemented"
|
| Stelem_i4 ->
|
||||||
|
let value, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let index, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let arr, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
stElem (CliType.Numeric (CliNumericType.Int32 0)) value index arr currentThread state
|
||||||
| Stelem_u4 -> failwith "TODO: Stelem_u4 unimplemented"
|
| Stelem_u4 -> failwith "TODO: Stelem_u4 unimplemented"
|
||||||
| Stelem_i8 -> failwith "TODO: Stelem_i8 unimplemented"
|
| Stelem_i8 ->
|
||||||
|
let value, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let index, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let arr, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
stElem (CliType.Numeric (CliNumericType.Int64 0L)) value index arr currentThread state
|
||||||
| Stelem_u8 -> failwith "TODO: Stelem_u8 unimplemented"
|
| Stelem_u8 -> failwith "TODO: Stelem_u8 unimplemented"
|
||||||
| Stelem_r4 -> failwith "TODO: Stelem_r4 unimplemented"
|
| Stelem_r4 -> failwith "TODO: Stelem_r4 unimplemented"
|
||||||
| Stelem_r8 -> failwith "TODO: Stelem_r8 unimplemented"
|
| Stelem_r8 -> failwith "TODO: Stelem_r8 unimplemented"
|
||||||
| Stelem_ref -> failwith "TODO: Stelem_ref unimplemented"
|
| Stelem_ref ->
|
||||||
|
let value, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let index, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let arr, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
stElem (CliType.ObjectRef None) value index arr currentThread state
|
||||||
| Cpblk -> failwith "TODO: Cpblk unimplemented"
|
| Cpblk -> failwith "TODO: Cpblk unimplemented"
|
||||||
| Initblk -> failwith "TODO: Initblk unimplemented"
|
| Initblk -> failwith "TODO: Initblk unimplemented"
|
||||||
| Conv_ovf_u1 -> failwith "TODO: Conv_ovf_u1 unimplemented"
|
| Conv_ovf_u1 -> failwith "TODO: Conv_ovf_u1 unimplemented"
|
||||||
|
@@ -3,6 +3,7 @@ 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>]
|
||||||
@@ -14,14 +15,23 @@ module Program =
|
|||||||
(state : IlMachineState)
|
(state : IlMachineState)
|
||||||
: ManagedHeapAddress * IlMachineState
|
: ManagedHeapAddress * IlMachineState
|
||||||
=
|
=
|
||||||
|
let state, stringType =
|
||||||
|
TypeDefn.FromDefinition (
|
||||||
|
ComparableTypeDefinitionHandle.Make corelib.String.TypeDefHandle,
|
||||||
|
corelib.Corelib.Name.FullName,
|
||||||
|
SignatureTypeKind.Class
|
||||||
|
)
|
||||||
|
|> IlMachineState.concretizeType
|
||||||
|
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
|
IlMachineState.allocateManagedObject stringType (failwith "TODO: assert fields and populate") state
|
||||||
(corelib.String
|
|
||||||
|> TypeInfo.mapGeneric (fun _ _ -> failwith<unit> "there are no generics here"))
|
|
||||||
(failwith "TODO: assert fields and populate")
|
|
||||||
state
|
|
||||||
// TODO: set the char values in memory
|
// TODO: set the char values in memory
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,7 +42,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
|
||||||
)
|
)
|
||||||
@@ -83,70 +93,180 @@ module Program =
|
|||||||
| None -> failwith "No entry point in input DLL"
|
| None -> failwith "No entry point in input DLL"
|
||||||
| Some d -> d
|
| Some d -> d
|
||||||
|
|
||||||
let mainMethod = dumped.Methods.[entryPoint]
|
let mainMethodFromMetadata = dumped.Methods.[entryPoint]
|
||||||
|
|
||||||
if mainMethod.Signature.GenericParameterCount > 0 then
|
if mainMethodFromMetadata.Signature.GenericParameterCount > 0 then
|
||||||
failwith "Refusing to execute generic main method"
|
failwith "Refusing to execute generic main method"
|
||||||
|
|
||||||
let mainMethod =
|
let state = IlMachineState.initial loggerFactory dotnetRuntimeDirs dumped
|
||||||
mainMethod
|
|
||||||
|> MethodInfo.mapTypeGenerics (fun _ -> failwith "Refusing to execute generic main method")
|
|
||||||
|> MethodInfo.mapMethodGenerics (fun _ -> failwith "Refusing to execute generic main method")
|
|
||||||
|
|
||||||
let rec computeState (baseClassTypes : BaseClassTypes<DumpedAssembly> option) (state : IlMachineState) =
|
// Find the core library by traversing the type hierarchy of the main method's declaring type
|
||||||
// The thread's state is slightly fake: we will need to put arguments onto the stack before actually
|
// until we reach System.Object
|
||||||
// executing the main method.
|
let rec handleBaseTypeInfo
|
||||||
// We construct the thread here before we are entirely ready, because we need a thread from which to
|
(state : IlMachineState)
|
||||||
// initialise the class containing the main method.
|
(baseTypeInfo : BaseTypeInfo)
|
||||||
// Once we've obtained e.g. the String and Array classes, we can populate the args array.
|
(currentAssembly : DumpedAssembly)
|
||||||
match
|
(continueWithGeneric :
|
||||||
MethodState.Empty
|
IlMachineState
|
||||||
(Option.toObj baseClassTypes)
|
-> TypeInfo<GenericParamFromMetadata, TypeDefn>
|
||||||
state._LoadedAssemblies
|
-> DumpedAssembly
|
||||||
dumped
|
-> IlMachineState * BaseClassTypes<DumpedAssembly> option)
|
||||||
// pretend there are no instructions, so we avoid preparing anything
|
(continueWithResolved :
|
||||||
{ mainMethod with
|
IlMachineState
|
||||||
Instructions = Some MethodInstructions.OnlyRet
|
-> TypeInfo<TypeDefn, TypeDefn>
|
||||||
}
|
-> DumpedAssembly
|
||||||
None
|
-> IlMachineState * BaseClassTypes<DumpedAssembly> option)
|
||||||
(ImmutableArray.CreateRange [ CliType.ObjectRef None ])
|
: IlMachineState * BaseClassTypes<DumpedAssembly> option
|
||||||
None
|
=
|
||||||
with
|
match baseTypeInfo with
|
||||||
| Ok meth -> IlMachineState.addThread meth dumped.Name state, baseClassTypes
|
| BaseTypeInfo.TypeRef typeRefHandle ->
|
||||||
| Error requiresRefs ->
|
// Look up the TypeRef from the handle
|
||||||
let state =
|
let typeRef = currentAssembly.TypeRefs.[typeRefHandle]
|
||||||
(state, requiresRefs)
|
|
||||||
||> List.fold (fun state ref ->
|
let rec go state =
|
||||||
let handle, referencingAssy = ref.Handle
|
// Resolve the type reference to find which assembly it's in
|
||||||
let referencingAssy = state.LoadedAssembly referencingAssy |> Option.get
|
match
|
||||||
|
Assembly.resolveTypeRef state._LoadedAssemblies currentAssembly typeRef ImmutableArray.Empty
|
||||||
|
with
|
||||||
|
| TypeResolutionResult.FirstLoadAssy assyRef ->
|
||||||
|
// Need to load this assembly first
|
||||||
|
let handle, definedIn = assyRef.Handle
|
||||||
|
|
||||||
let state, _, _ =
|
let state, _, _ =
|
||||||
IlMachineState.loadAssembly loggerFactory referencingAssy handle state
|
IlMachineState.loadAssembly
|
||||||
|
loggerFactory
|
||||||
|
state._LoadedAssemblies.[definedIn.FullName]
|
||||||
|
handle
|
||||||
|
state
|
||||||
|
|
||||||
|
go state
|
||||||
|
| TypeResolutionResult.Resolved (resolvedAssembly, resolvedType) ->
|
||||||
|
continueWithResolved state resolvedType resolvedAssembly
|
||||||
|
|
||||||
|
go state
|
||||||
|
| BaseTypeInfo.TypeDef typeDefHandle ->
|
||||||
|
// Base type is in the same assembly
|
||||||
|
let baseType = currentAssembly.TypeDefs.[typeDefHandle]
|
||||||
|
continueWithGeneric state baseType currentAssembly
|
||||||
|
| BaseTypeInfo.TypeSpec _ -> failwith "Type specs not yet supported in base type traversal"
|
||||||
|
| BaseTypeInfo.ForeignAssemblyType (assemblyName, typeDefHandle) ->
|
||||||
|
// Base type is in a foreign assembly
|
||||||
|
match state._LoadedAssemblies.TryGetValue assemblyName.FullName with
|
||||||
|
| true, foreignAssembly ->
|
||||||
|
let baseType = foreignAssembly.TypeDefs.[typeDefHandle]
|
||||||
|
continueWithGeneric state baseType foreignAssembly
|
||||||
|
| false, _ -> failwith $"Foreign assembly {assemblyName.FullName} not loaded"
|
||||||
|
|
||||||
|
let rec findCoreLibraryAssemblyFromGeneric
|
||||||
|
(state : IlMachineState)
|
||||||
|
(currentType : TypeInfo<GenericParamFromMetadata, TypeDefn>)
|
||||||
|
(currentAssembly : DumpedAssembly)
|
||||||
|
=
|
||||||
|
match currentType.BaseType with
|
||||||
|
| None ->
|
||||||
|
// We've reached the root (System.Object), so this assembly contains the core library
|
||||||
|
let baseTypes = Corelib.getBaseTypes currentAssembly
|
||||||
|
state, Some baseTypes
|
||||||
|
| Some baseTypeInfo ->
|
||||||
|
handleBaseTypeInfo
|
||||||
|
state
|
||||||
|
baseTypeInfo
|
||||||
|
currentAssembly
|
||||||
|
findCoreLibraryAssemblyFromGeneric
|
||||||
|
findCoreLibraryAssemblyFromResolved
|
||||||
|
|
||||||
|
and findCoreLibraryAssemblyFromResolved
|
||||||
|
(state : IlMachineState)
|
||||||
|
(currentType : TypeInfo<TypeDefn, TypeDefn>)
|
||||||
|
(currentAssembly : DumpedAssembly)
|
||||||
|
=
|
||||||
|
match currentType.BaseType with
|
||||||
|
| None ->
|
||||||
|
// We've reached the root (System.Object), so this assembly contains the core library
|
||||||
|
let baseTypes = Corelib.getBaseTypes currentAssembly
|
||||||
|
state, Some baseTypes
|
||||||
|
| Some baseTypeInfo ->
|
||||||
|
handleBaseTypeInfo
|
||||||
|
state
|
||||||
|
baseTypeInfo
|
||||||
|
currentAssembly
|
||||||
|
findCoreLibraryAssemblyFromGeneric
|
||||||
|
findCoreLibraryAssemblyFromResolved
|
||||||
|
|
||||||
|
let rec computeState (baseClassTypes : BaseClassTypes<DumpedAssembly> option) (state : IlMachineState) =
|
||||||
|
match baseClassTypes with
|
||||||
|
| Some baseTypes ->
|
||||||
|
// We already have base class types, can directly create the concretized method
|
||||||
|
// Use the original method from metadata, but convert FakeUnit to TypeDefn
|
||||||
|
let rawMainMethod =
|
||||||
|
mainMethodFromMetadata
|
||||||
|
|> MethodInfo.mapTypeGenerics (fun (i, _) -> TypeDefn.GenericTypeParameter i.SequenceNumber)
|
||||||
|
|
||||||
|
let state, concretizedMainMethod, _ =
|
||||||
|
IlMachineState.concretizeMethodWithTypeGenerics
|
||||||
|
loggerFactory
|
||||||
|
baseTypes
|
||||||
|
ImmutableArray.Empty // No type generics for main method's declaring type
|
||||||
|
{ rawMainMethod with
|
||||||
|
Instructions = Some (MethodInstructions.onlyRet ())
|
||||||
|
}
|
||||||
|
None
|
||||||
|
dumped.Name
|
||||||
|
ImmutableArray.Empty
|
||||||
state
|
state
|
||||||
)
|
|
||||||
|
|
||||||
let corelib =
|
// Create the method state with the concretized method
|
||||||
let coreLib =
|
match
|
||||||
state._LoadedAssemblies.Keys
|
MethodState.Empty
|
||||||
|> Seq.tryFind (fun x -> x.StartsWith ("System.Private.CoreLib, ", StringComparison.Ordinal))
|
state.ConcreteTypes
|
||||||
|
baseTypes
|
||||||
|
state._LoadedAssemblies
|
||||||
|
dumped
|
||||||
|
concretizedMainMethod
|
||||||
|
ImmutableArray.Empty
|
||||||
|
(ImmutableArray.CreateRange [ CliType.ObjectRef None ])
|
||||||
|
None
|
||||||
|
with
|
||||||
|
| Ok concretizedMeth -> IlMachineState.addThread concretizedMeth dumped.Name state, Some baseTypes
|
||||||
|
| Error _ -> failwith "Unexpected failure creating method state with concretized method"
|
||||||
|
| None ->
|
||||||
|
// We need to discover the core library by traversing the type hierarchy
|
||||||
|
let mainMethodType =
|
||||||
|
dumped.TypeDefs.[mainMethodFromMetadata.DeclaringType.Definition.Get]
|
||||||
|
|
||||||
coreLib
|
let state, baseTypes =
|
||||||
|> Option.map (fun coreLib -> state._LoadedAssemblies.[coreLib] |> Corelib.getBaseTypes)
|
findCoreLibraryAssemblyFromGeneric state mainMethodType dumped
|
||||||
|
|
||||||
computeState corelib state
|
computeState baseTypes state
|
||||||
|
|
||||||
let (state, mainThread), baseClassTypes =
|
let (state, mainThread), baseClassTypes = state |> computeState None
|
||||||
IlMachineState.initial loggerFactory dotnetRuntimeDirs dumped
|
|
||||||
|> computeState None
|
// Now that we have base class types, concretize the main method for use in the rest of the function
|
||||||
|
let state, concretizedMainMethod, mainTypeHandle =
|
||||||
|
match baseClassTypes with
|
||||||
|
| Some baseTypes ->
|
||||||
|
let rawMainMethod =
|
||||||
|
mainMethodFromMetadata
|
||||||
|
|> MethodInfo.mapTypeGenerics (fun (i, _) -> TypeDefn.GenericTypeParameter i.SequenceNumber)
|
||||||
|
|
||||||
|
IlMachineState.concretizeMethodWithTypeGenerics
|
||||||
|
loggerFactory
|
||||||
|
baseTypes
|
||||||
|
ImmutableArray.Empty // No type generics for main method's declaring type
|
||||||
|
rawMainMethod
|
||||||
|
None
|
||||||
|
dumped.Name
|
||||||
|
ImmutableArray.Empty
|
||||||
|
state
|
||||||
|
| None -> failwith "Expected base class types to be available at this point"
|
||||||
|
|
||||||
let rec loadInitialState (state : IlMachineState) =
|
let rec loadInitialState (state : IlMachineState) =
|
||||||
match
|
match
|
||||||
state
|
state
|
||||||
|> IlMachineState.loadClass
|
|> IlMachineStateExecution.loadClass
|
||||||
loggerFactory
|
loggerFactory
|
||||||
(Option.toObj baseClassTypes)
|
(Option.toObj baseClassTypes)
|
||||||
mainMethod.DeclaringType
|
mainTypeHandle
|
||||||
mainThread
|
mainThread
|
||||||
with
|
with
|
||||||
| StateLoadResult.NothingToDo ilMachineState -> ilMachineState
|
| StateLoadResult.NothingToDo ilMachineState -> ilMachineState
|
||||||
@@ -167,12 +287,12 @@ module Program =
|
|||||||
| Some c -> c
|
| Some c -> c
|
||||||
|
|
||||||
let arrayAllocation, state =
|
let arrayAllocation, state =
|
||||||
match mainMethod.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 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 mainMethod.Signature.ReturnType with
|
match mainMethodFromMetadata.Signature.ReturnType with
|
||||||
| TypeDefn.PrimitiveType PrimitiveType.Int32 -> ()
|
| TypeDefn.PrimitiveType PrimitiveType.Int32 -> ()
|
||||||
| _ -> failwith "Main method must return int32; other types not currently supported"
|
| _ -> failwith "Main method must return int32; other types not currently supported"
|
||||||
|
|
||||||
@@ -185,16 +305,17 @@ module Program =
|
|||||||
logger.LogInformation "Main method class now initialised"
|
logger.LogInformation "Main method class now initialised"
|
||||||
|
|
||||||
// Now that BCL initialisation has taken place and the user-code classes are constructed,
|
// Now that BCL initialisation has taken place and the user-code classes are constructed,
|
||||||
// overwrite the main thread completely.
|
// overwrite the main thread completely using the already-concretized method.
|
||||||
let methodState =
|
let methodState =
|
||||||
match
|
match
|
||||||
MethodState.Empty
|
MethodState.Empty
|
||||||
|
state.ConcreteTypes
|
||||||
baseClassTypes
|
baseClassTypes
|
||||||
state._LoadedAssemblies
|
state._LoadedAssemblies
|
||||||
dumped
|
dumped
|
||||||
mainMethod
|
concretizedMainMethod
|
||||||
None
|
ImmutableArray.Empty
|
||||||
(ImmutableArray.Create (CliType.OfManagedObject arrayAllocation))
|
(ImmutableArray.Create (CliType.ofManagedObject arrayAllocation))
|
||||||
None
|
None
|
||||||
with
|
with
|
||||||
| Ok s -> s
|
| Ok s -> s
|
||||||
@@ -210,11 +331,7 @@ module Program =
|
|||||||
{ state with
|
{ state with
|
||||||
ThreadState = state.ThreadState |> Map.add mainThread threadState
|
ThreadState = state.ThreadState |> Map.add mainThread threadState
|
||||||
}
|
}
|
||||||
|> IlMachineState.ensureTypeInitialised
|
|> IlMachineStateExecution.ensureTypeInitialised loggerFactory baseClassTypes mainThread mainTypeHandle
|
||||||
loggerFactory
|
|
||||||
baseClassTypes
|
|
||||||
mainThread
|
|
||||||
methodState.ExecutingMethod.DeclaringType
|
|
||||||
|
|
||||||
match init with
|
match init with
|
||||||
| WhatWeDid.SuspendedForClassInit -> failwith "TODO: suspended for class init"
|
| WhatWeDid.SuspendedForClassInit -> failwith "TODO: suspended for class init"
|
||||||
|
@@ -4,3 +4,5 @@ namespace WoofWare.PawPrint
|
|||||||
module internal Tuple =
|
module internal Tuple =
|
||||||
let withLeft<'a, 'b> (x : 'a) (y : 'b) : 'a * 'b = x, y
|
let withLeft<'a, 'b> (x : 'a) (y : 'b) : 'a * 'b = x, y
|
||||||
let withRight<'a, 'b> (y : 'b) (x : 'a) = x, y
|
let withRight<'a, 'b> (y : 'b) (x : 'a) = x, y
|
||||||
|
let lmap<'a, 'b, 'c> (f : 'a -> 'c) (x : 'a, y : 'b) : 'c * 'b = f x, y
|
||||||
|
let rmap<'a, 'b, 'c> (f : 'b -> 'c) (x : 'a, y : 'b) : 'a * 'c = x, f y
|
||||||
|
@@ -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,23 +13,20 @@ 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 : (string * CliType) 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.
|
||||||
let fields =
|
let fields =
|
||||||
@@ -46,7 +35,7 @@ module TypeHandleRegistry =
|
|||||||
"m_keepalive", CliType.ObjectRef None
|
"m_keepalive", CliType.ObjectRef None
|
||||||
// TODO: this is actually a System.IntPtr https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/coreclr/nativeaot/Runtime.Base/src/System/Primitives.cs#L339
|
// 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_cache", CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.Verbatim 0L))
|
||||||
"m_handle", CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.TypeHandlePtr handle))
|
"m_handle", CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.TypeHandlePtr def))
|
||||||
// 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)
|
"GenericParameterCountAny", CliType.Numeric (CliNumericType.Int32 -1)
|
||||||
@@ -56,9 +45,8 @@ module TypeHandleRegistry =
|
|||||||
|
|
||||||
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
|
||||||
|
@@ -9,21 +9,21 @@ type TypeInitState =
|
|||||||
|
|
||||||
/// Tracks the initialization state of types across assemblies. The string in the key is the FullName of the AssemblyName where the type comes from.
|
/// Tracks the initialization state of types across assemblies. The string in the key is the FullName of the AssemblyName where the type comes from.
|
||||||
// TODO: need a better solution than string here! AssemblyName didn't work, we had nonequal assembly names.
|
// TODO: need a better solution than string here! AssemblyName didn't work, we had nonequal assembly names.
|
||||||
type TypeInitTable = ImmutableDictionary<RuntimeConcreteType, TypeInitState>
|
type TypeInitTable = ImmutableDictionary<ConcreteTypeHandle, TypeInitState>
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module TypeInitTable =
|
module TypeInitTable =
|
||||||
let tryGet (ty : RuntimeConcreteType) (t : TypeInitTable) =
|
let tryGet (ty : ConcreteTypeHandle) (t : TypeInitTable) =
|
||||||
match t.TryGetValue ty with
|
match t.TryGetValue ty with
|
||||||
| true, v -> Some v
|
| true, v -> Some v
|
||||||
| false, _ -> None
|
| false, _ -> None
|
||||||
|
|
||||||
let beginInitialising (thread : ThreadId) (ty : RuntimeConcreteType) (t : TypeInitTable) : TypeInitTable =
|
let beginInitialising (thread : ThreadId) (ty : ConcreteTypeHandle) (t : TypeInitTable) : TypeInitTable =
|
||||||
match t.TryGetValue ty with
|
match t.TryGetValue ty with
|
||||||
| false, _ -> t.Add (ty, TypeInitState.InProgress thread)
|
| false, _ -> t.Add (ty, TypeInitState.InProgress thread)
|
||||||
| true, v -> failwith "Logic error: tried initialising a type which has already started initialising"
|
| true, v -> failwith "Logic error: tried initialising a type which has already started initialising"
|
||||||
|
|
||||||
let markInitialised (thread : ThreadId) (ty : RuntimeConcreteType) (t : TypeInitTable) : TypeInitTable =
|
let markInitialised (thread : ThreadId) (ty : ConcreteTypeHandle) (t : TypeInitTable) : TypeInitTable =
|
||||||
match t.TryGetValue ty with
|
match t.TryGetValue ty with
|
||||||
| false, _ -> failwith "Logic error: completing initialisation of a type which never started initialising"
|
| false, _ -> failwith "Logic error: completing initialisation of a type which never started initialising"
|
||||||
| true, TypeInitState.Initialized ->
|
| true, TypeInitState.Initialized ->
|
||||||
|
@@ -87,7 +87,11 @@ module internal UnaryConstIlOp =
|
|||||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int8 b)) currentThread
|
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int8 b)) currentThread
|
||||||
|> IlMachineState.advanceProgramCounter currentThread
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
|> Tuple.withRight WhatWeDid.Executed
|
|> Tuple.withRight WhatWeDid.Executed
|
||||||
| Br i -> failwith "TODO: Br unimplemented"
|
| Br i ->
|
||||||
|
state
|
||||||
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
|
|> IlMachineState.jumpProgramCounter currentThread i
|
||||||
|
|> Tuple.withRight WhatWeDid.Executed
|
||||||
| Br_s b ->
|
| Br_s b ->
|
||||||
state
|
state
|
||||||
|> IlMachineState.advanceProgramCounter currentThread
|
|> IlMachineState.advanceProgramCounter currentThread
|
||||||
@@ -208,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
|
||||||
@@ -236,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
|
||||||
@@ -347,7 +403,34 @@ 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
|
||||||
|
| _, _ -> 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
|
||||||
@@ -383,9 +466,111 @@ module internal UnaryConstIlOp =
|
|||||||
else
|
else
|
||||||
id
|
id
|
||||||
|> Tuple.withRight WhatWeDid.Executed
|
|> Tuple.withRight WhatWeDid.Executed
|
||||||
| Bgt_un_s b -> failwith "TODO: Bgt_un_s unimplemented"
|
| Bgt_un_s b ->
|
||||||
| Ble_un_s b -> failwith "TODO: Ble_un_s unimplemented"
|
let value2, state = IlMachineState.popEvalStack currentThread state
|
||||||
| Blt_un_s b -> failwith "TODO: Blt_un_s unimplemented"
|
let value1, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
|
||||||
|
let isGreaterThan =
|
||||||
|
match value1, value2 with
|
||||||
|
| EvalStackValue.Int32 v1, EvalStackValue.Int32 v2 ->
|
||||||
|
if v1 < 0 || v2 < 0 then
|
||||||
|
failwith "TODO"
|
||||||
|
|
||||||
|
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 ->
|
||||||
|
if v1 < 0L || v2 < 0L then
|
||||||
|
failwith "TODO"
|
||||||
|
|
||||||
|
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 b)
|
||||||
|
else
|
||||||
|
id
|
||||||
|
|> Tuple.withRight WhatWeDid.Executed
|
||||||
|
| Ble_un_s b ->
|
||||||
|
let value2, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
let value1, state = IlMachineState.popEvalStack currentThread state
|
||||||
|
|
||||||
|
let isLessEq =
|
||||||
|
match value1, value2 with
|
||||||
|
| EvalStackValue.Int32 v1, EvalStackValue.Int32 v2 ->
|
||||||
|
if v1 < 0 || v2 < 0 then
|
||||||
|
failwith "TODO"
|
||||||
|
|
||||||
|
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 ->
|
||||||
|
if v1 < 0L || v2 < 0L then
|
||||||
|
failwith "TODO"
|
||||||
|
|
||||||
|
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 isLessEq then
|
||||||
|
IlMachineState.jumpProgramCounter currentThread (int b)
|
||||||
|
else
|
||||||
|
id
|
||||||
|
|> Tuple.withRight WhatWeDid.Executed
|
||||||
|
| Blt_un_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 ->
|
||||||
|
if v1 < 0 || v2 < 0 then
|
||||||
|
failwith "TODO"
|
||||||
|
|
||||||
|
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 ->
|
||||||
|
if v1 < 0L || v2 < 0L then
|
||||||
|
failwith "TODO"
|
||||||
|
|
||||||
|
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 b)
|
||||||
|
else
|
||||||
|
id
|
||||||
|
|> Tuple.withRight WhatWeDid.Executed
|
||||||
| Bne_un i -> failwith "TODO: Bne_un unimplemented"
|
| Bne_un i -> failwith "TODO: Bne_un unimplemented"
|
||||||
| Bge_un i -> failwith "TODO: Bge_un unimplemented"
|
| Bge_un i -> failwith "TODO: Bge_un unimplemented"
|
||||||
| Bgt_un i -> failwith "TODO: Bgt_un unimplemented"
|
| Bgt_un i -> failwith "TODO: Bgt_un unimplemented"
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,14 @@
|
|||||||
namespace WoofWare.PawPrint
|
namespace WoofWare.PawPrint
|
||||||
|
|
||||||
|
open System.Collections.Immutable
|
||||||
open System.Reflection
|
open System.Reflection
|
||||||
|
open System.Reflection.Metadata
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||||
module internal UnaryStringTokenIlOp =
|
module internal UnaryStringTokenIlOp =
|
||||||
let execute
|
let execute
|
||||||
(baseClassTypes : BaseClassTypes<'a>)
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||||
(op : UnaryStringTokenIlOp)
|
(op : UnaryStringTokenIlOp)
|
||||||
(sh : StringToken)
|
(sh : StringToken)
|
||||||
(state : IlMachineState)
|
(state : IlMachineState)
|
||||||
@@ -47,16 +49,24 @@ module internal UnaryStringTokenIlOp =
|
|||||||
|
|
||||||
let fields =
|
let fields =
|
||||||
[
|
[
|
||||||
"_firstChar", CliType.OfChar state.ManagedHeap.StringArrayData.[dataAddr]
|
"_firstChar", CliType.ofChar state.ManagedHeap.StringArrayData.[dataAddr]
|
||||||
"_stringLength", CliType.Numeric (CliNumericType.Int32 stringToAllocate.Length)
|
"_stringLength", CliType.Numeric (CliNumericType.Int32 stringToAllocate.Length)
|
||||||
]
|
]
|
||||||
|
|
||||||
let addr, state =
|
let state, stringType =
|
||||||
IlMachineState.allocateManagedObject
|
TypeDefn.FromDefinition (
|
||||||
(baseClassTypes.String
|
ComparableTypeDefinitionHandle.Make baseClassTypes.String.TypeDefHandle,
|
||||||
|> TypeInfo.mapGeneric (fun _ _ -> failwith<unit> "string is not generic"))
|
baseClassTypes.Corelib.Name.FullName,
|
||||||
fields
|
SignatureTypeKind.Class
|
||||||
|
)
|
||||||
|
|> IlMachineState.concretizeType
|
||||||
|
baseClassTypes
|
||||||
state
|
state
|
||||||
|
baseClassTypes.Corelib.Name
|
||||||
|
ImmutableArray.Empty
|
||||||
|
ImmutableArray.Empty
|
||||||
|
|
||||||
|
let addr, state = IlMachineState.allocateManagedObject stringType fields state
|
||||||
|
|
||||||
addr,
|
addr,
|
||||||
{ state with
|
{ state with
|
||||||
|
@@ -7,18 +7,24 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Tuple.fs" />
|
<Compile Include="Tuple.fs" />
|
||||||
|
<Compile Include="ImmutableArray.fs" />
|
||||||
<Compile Include="Result.fs" />
|
<Compile Include="Result.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" />
|
||||||
<Compile Include="EvalStack.fs" />
|
<Compile Include="EvalStack.fs" />
|
||||||
|
<Compile Include="EvalStackValueComparisons.fs" />
|
||||||
|
<Compile Include="BinaryArithmetic.fs" />
|
||||||
<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
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1747467164,
|
"lastModified": 1755736253,
|
||||||
"narHash": "sha256-JBXbjJ0t6T6BbVc9iPVquQI9XSXCGQJD8c8SgnUquus=",
|
"narHash": "sha256-jlIQRypNhB1PcB1BE+expE4xZeJxzoAGr1iUbHQta8s=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "3fcbdcfc707e0aa42c541b7743e05820472bdaec",
|
"rev": "596312aae91421d6923f18cecce934a7d3bfd6b8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@@ -12,7 +12,10 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
flake-utils.lib.eachDefaultSystem (system: let
|
flake-utils.lib.eachDefaultSystem (system: let
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
config.allowUnfree = true;
|
||||||
|
};
|
||||||
pname = "WoofWare.PawPrint";
|
pname = "WoofWare.PawPrint";
|
||||||
dotnet-sdk = pkgs.dotnetCorePackages.sdk_9_0;
|
dotnet-sdk = pkgs.dotnetCorePackages.sdk_9_0;
|
||||||
dotnet-runtime = pkgs.dotnetCorePackages.runtime_9_0;
|
dotnet-runtime = pkgs.dotnetCorePackages.runtime_9_0;
|
||||||
@@ -67,6 +70,7 @@
|
|||||||
pkgs.nodePackages.markdown-link-check
|
pkgs.nodePackages.markdown-link-check
|
||||||
pkgs.shellcheck
|
pkgs.shellcheck
|
||||||
pkgs.xmlstarlet
|
pkgs.xmlstarlet
|
||||||
|
pkgs.claude-code
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
163
nix/deps.json
163
nix/deps.json
@@ -21,123 +21,133 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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",
|
||||||
"version": "7.0.1",
|
"version": "7.1.1",
|
||||||
"hash": "sha256-K85CIdxMeFSHEKZk6heIXp/oFjWAn7dBILKrw49pJUY="
|
"hash": "sha256-UMCEGKxQ4ytjmPuVpiNaAPbi3RQH9gqa61JJIUS/6hg="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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.15",
|
"version": "8.0.19",
|
||||||
"hash": "sha256-kqHuc031x46OTy7Mr2C86lLgXK83Uo6swvEjU8yZJ6c="
|
"hash": "sha256-QySX2bih1UvwmLcn9cy1j+RuvZZwbcFKggL5Y/WcTnw="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.AspNetCore.App.Runtime.linux-arm64",
|
"pname": "Microsoft.AspNetCore.App.Runtime.linux-arm64",
|
||||||
"version": "8.0.15",
|
"version": "8.0.19",
|
||||||
"hash": "sha256-9E6nEfEMGqAHAv3GFNOrz7JqZnqskM++6w92YMyDUT0="
|
"hash": "sha256-69S+Ywyc5U8PDsVkkCVvZdHOgWb6ZZ3+f4UA0MLOLFI="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.AspNetCore.App.Runtime.linux-x64",
|
"pname": "Microsoft.AspNetCore.App.Runtime.linux-x64",
|
||||||
"version": "8.0.15",
|
"version": "8.0.19",
|
||||||
"hash": "sha256-bAUGsjDxo5VHXo/IydzmjTixAhWh4lYpCmuUFpEo8ok="
|
"hash": "sha256-u50rdLuoADSDCthx2Fg+AnT192TalHhFrzFCfMgmTn4="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.AspNetCore.App.Runtime.osx-arm64",
|
"pname": "Microsoft.AspNetCore.App.Runtime.osx-arm64",
|
||||||
"version": "8.0.15",
|
"version": "8.0.19",
|
||||||
"hash": "sha256-fS/n2UQViU1TJqQbLWk3waURqPQWn1zd5ad5L7i8ZGM="
|
"hash": "sha256-QAKu2xD4UQ4+gX79ynNQ0aA07D+EW6Ke0jRiTZne8CY="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.AspNetCore.App.Runtime.osx-x64",
|
"pname": "Microsoft.AspNetCore.App.Runtime.osx-x64",
|
||||||
"version": "8.0.15",
|
"version": "8.0.19",
|
||||||
"hash": "sha256-IZBqDYmUVPuujfhKGwXnbsNhrMSDy/olXDjVWKGb5Y0="
|
"hash": "sha256-v5lzESMpodrH2grgk8ojA6BLDUfyxX5r6YY5Pgq61tA="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.CodeAnalysis.Analyzers",
|
"pname": "Microsoft.CodeAnalysis.Analyzers",
|
||||||
"version": "3.3.4",
|
"version": "3.11.0",
|
||||||
"hash": "sha256-qDzTfZBSCvAUu9gzq2k+LOvh6/eRvJ9++VCNck/ZpnE="
|
"hash": "sha256-hQ2l6E6PO4m7i+ZsfFlEx+93UsLPo4IY3wDkNG11/Sw="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.CodeAnalysis.Common",
|
"pname": "Microsoft.CodeAnalysis.Common",
|
||||||
"version": "4.8.0",
|
"version": "4.14.0",
|
||||||
"hash": "sha256-3IEinVTZq6/aajMVA8XTRO3LTIEt0PuhGyITGJLtqz4="
|
"hash": "sha256-ne/zxH3GqoGB4OemnE8oJElG5mai+/67ASaKqwmL2BE="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.CodeAnalysis.CSharp",
|
"pname": "Microsoft.CodeAnalysis.CSharp",
|
||||||
"version": "4.8.0",
|
"version": "4.14.0",
|
||||||
"hash": "sha256-MmOnXJvd/ezs5UPcqyGLnbZz5m+VedpRfB+kFZeeqkU="
|
"hash": "sha256-5Mzj3XkYYLkwDWh17r1NEXSbXwwWYQPiOmkSMlgo1JY="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.CodeCoverage",
|
"pname": "Microsoft.CodeCoverage",
|
||||||
"version": "17.13.0",
|
"version": "17.14.1",
|
||||||
"hash": "sha256-GKrIxeyQo5Az1mztfQgea1kGtJwonnNOrXK/0ULfu8o="
|
"hash": "sha256-f8QytG8GvRoP47rO2KEmnDLxIpyesaq26TFjDdW40Gs="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.Extensions.DependencyInjection.Abstractions",
|
"pname": "Microsoft.Extensions.DependencyInjection.Abstractions",
|
||||||
"version": "9.0.2",
|
"version": "9.0.2",
|
||||||
"hash": "sha256-WoTLgw/OlXhgN54Szip0Zpne7i/YTXwZ1ZLCPcHV6QM="
|
"hash": "sha256-WoTLgw/OlXhgN54Szip0Zpne7i/YTXwZ1ZLCPcHV6QM="
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.Extensions.DependencyInjection.Abstractions",
|
||||||
|
"version": "9.0.6",
|
||||||
|
"hash": "sha256-40rY38OwSqueIWr/KMvJX9u+vipN+AaRQ6eNCZLqrog="
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.Extensions.Logging.Abstractions",
|
"pname": "Microsoft.Extensions.Logging.Abstractions",
|
||||||
"version": "9.0.2",
|
"version": "9.0.2",
|
||||||
"hash": "sha256-mCxeuc+37XY0bmZR+z4p1hrZUdTZEg+FRcs/m6dAQDU="
|
"hash": "sha256-mCxeuc+37XY0bmZR+z4p1hrZUdTZEg+FRcs/m6dAQDU="
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.Extensions.Logging.Abstractions",
|
||||||
|
"version": "9.0.6",
|
||||||
|
"hash": "sha256-lhOMYT4+hua7SlgASGFBDhOkrNOsy35WyIxU3nVsD08="
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NET.Test.Sdk",
|
"pname": "Microsoft.NET.Test.Sdk",
|
||||||
"version": "17.13.0",
|
"version": "17.14.1",
|
||||||
"hash": "sha256-sc2wvyV8cGm1FrNP2GGHEI584RCvRPu15erYCsgw5QY="
|
"hash": "sha256-mZUzDFvFp7x1nKrcnRd0hhbNu5g8EQYt8SKnRgdhT/A="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Host.linux-arm64",
|
"pname": "Microsoft.NETCore.App.Host.linux-arm64",
|
||||||
"version": "8.0.15",
|
"version": "8.0.19",
|
||||||
"hash": "sha256-AKPshSm9pO8nG8ksSVfWOHUAv92B/dMS/QwE33rUiCI="
|
"hash": "sha256-R86Kqzi3FUuPZlgj3zNOObLAvXtnGrS2mxsBAxWIZrY="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Host.linux-x64",
|
"pname": "Microsoft.NETCore.App.Host.linux-x64",
|
||||||
"version": "8.0.15",
|
"version": "8.0.19",
|
||||||
"hash": "sha256-EmkuXpbpKVyghOw5JcVP2l3gQUebYHw+i+6romHTQyo="
|
"hash": "sha256-a9t/bX+WIKOu9q2R52b/hPGwOpkAgpYuP42SW2QXTak="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Host.osx-arm64",
|
"pname": "Microsoft.NETCore.App.Host.osx-arm64",
|
||||||
"version": "8.0.15",
|
"version": "8.0.19",
|
||||||
"hash": "sha256-fBucka/K+3dwVrPUU9RLTA4xKzJurWpaxI6OYH/F5WE="
|
"hash": "sha256-VaeGPR+6ApGNtQpEaky2rdUKd4X/Pp3xFGaSgUfGNiE="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Host.osx-x64",
|
"pname": "Microsoft.NETCore.App.Host.osx-x64",
|
||||||
"version": "8.0.15",
|
"version": "8.0.19",
|
||||||
"hash": "sha256-uZywV0js9dMNRWipBRO5M/l+7dJpPJWQu6wa5rV2sKA="
|
"hash": "sha256-hw7WMpTq7o544uSNvWUCIr6IRt5xZOo+eERMnwAbYyk="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Ref",
|
"pname": "Microsoft.NETCore.App.Ref",
|
||||||
"version": "8.0.15",
|
"version": "8.0.19",
|
||||||
"hash": "sha256-Y5/MxR0FnKnjgJ5p+7qk/VbabjCrA8bdR4Dkm20javU="
|
"hash": "sha256-4ymel0R1c0HrX0plAWubJPzev52y0Fsx1esyQ1R7bXc="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Runtime.linux-arm64",
|
"pname": "Microsoft.NETCore.App.Runtime.linux-arm64",
|
||||||
"version": "8.0.15",
|
"version": "8.0.19",
|
||||||
"hash": "sha256-RksQhngGt+9J4d18K0oZT6L9ZpoiJ4T7D+DEEfhAXP0="
|
"hash": "sha256-hqhpd8yT8bv05DhFTuMhfsaSISpLs3t4oM+R/ZkJH80="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Runtime.linux-x64",
|
"pname": "Microsoft.NETCore.App.Runtime.linux-x64",
|
||||||
"version": "8.0.15",
|
"version": "8.0.19",
|
||||||
"hash": "sha256-iO6pkXm4UouTsUpCeXjStckUxomy6pxrbOnM+zaR9a0="
|
"hash": "sha256-Ou51zUFTPESAAzP/z0+sLDAAXC54+oDlESBBT12M2lM="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Runtime.osx-arm64",
|
"pname": "Microsoft.NETCore.App.Runtime.osx-arm64",
|
||||||
"version": "8.0.15",
|
"version": "8.0.19",
|
||||||
"hash": "sha256-qUo70LnzZC+nVz1yvLg0R8u5gxuJawoMB8fzkcoCosw="
|
"hash": "sha256-IC/e8AmT9twcXwzFmXAelf4ctMbg4ancKPGrPLFMNn8="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Runtime.osx-x64",
|
"pname": "Microsoft.NETCore.App.Runtime.osx-x64",
|
||||||
"version": "8.0.15",
|
"version": "8.0.19",
|
||||||
"hash": "sha256-qraVbQtSG/gVKXAy2PHi1ua7SY6iasveIN2ayYFmE9U="
|
"hash": "sha256-Rb0z0PT/FHyk/Fgjj9W3WDpkDMKJoXR9DgHB1cJeZSA="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.Platforms",
|
"pname": "Microsoft.NETCore.Platforms",
|
||||||
@@ -161,43 +171,48 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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.ObjectModel",
|
"pname": "Microsoft.TestPlatform.AdapterUtilities",
|
||||||
"version": "17.12.0",
|
"version": "17.13.0",
|
||||||
"hash": "sha256-3XBHBSuCxggAIlHXmKNQNlPqMqwFlM952Av6RrLw1/w="
|
"hash": "sha256-Vr+3Tad/h/nk7f/5HMExn3HvCGFCarehFAzJSfCBaOc="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.TestPlatform.ObjectModel",
|
"pname": "Microsoft.TestPlatform.ObjectModel",
|
||||||
"version": "17.13.0",
|
"version": "17.13.0",
|
||||||
"hash": "sha256-6S0fjfj8vA+h6dJVNwLi6oZhYDO/I/6hBZaq2VTW+Uk="
|
"hash": "sha256-6S0fjfj8vA+h6dJVNwLi6oZhYDO/I/6hBZaq2VTW+Uk="
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.TestPlatform.ObjectModel",
|
||||||
|
"version": "17.14.1",
|
||||||
|
"hash": "sha256-QMf6O+w0IT+16Mrzo7wn+N20f3L1/mDhs/qjmEo1rYs="
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.TestPlatform.TestHost",
|
"pname": "Microsoft.TestPlatform.TestHost",
|
||||||
"version": "17.13.0",
|
"version": "17.14.1",
|
||||||
"hash": "sha256-L/CJzou7dhmShUgXq3aXL3CaLTJll17Q+JY2DBdUUpo="
|
"hash": "sha256-1cxHWcvHRD7orQ3EEEPPxVGEkTpxom1/zoICC9SInJs="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Myriad.Core",
|
"pname": "Myriad.Core",
|
||||||
@@ -216,18 +231,18 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Newtonsoft.Json",
|
"pname": "Newtonsoft.Json",
|
||||||
"version": "13.0.1",
|
"version": "13.0.3",
|
||||||
"hash": "sha256-K2tSVW4n4beRPzPu3rlVaBEMdGvWSv/3Q1fxaDh4Mjo="
|
"hash": "sha256-hy/BieY4qxBWVVsDqqOPaLy1QobiIapkbrESm6v2PHc="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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",
|
||||||
@@ -246,8 +261,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "System.Collections.Immutable",
|
"pname": "System.Collections.Immutable",
|
||||||
"version": "7.0.0",
|
"version": "9.0.0",
|
||||||
"hash": "sha256-9an2wbxue2qrtugYES9awshQg+KfJqajhnhs45kQIdk="
|
"hash": "sha256-+6q5VMeoc5bm4WFsoV6nBXA9dV5pa/O4yW+gOdi8yac="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "System.Diagnostics.DiagnosticSource",
|
"pname": "System.Diagnostics.DiagnosticSource",
|
||||||
@@ -281,19 +296,19 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "System.Reflection.Metadata",
|
"pname": "System.Reflection.Metadata",
|
||||||
"version": "7.0.0",
|
"version": "8.0.0",
|
||||||
"hash": "sha256-GwAKQhkhPBYTqmRdG9c9taqrKSKDwyUgOEhWLKxWNPI="
|
"hash": "sha256-dQGC30JauIDWNWXMrSNOJncVa1umR1sijazYwUDdSIE="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Reflection.Metadata",
|
||||||
|
"version": "9.0.0",
|
||||||
|
"hash": "sha256-avEWbcCh7XgpsSesnR3/SgxWi/6C5OxjR89Jf/SfRjQ="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "System.Runtime",
|
"pname": "System.Runtime",
|
||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
"hash": "sha256-R9T68AzS1PJJ7v6ARz9vo88pKL1dWqLOANg4pkQjkA0="
|
"hash": "sha256-R9T68AzS1PJJ7v6ARz9vo88pKL1dWqLOANg4pkQjkA0="
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"pname": "System.Runtime.CompilerServices.Unsafe",
|
|
||||||
"version": "6.0.0",
|
|
||||||
"hash": "sha256-bEG1PnDp7uKYz/OgLOWs3RWwQSVYm+AnPwVmAmcgp2I="
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"pname": "TypeEquality",
|
"pname": "TypeEquality",
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
|
Reference in New Issue
Block a user