mirror of
https://github.com/Smaug123/WoofWare.PawPrint
synced 2025-10-07 06:58:39 +00:00
Compare commits
25 Commits
more-ldind
...
castclass
Author | SHA1 | Date | |
---|---|---|---|
|
4816fcaef2 | ||
|
f39e7c07bf | ||
|
ad8e625678 | ||
|
0fc4335760 | ||
|
c79f775ce4 | ||
|
b5f4ed6dec | ||
|
af3e4f20f2 | ||
|
3d5667ebba | ||
|
4dbb737648 | ||
|
ddd6374c72 | ||
|
711bfd5aad | ||
|
84df17295b | ||
|
fcc778d7aa | ||
|
9905bbf436 | ||
|
641040509f | ||
|
e3b797705d | ||
|
7c636b61a7 | ||
|
4352bfa218 | ||
|
277f303431 | ||
|
c049313dd9 | ||
|
477cb9b3fb | ||
|
ca36bb3eba | ||
|
c859f88f52 | ||
|
1ebcd0b4f5 | ||
|
5cf0789439 |
76
CLAUDE.md
76
CLAUDE.md
@@ -81,7 +81,9 @@ 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.
|
||||
* 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.
|
||||
* 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
|
||||
|
||||
@@ -98,3 +100,77 @@ It strongly prefers to avoid special-casing to get around problems, but instead
|
||||
### 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.
|
||||
|
||||
## 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
|
||||
|
@@ -4,6 +4,8 @@
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<WarningsAsErrors>false</WarningsAsErrors>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -45,7 +45,10 @@ type DumpedAssembly =
|
||||
/// Dictionary of all type definitions in this assembly, keyed by their handle.
|
||||
/// </summary>
|
||||
TypeDefs :
|
||||
IReadOnlyDictionary<TypeDefinitionHandle, WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter>>
|
||||
IReadOnlyDictionary<
|
||||
TypeDefinitionHandle,
|
||||
WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
>
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of all type references in this assembly, keyed by their handle.
|
||||
@@ -64,7 +67,7 @@ type DumpedAssembly =
|
||||
Methods :
|
||||
IReadOnlyDictionary<
|
||||
MethodDefinitionHandle,
|
||||
WoofWare.PawPrint.MethodInfo<FakeUnit, WoofWare.PawPrint.GenericParameter>
|
||||
WoofWare.PawPrint.MethodInfo<FakeUnit, WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
>
|
||||
|
||||
/// <summary>
|
||||
@@ -75,7 +78,7 @@ type DumpedAssembly =
|
||||
/// <summary>
|
||||
/// Dictionary of all field definitions in this assembly, keyed by their handle.
|
||||
/// </summary>
|
||||
Fields : IReadOnlyDictionary<FieldDefinitionHandle, WoofWare.PawPrint.FieldInfo<FakeUnit>>
|
||||
Fields : IReadOnlyDictionary<FieldDefinitionHandle, WoofWare.PawPrint.FieldInfo<FakeUnit, TypeDefn>>
|
||||
|
||||
/// <summary>
|
||||
/// The entry point method of the assembly, if one exists.
|
||||
@@ -143,7 +146,10 @@ type DumpedAssembly =
|
||||
/// Internal lookup for type definitions by namespace and name.
|
||||
/// </summary>
|
||||
_TypeDefsLookup :
|
||||
ImmutableDictionary<string * string, WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter>>
|
||||
ImmutableDictionary<
|
||||
string * string,
|
||||
WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
>
|
||||
}
|
||||
|
||||
static member internal BuildExportedTypesLookup
|
||||
@@ -199,7 +205,7 @@ type DumpedAssembly =
|
||||
static member internal BuildTypeDefsLookup
|
||||
(logger : ILogger)
|
||||
(name : AssemblyName)
|
||||
(typeDefs : WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter> seq)
|
||||
(typeDefs : WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> seq)
|
||||
=
|
||||
let result = ImmutableDictionary.CreateBuilder ()
|
||||
let keys = HashSet ()
|
||||
@@ -230,7 +236,7 @@ type DumpedAssembly =
|
||||
member this.TypeDef
|
||||
(``namespace`` : string)
|
||||
(name : string)
|
||||
: WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter> option
|
||||
: WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> option
|
||||
=
|
||||
match this._TypeDefsLookup.TryGetValue ((``namespace``, name)) with
|
||||
| false, _ -> None
|
||||
@@ -247,13 +253,13 @@ type DumpedAssembly =
|
||||
|
||||
type TypeResolutionResult =
|
||||
| FirstLoadAssy of WoofWare.PawPrint.AssemblyReference
|
||||
| Resolved of DumpedAssembly * TypeInfo<TypeDefn>
|
||||
| Resolved of DumpedAssembly * TypeInfo<TypeDefn, TypeDefn>
|
||||
|
||||
override this.ToString () : string =
|
||||
match this with
|
||||
| TypeResolutionResult.FirstLoadAssy a -> $"FirstLoadAssy(%s{a.Name.FullName})"
|
||||
| TypeResolutionResult.Resolved (assy, ty) ->
|
||||
$"Resolved(%s{assy.Name.FullName}: {string<TypeInfo<TypeDefn>> ty})"
|
||||
$"Resolved(%s{assy.Name.FullName}: {string<TypeInfo<TypeDefn, TypeDefn>> ty})"
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Assembly =
|
||||
@@ -423,50 +429,52 @@ module Assembly =
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(referencedInAssembly : DumpedAssembly)
|
||||
(target : TypeRef)
|
||||
(genericArgs : ImmutableArray<TypeDefn> option)
|
||||
(genericArgs : ImmutableArray<TypeDefn>)
|
||||
: TypeResolutionResult
|
||||
=
|
||||
match target.ResolutionScope with
|
||||
| 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
|
||||
|
||||
match assemblies.TryGetValue assemblyName.FullName with
|
||||
| false, _ -> TypeResolutionResult.FirstLoadAssy assemblyRef
|
||||
| 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 =
|
||||
targetNs.TypeDefinitions
|
||||
|> Seq.choose (fun td ->
|
||||
let ty = assy.TypeDefs.[td]
|
||||
let targetType =
|
||||
targetNs.TypeDefinitions
|
||||
|> Seq.choose (fun td ->
|
||||
let ty = assy.TypeDefs.[td]
|
||||
|
||||
if ty.Name = target.Name && ty.Namespace = target.Namespace then
|
||||
Some ty
|
||||
else
|
||||
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]
|
||||
if ty.Name = target.Name && ty.Namespace = target.Namespace then
|
||||
Some ty
|
||||
else
|
||||
None
|
||||
)
|
||||
|> Seq.toList
|
||||
|
||||
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
|
||||
match targetType with
|
||||
| [ t ] ->
|
||||
let t = t |> TypeInfo.mapGeneric (fun _ param -> genericArgs.[param.SequenceNumber])
|
||||
|
||||
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}"
|
||||
|
||||
and resolveTypeFromName
|
||||
@@ -474,7 +482,7 @@ module Assembly =
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(ns : string option)
|
||||
(name : string)
|
||||
(genericArgs : ImmutableArray<TypeDefn> option)
|
||||
(genericArgs : ImmutableArray<TypeDefn>)
|
||||
: TypeResolutionResult
|
||||
=
|
||||
match ns with
|
||||
@@ -485,11 +493,7 @@ module Assembly =
|
||||
| Some typeDef ->
|
||||
let typeDef =
|
||||
typeDef
|
||||
|> TypeInfo.mapGeneric (fun _ param ->
|
||||
match genericArgs with
|
||||
| None -> failwith<TypeDefn> $"tried to resolve generic type {ns}.{name} but no generics in scope"
|
||||
| Some genericArgs -> genericArgs.[param.SequenceNumber]
|
||||
)
|
||||
|> TypeInfo.mapGeneric (fun _ param -> genericArgs.[param.SequenceNumber])
|
||||
|
||||
TypeResolutionResult.Resolved (assy, typeDef)
|
||||
| None ->
|
||||
@@ -506,7 +510,7 @@ module Assembly =
|
||||
(fromAssembly : DumpedAssembly)
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(ty : WoofWare.PawPrint.ExportedType)
|
||||
(genericArgs : ImmutableArray<TypeDefn> option)
|
||||
(genericArgs : ImmutableArray<TypeDefn>)
|
||||
: TypeResolutionResult
|
||||
=
|
||||
match ty.Data with
|
||||
@@ -532,7 +536,7 @@ module DumpedAssembly =
|
||||
| Some (BaseTypeInfo.TypeRef r) ->
|
||||
let assy = loadedAssemblies.[source.FullName]
|
||||
// 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 _ ->
|
||||
failwith
|
||||
"seems pretty unlikely that we could have constructed this object without loading its base type"
|
||||
|
@@ -20,9 +20,16 @@ module FakeUnit =
|
||||
type ConcreteType<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :> IComparable<'typeGeneric>> =
|
||||
private
|
||||
{
|
||||
/// Do not use this, because it's intended to be private; use the accessor `.Assembly : AssemblyName`
|
||||
/// instead.
|
||||
_AssemblyName : AssemblyName
|
||||
/// Do not use this, because it's intended to be private; use the accessor `.Definition` instead.
|
||||
_Definition : ComparableTypeDefinitionHandle
|
||||
/// Do not use this, because it's intended to be private; use the accessor `.Name` instead.
|
||||
_Name : string
|
||||
/// Do not use this, because it's intended to be private; use the accessor `.Namespace` instead.
|
||||
_Namespace : string
|
||||
/// Do not use this, because it's intended to be private; use the accessor `.Generics` instead.
|
||||
_Generics : 'typeGeneric list
|
||||
}
|
||||
|
||||
@@ -30,6 +37,7 @@ type ConcreteType<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :
|
||||
member this.Definition : ComparableTypeDefinitionHandle = this._Definition
|
||||
member this.Generics : 'typeGeneric list = this._Generics
|
||||
member this.Name = this._Name
|
||||
member this.Namespace = this._Namespace
|
||||
|
||||
override this.Equals (other : obj) : bool =
|
||||
match other with
|
||||
@@ -46,17 +54,19 @@ type ConcreteType<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :
|
||||
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
|
||||
if comp <> 0 then
|
||||
comp
|
||||
else
|
||||
|
||||
let comp =
|
||||
(this._Definition :> IComparable<ComparableTypeDefinitionHandle>).CompareTo other._Definition
|
||||
|
||||
if comp <> 0 then
|
||||
comp
|
||||
else
|
||||
|
||||
let thisGen = (this._Generics : 'typeGeneric list) :> IComparable<'typeGeneric list>
|
||||
thisGen.CompareTo other._Generics
|
||||
|
||||
interface IComparable with
|
||||
member this.CompareTo other =
|
||||
@@ -65,27 +75,28 @@ type ConcreteType<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :
|
||||
(this :> IComparable<ConcreteType<'typeGeneric>>).CompareTo other
|
||||
| _ -> failwith "bad comparison"
|
||||
|
||||
type RuntimeConcreteType = ConcreteType<TypeDefn>
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module ConcreteType =
|
||||
let make
|
||||
(assemblyName : AssemblyName)
|
||||
(ns : string)
|
||||
(name : string)
|
||||
(defn : TypeDefinitionHandle)
|
||||
(generics : TypeDefn list)
|
||||
: RuntimeConcreteType
|
||||
: ConcreteType<TypeDefn>
|
||||
=
|
||||
{
|
||||
_AssemblyName = assemblyName
|
||||
_Definition = ComparableTypeDefinitionHandle.Make defn
|
||||
_Name = name
|
||||
_Namespace = ns
|
||||
_Generics = generics
|
||||
}
|
||||
|
||||
let make'
|
||||
(assemblyName : AssemblyName)
|
||||
(defn : TypeDefinitionHandle)
|
||||
(ns : string)
|
||||
(name : string)
|
||||
(genericParamCount : int)
|
||||
: ConcreteType<FakeUnit>
|
||||
@@ -94,6 +105,7 @@ module ConcreteType =
|
||||
_AssemblyName = assemblyName
|
||||
_Definition = ComparableTypeDefinitionHandle.Make defn
|
||||
_Name = name
|
||||
_Namespace = ns
|
||||
_Generics = List.replicate genericParamCount FakeUnit.FakeUnit
|
||||
}
|
||||
|
||||
@@ -110,4 +122,5 @@ module ConcreteType =
|
||||
_Definition = x._Definition
|
||||
_Generics = generics
|
||||
_Name = x._Name
|
||||
_Namespace = x._Namespace
|
||||
}
|
||||
|
@@ -8,7 +8,8 @@ open System.Reflection.Metadata
|
||||
/// Represents detailed information about a field in a .NET assembly.
|
||||
/// This is a strongly-typed representation of FieldDefinition from System.Reflection.Metadata.
|
||||
/// </summary>
|
||||
type FieldInfo<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :> IComparable<'typeGeneric>> =
|
||||
type FieldInfo<'typeGeneric, 'fieldGeneric when 'typeGeneric : comparison and 'typeGeneric :> IComparable<'typeGeneric>>
|
||||
=
|
||||
{
|
||||
/// <summary>
|
||||
/// The metadata token handle that uniquely identifies this field in the assembly.
|
||||
@@ -26,7 +27,7 @@ type FieldInfo<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :> I
|
||||
/// <summary>
|
||||
/// The type of the field.
|
||||
/// </summary>
|
||||
Signature : TypeDefn
|
||||
Signature : 'fieldGeneric
|
||||
|
||||
/// <summary>
|
||||
/// The attributes applied to this field, including visibility, static/instance,
|
||||
@@ -45,16 +46,18 @@ module FieldInfo =
|
||||
(assembly : AssemblyName)
|
||||
(handle : FieldDefinitionHandle)
|
||||
(def : FieldDefinition)
|
||||
: FieldInfo<FakeUnit>
|
||||
: FieldInfo<FakeUnit, TypeDefn>
|
||||
=
|
||||
let name = mr.GetString def.Name
|
||||
let fieldSig = def.DecodeSignature (TypeDefn.typeProvider assembly, ())
|
||||
let declaringType = def.GetDeclaringType ()
|
||||
let typeGenerics = mr.GetTypeDefinition(declaringType).GetGenericParameters().Count
|
||||
let declaringTypeName = mr.GetString (mr.GetTypeDefinition(declaringType).Name)
|
||||
let decType = mr.GetTypeDefinition (declaringType)
|
||||
let declaringTypeNamespace = mr.GetString decType.Namespace
|
||||
let declaringTypeName = mr.GetString decType.Name
|
||||
|
||||
let declaringType =
|
||||
ConcreteType.make' assembly declaringType declaringTypeName typeGenerics
|
||||
ConcreteType.make' assembly declaringType declaringTypeNamespace declaringTypeName typeGenerics
|
||||
|
||||
{
|
||||
Name = name
|
||||
@@ -64,11 +67,11 @@ module FieldInfo =
|
||||
Attributes = def.Attributes
|
||||
}
|
||||
|
||||
let mapTypeGenerics<'a, 'b
|
||||
let mapTypeGenerics<'a, 'b, 'field
|
||||
when 'a :> IComparable<'a> and 'a : comparison and 'b :> IComparable<'b> and 'b : comparison>
|
||||
(f : int -> 'a -> 'b)
|
||||
(input : FieldInfo<'a>)
|
||||
: FieldInfo<'b>
|
||||
(input : FieldInfo<'a, 'field>)
|
||||
: FieldInfo<'b, 'field>
|
||||
=
|
||||
let declaringType = input.DeclaringType |> ConcreteType.mapGeneric f
|
||||
|
||||
|
@@ -78,6 +78,9 @@ type NullaryIlOp =
|
||||
| Not
|
||||
| Shr
|
||||
| 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
|
||||
| Conv_ovf_i
|
||||
| Conv_ovf_u
|
||||
@@ -123,6 +126,7 @@ type NullaryIlOp =
|
||||
| Localloc
|
||||
/// Dereferences the pointer on top of the stack, and pushes the target to the stack as a type O (object reference).
|
||||
| Ldind_ref
|
||||
/// Stores an object reference value at a supplied address.
|
||||
| Stind_ref
|
||||
| Stind_I
|
||||
| Stind_I1
|
||||
@@ -166,18 +170,25 @@ type NullaryIlOp =
|
||||
| Ldelem_u8
|
||||
| Ldelem_r4
|
||||
| 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
|
||||
/// Replaces the array element at a given index with the nativeint value on the evaluation stack.
|
||||
| Stelem_i
|
||||
/// Replaces the array element at a given index with the int8 value on the evaluation stack.
|
||||
| Stelem_i1
|
||||
| Stelem_u1
|
||||
/// Replaces the array element at a given index with the int16 value on the evaluation stack.
|
||||
| Stelem_i2
|
||||
| Stelem_u2
|
||||
/// Replaces the array element at a given index with the int32 value on the evaluation stack.
|
||||
| Stelem_i4
|
||||
| Stelem_u4
|
||||
/// Replaces the array element at a given index with the int64 value on the evaluation stack.
|
||||
| Stelem_i8
|
||||
| Stelem_u8
|
||||
| Stelem_r4
|
||||
| Stelem_r8
|
||||
/// Replaces the array element at a given index with the object ref value (type O) on the evaluation stack.
|
||||
| Stelem_ref
|
||||
| Cpblk
|
||||
| Initblk
|
||||
@@ -384,6 +395,7 @@ type UnaryConstIlOp =
|
||||
| Bge_un_s of int8
|
||||
| Bgt_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
|
||||
| Bne_un of int32
|
||||
| Bge_un of int32
|
||||
@@ -529,6 +541,7 @@ type UnaryMetadataTokenIlOp =
|
||||
| Newobj
|
||||
| Newarr
|
||||
| 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
|
||||
| Isinst
|
||||
/// Pop value from stack; pop object ref from stack; set specified field on that object to that value.
|
||||
|
@@ -117,7 +117,7 @@ type ExceptionRegion =
|
||||
| ExceptionRegionKind.Fault -> ExceptionRegion.Fault offset
|
||||
| _ -> raise (ArgumentOutOfRangeException ())
|
||||
|
||||
type MethodInstructions =
|
||||
type MethodInstructions<'methodVars> =
|
||||
{
|
||||
/// <summary>
|
||||
/// The IL instructions that compose the method body, along with their offset positions.
|
||||
@@ -137,12 +137,14 @@ type MethodInstructions =
|
||||
/// </summary>
|
||||
LocalsInit : bool
|
||||
|
||||
LocalVars : ImmutableArray<TypeDefn> option
|
||||
LocalVars : ImmutableArray<'methodVars> option
|
||||
|
||||
ExceptionRegions : ImmutableArray<ExceptionRegion>
|
||||
}
|
||||
|
||||
static member OnlyRet : MethodInstructions =
|
||||
[<RequireQualifiedAccess>]
|
||||
module MethodInstructions =
|
||||
let onlyRet () : MethodInstructions<'methodVars> =
|
||||
let op = IlOp.Nullary NullaryIlOp.Ret
|
||||
|
||||
{
|
||||
@@ -153,11 +155,20 @@ type MethodInstructions =
|
||||
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>
|
||||
/// Represents detailed information about a method in a .NET assembly.
|
||||
/// This is a strongly-typed representation of MethodDefinition from System.Reflection.Metadata.
|
||||
/// </summary>
|
||||
type MethodInfo<'typeGenerics, 'methodGenerics
|
||||
type MethodInfo<'typeGenerics, 'methodGenerics, 'methodVars
|
||||
when 'typeGenerics :> IComparable<'typeGenerics> and 'typeGenerics : comparison> =
|
||||
{
|
||||
/// <summary>
|
||||
@@ -178,7 +189,7 @@ type MethodInfo<'typeGenerics, 'methodGenerics
|
||||
///
|
||||
/// There may be no instructions for this method, e.g. if it's an `InternalCall`.
|
||||
/// </summary>
|
||||
Instructions : MethodInstructions option
|
||||
Instructions : MethodInstructions<'methodVars> option
|
||||
|
||||
/// <summary>
|
||||
/// The parameters of this method.
|
||||
@@ -193,7 +204,12 @@ type MethodInfo<'typeGenerics, 'methodGenerics
|
||||
/// <summary>
|
||||
/// The signature of the method, including return type and parameter types.
|
||||
/// </summary>
|
||||
Signature : TypeMethodSignature<TypeDefn>
|
||||
Signature : TypeMethodSignature<'methodVars>
|
||||
|
||||
/// <summary>
|
||||
/// The signature as it was read from assembly metadata.
|
||||
/// </summary>
|
||||
RawSignature : TypeMethodSignature<TypeDefn>
|
||||
|
||||
/// <summary>
|
||||
/// Custom attributes defined on the method. I've never yet seen one of these in practice.
|
||||
@@ -226,9 +242,12 @@ type MethodInfo<'typeGenerics, 'methodGenerics
|
||||
member this.IsPinvokeImpl : bool =
|
||||
this.MethodAttributes.HasFlag MethodAttributes.PinvokeImpl
|
||||
|
||||
member this.IsJITIntrinsic
|
||||
[<RequireQualifiedAccess>]
|
||||
module MethodInfo =
|
||||
let isJITIntrinsic
|
||||
(getMemberRefParentType : MemberReferenceHandle -> TypeRef)
|
||||
(methodDefs : IReadOnlyDictionary<MethodDefinitionHandle, MethodInfo<FakeUnit, GenericParameter>>)
|
||||
(methodDefs : IReadOnlyDictionary<MethodDefinitionHandle, MethodInfo<'a, 'b, 'c>>)
|
||||
(this : MethodInfo<'d, 'e, 'f>)
|
||||
: bool
|
||||
=
|
||||
this.CustomAttributes
|
||||
@@ -248,13 +267,11 @@ type MethodInfo<'typeGenerics, 'methodGenerics
|
||||
| con -> failwith $"TODO: {con}"
|
||||
)
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module MethodInfo =
|
||||
let mapTypeGenerics<'a, 'b, 'methodGen
|
||||
let mapTypeGenerics<'a, 'b, 'methodGen, 'vars
|
||||
when 'a :> IComparable<'a> and 'a : comparison and 'b : comparison and 'b :> IComparable<'b>>
|
||||
(f : int -> 'a -> 'b)
|
||||
(m : MethodInfo<'a, 'methodGen>)
|
||||
: MethodInfo<'b, 'methodGen>
|
||||
(m : MethodInfo<'a, 'methodGen, 'vars>)
|
||||
: MethodInfo<'b, 'methodGen, 'vars>
|
||||
=
|
||||
{
|
||||
DeclaringType = m.DeclaringType |> ConcreteType.mapGeneric f
|
||||
@@ -264,16 +281,17 @@ module MethodInfo =
|
||||
Parameters = m.Parameters
|
||||
Generics = m.Generics
|
||||
Signature = m.Signature
|
||||
RawSignature = m.RawSignature
|
||||
CustomAttributes = m.CustomAttributes
|
||||
MethodAttributes = m.MethodAttributes
|
||||
ImplAttributes = m.ImplAttributes
|
||||
IsStatic = m.IsStatic
|
||||
}
|
||||
|
||||
let mapMethodGenerics<'a, 'b, 'typeGen when 'typeGen :> IComparable<'typeGen> and 'typeGen : comparison>
|
||||
let mapMethodGenerics<'a, 'b, 'vars, 'typeGen when 'typeGen :> IComparable<'typeGen> and 'typeGen : comparison>
|
||||
(f : int -> 'a -> 'b)
|
||||
(m : MethodInfo<'typeGen, 'a>)
|
||||
: MethodInfo<'typeGen, 'b>
|
||||
(m : MethodInfo<'typeGen, 'a, 'vars>)
|
||||
: MethodInfo<'typeGen, 'b, 'vars>
|
||||
=
|
||||
{
|
||||
DeclaringType = m.DeclaringType
|
||||
@@ -283,12 +301,33 @@ module MethodInfo =
|
||||
Parameters = m.Parameters
|
||||
Generics = m.Generics |> Seq.mapi f |> ImmutableArray.CreateRange
|
||||
Signature = m.Signature
|
||||
RawSignature = m.RawSignature
|
||||
CustomAttributes = m.CustomAttributes
|
||||
MethodAttributes = m.MethodAttributes
|
||||
ImplAttributes = m.ImplAttributes
|
||||
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
|
||||
|
||||
@@ -637,7 +676,7 @@ module MethodInfo =
|
||||
(peReader : PEReader)
|
||||
(metadataReader : MetadataReader)
|
||||
(methodHandle : MethodDefinitionHandle)
|
||||
: MethodInfo<FakeUnit, GenericParameter> option
|
||||
: MethodInfo<FakeUnit, GenericParameter, TypeDefn> option
|
||||
=
|
||||
let logger = loggerFactory.CreateLogger "MethodInfo"
|
||||
let assemblyName = metadataReader.GetAssemblyDefinition().GetAssemblyName ()
|
||||
@@ -671,8 +710,11 @@ module MethodInfo =
|
||||
|
||||
let declaringType = methodDef.GetDeclaringType ()
|
||||
|
||||
let declaringTypeName =
|
||||
metadataReader.GetString (metadataReader.GetTypeDefinition(declaringType).Name)
|
||||
let declaringDefn = metadataReader.GetTypeDefinition (declaringType)
|
||||
|
||||
let declaringTypeNamespace = metadataReader.GetString declaringDefn.Namespace
|
||||
|
||||
let declaringTypeName = metadataReader.GetString declaringDefn.Name
|
||||
|
||||
let declaringTypeGenericParams =
|
||||
metadataReader.GetTypeDefinition(declaringType).GetGenericParameters().Count
|
||||
@@ -696,7 +738,12 @@ module MethodInfo =
|
||||
GenericParameter.readAll metadataReader (methodDef.GetGenericParameters ())
|
||||
|
||||
let declaringType =
|
||||
ConcreteType.make' assemblyName declaringType declaringTypeName declaringTypeGenericParams
|
||||
ConcreteType.make'
|
||||
assemblyName
|
||||
declaringType
|
||||
declaringTypeNamespace
|
||||
declaringTypeName
|
||||
declaringTypeGenericParams
|
||||
|
||||
{
|
||||
DeclaringType = declaringType
|
||||
@@ -706,6 +753,7 @@ module MethodInfo =
|
||||
Parameters = methodParams
|
||||
Generics = methodGenericParams
|
||||
Signature = typeSig
|
||||
RawSignature = typeSig
|
||||
MethodAttributes = methodDef.Attributes
|
||||
CustomAttributes = attrs
|
||||
IsStatic = not methodSig.Header.IsInstance
|
||||
@@ -715,7 +763,7 @@ module MethodInfo =
|
||||
|
||||
let rec resolveBaseType
|
||||
(methodGenerics : TypeDefn ImmutableArray option)
|
||||
(executingMethod : MethodInfo<TypeDefn, 'methodGen>)
|
||||
(executingMethod : MethodInfo<TypeDefn, 'methodGen, 'vars>)
|
||||
(td : TypeDefn)
|
||||
: ResolvedBaseType
|
||||
=
|
||||
|
@@ -15,6 +15,17 @@ type MethodSpec =
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
|
900
WoofWare.PawPrint.Domain/TypeConcretisation.fs
Normal file
900
WoofWare.PawPrint.Domain/TypeConcretisation.fs
Normal file
@@ -0,0 +1,900 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection
|
||||
open System.Reflection.Metadata
|
||||
|
||||
type ConcreteTypeHandle =
|
||||
| Concrete of int
|
||||
| Byref of ConcreteTypeHandle
|
||||
| Pointer of ConcreteTypeHandle
|
||||
|
||||
type AllConcreteTypes =
|
||||
{
|
||||
Mapping : Map<int, ConcreteType<ConcreteTypeHandle>>
|
||||
NextHandle : int
|
||||
}
|
||||
|
||||
static member Empty =
|
||||
{
|
||||
Mapping = Map.empty
|
||||
NextHandle = 0
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module AllConcreteTypes =
|
||||
let lookup (cth : ConcreteTypeHandle) (this : AllConcreteTypes) : ConcreteType<ConcreteTypeHandle> option =
|
||||
match cth with
|
||||
| ConcreteTypeHandle.Concrete id -> this.Mapping |> Map.tryFind id
|
||||
| ConcreteTypeHandle.Byref _ -> None // Byref types are not stored in the mapping
|
||||
| ConcreteTypeHandle.Pointer _ -> None // Pointer types are not stored in the mapping
|
||||
|
||||
let lookup' (ct : ConcreteType<ConcreteTypeHandle>) (this : AllConcreteTypes) : ConcreteTypeHandle option =
|
||||
this.Mapping
|
||||
|> Map.tryPick (fun id existingCt ->
|
||||
if
|
||||
existingCt._AssemblyName = ct._AssemblyName
|
||||
&& existingCt._Namespace = ct._Namespace
|
||||
&& existingCt._Name = ct._Name
|
||||
&& existingCt._Definition = ct._Definition
|
||||
&& existingCt._Generics = ct._Generics
|
||||
then
|
||||
Some (ConcreteTypeHandle.Concrete id)
|
||||
else
|
||||
None
|
||||
)
|
||||
|
||||
let findExistingConcreteType
|
||||
(concreteTypes : AllConcreteTypes)
|
||||
(asm : AssemblyName, ns : string, name : string, generics : ConcreteTypeHandle list as key)
|
||||
: ConcreteTypeHandle option
|
||||
=
|
||||
concreteTypes.Mapping
|
||||
|> Map.tryPick (fun id ct ->
|
||||
if
|
||||
ct.Assembly.FullName = asm.FullName
|
||||
&& ct.Namespace = ns
|
||||
&& ct.Name = name
|
||||
&& ct.Generics = generics
|
||||
then
|
||||
Some (ConcreteTypeHandle.Concrete id)
|
||||
else
|
||||
None
|
||||
)
|
||||
|
||||
/// `source` is AssemblyName * Namespace * Name
|
||||
let add (ct : ConcreteType<ConcreteTypeHandle>) (this : AllConcreteTypes) : ConcreteTypeHandle * AllConcreteTypes =
|
||||
let id = this.NextHandle
|
||||
let toRet = ConcreteTypeHandle.Concrete id
|
||||
|
||||
let newState =
|
||||
{
|
||||
NextHandle = this.NextHandle + 1
|
||||
Mapping = this.Mapping |> Map.add id ct
|
||||
}
|
||||
|
||||
toRet, newState
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module TypeConcretization =
|
||||
|
||||
type ConcretizationContext =
|
||||
{
|
||||
/// Types currently being processed (to detect cycles)
|
||||
InProgress : ImmutableDictionary<AssemblyName * TypeDefn, ConcreteTypeHandle>
|
||||
/// All concrete types created so far
|
||||
ConcreteTypes : AllConcreteTypes
|
||||
/// For resolving type references
|
||||
LoadedAssemblies : ImmutableDictionary<string, DumpedAssembly>
|
||||
BaseTypes : BaseClassTypes<DumpedAssembly>
|
||||
}
|
||||
|
||||
// Helper function to find existing types by assembly, namespace, name, and generics
|
||||
let private findExistingType
|
||||
(concreteTypes : AllConcreteTypes)
|
||||
(assembly : AssemblyName)
|
||||
(ns : string)
|
||||
(name : string)
|
||||
(generics : ConcreteTypeHandle list)
|
||||
: ConcreteTypeHandle option
|
||||
=
|
||||
concreteTypes.Mapping
|
||||
|> Map.tryPick (fun id ct ->
|
||||
if
|
||||
ct.Assembly.FullName = assembly.FullName
|
||||
&& ct.Namespace = ns
|
||||
&& ct.Name = name
|
||||
&& ct.Generics = generics
|
||||
then
|
||||
Some (ConcreteTypeHandle.Concrete id)
|
||||
else
|
||||
None
|
||||
)
|
||||
|
||||
// Helper function for primitive types (convenience wrapper)
|
||||
let private findExistingPrimitiveType
|
||||
(concreteTypes : AllConcreteTypes)
|
||||
(key : AssemblyName * string * string)
|
||||
: ConcreteTypeHandle option
|
||||
=
|
||||
let (asm, ns, name) = key
|
||||
findExistingType concreteTypes asm ns name []
|
||||
|
||||
// Helper function to create and add a ConcreteType to the context
|
||||
let private createAndAddConcreteType
|
||||
(ctx : ConcretizationContext)
|
||||
(assembly : AssemblyName)
|
||||
(definition : ComparableTypeDefinitionHandle)
|
||||
(ns : string)
|
||||
(name : string)
|
||||
(generics : ConcreteTypeHandle list)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
=
|
||||
let concreteType =
|
||||
{
|
||||
_AssemblyName = assembly
|
||||
_Definition = definition
|
||||
_Namespace = ns
|
||||
_Name = name
|
||||
_Generics = generics
|
||||
}
|
||||
|
||||
let handle, newConcreteTypes = AllConcreteTypes.add concreteType ctx.ConcreteTypes
|
||||
|
||||
let newCtx =
|
||||
{ ctx with
|
||||
ConcreteTypes = newConcreteTypes
|
||||
}
|
||||
|
||||
handle, newCtx
|
||||
|
||||
// Helper function for assembly loading with retry pattern
|
||||
let private loadAssemblyAndResolveTypeRef
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly)
|
||||
(ctx : ConcretizationContext)
|
||||
(currentAssembly : AssemblyName)
|
||||
(typeRef : TypeRef)
|
||||
: (DumpedAssembly * WoofWare.PawPrint.TypeInfo<_, _>) * ConcretizationContext
|
||||
=
|
||||
let currentAssy =
|
||||
match ctx.LoadedAssemblies.TryGetValue currentAssembly.FullName with
|
||||
| false, _ -> failwithf "Current assembly %s not loaded" currentAssembly.FullName
|
||||
| true, assy -> assy
|
||||
|
||||
// First try to resolve without loading new assemblies
|
||||
let resolutionResult =
|
||||
Assembly.resolveTypeRef ctx.LoadedAssemblies currentAssy typeRef ImmutableArray.Empty
|
||||
|
||||
match resolutionResult with
|
||||
| TypeResolutionResult.Resolved (targetAssy, typeInfo) -> (targetAssy, typeInfo), ctx
|
||||
| TypeResolutionResult.FirstLoadAssy assemblyRef ->
|
||||
// Need to load the assembly
|
||||
match typeRef.ResolutionScope with
|
||||
| TypeRefResolutionScope.Assembly assyRef ->
|
||||
let newAssemblies, loadedAssy = loadAssembly currentAssembly assyRef
|
||||
|
||||
let newCtx =
|
||||
{ ctx with
|
||||
LoadedAssemblies = newAssemblies
|
||||
}
|
||||
|
||||
// Now try to resolve again with the loaded assembly
|
||||
let resolutionResult2 =
|
||||
Assembly.resolveTypeRef newCtx.LoadedAssemblies currentAssy typeRef ImmutableArray.Empty
|
||||
|
||||
match resolutionResult2 with
|
||||
| TypeResolutionResult.Resolved (targetAssy, typeInfo) -> (targetAssy, typeInfo), newCtx
|
||||
| TypeResolutionResult.FirstLoadAssy _ ->
|
||||
failwithf "Failed to resolve type %s.%s after loading assembly" typeRef.Namespace typeRef.Name
|
||||
| _ -> failwith "Unexpected resolution scope"
|
||||
|
||||
let private concretizePrimitive
|
||||
(ctx : ConcretizationContext)
|
||||
(prim : PrimitiveType)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
=
|
||||
|
||||
// Get the TypeInfo for this primitive from BaseClassTypes
|
||||
let typeInfo =
|
||||
match prim with
|
||||
| PrimitiveType.Boolean -> ctx.BaseTypes.Boolean
|
||||
| PrimitiveType.Char -> ctx.BaseTypes.Char
|
||||
| PrimitiveType.SByte -> ctx.BaseTypes.SByte
|
||||
| PrimitiveType.Byte -> ctx.BaseTypes.Byte
|
||||
| PrimitiveType.Int16 -> ctx.BaseTypes.Int16
|
||||
| PrimitiveType.UInt16 -> ctx.BaseTypes.UInt16
|
||||
| PrimitiveType.Int32 -> ctx.BaseTypes.Int32
|
||||
| PrimitiveType.UInt32 -> ctx.BaseTypes.UInt32
|
||||
| PrimitiveType.Int64 -> ctx.BaseTypes.Int64
|
||||
| PrimitiveType.UInt64 -> ctx.BaseTypes.UInt64
|
||||
| PrimitiveType.Single -> ctx.BaseTypes.Single
|
||||
| PrimitiveType.Double -> ctx.BaseTypes.Double
|
||||
| PrimitiveType.String -> ctx.BaseTypes.String
|
||||
| PrimitiveType.Object -> ctx.BaseTypes.Object
|
||||
| PrimitiveType.TypedReference -> ctx.BaseTypes.TypedReference
|
||||
| PrimitiveType.IntPtr -> ctx.BaseTypes.IntPtr
|
||||
| PrimitiveType.UIntPtr -> ctx.BaseTypes.UIntPtr
|
||||
|
||||
// Check if we've already concretized this primitive type
|
||||
let key = (typeInfo.Assembly, typeInfo.Namespace, typeInfo.Name)
|
||||
|
||||
match findExistingPrimitiveType ctx.ConcreteTypes key with
|
||||
| Some handle -> handle, ctx
|
||||
| None ->
|
||||
// Create and add the concrete type (primitives have no generic arguments)
|
||||
createAndAddConcreteType
|
||||
ctx
|
||||
typeInfo.Assembly
|
||||
(ComparableTypeDefinitionHandle.Make typeInfo.TypeDefHandle)
|
||||
typeInfo.Namespace
|
||||
typeInfo.Name
|
||||
[] // Primitives have no generic parameters
|
||||
|
||||
let private concretizeArray
|
||||
(ctx : ConcretizationContext)
|
||||
(elementHandle : ConcreteTypeHandle)
|
||||
(shape : 'a)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
=
|
||||
|
||||
// Arrays are System.Array<T> where T is the element type
|
||||
let arrayTypeInfo = ctx.BaseTypes.Array
|
||||
|
||||
// Check if we've already concretized this array type
|
||||
match
|
||||
findExistingType
|
||||
ctx.ConcreteTypes
|
||||
arrayTypeInfo.Assembly
|
||||
arrayTypeInfo.Namespace
|
||||
arrayTypeInfo.Name
|
||||
[ elementHandle ]
|
||||
with
|
||||
| Some handle -> handle, ctx
|
||||
| None ->
|
||||
// Create and add the concrete array type
|
||||
createAndAddConcreteType
|
||||
ctx
|
||||
arrayTypeInfo.Assembly
|
||||
(ComparableTypeDefinitionHandle.Make arrayTypeInfo.TypeDefHandle)
|
||||
arrayTypeInfo.Namespace
|
||||
arrayTypeInfo.Name
|
||||
[ elementHandle ] // Array<T> has one generic parameter
|
||||
|
||||
let private concretizeOneDimArray
|
||||
(ctx : ConcretizationContext)
|
||||
(elementHandle : ConcreteTypeHandle)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
=
|
||||
|
||||
// One-dimensional arrays with lower bound 0 are also System.Array<T>
|
||||
// They just have different IL instructions for access
|
||||
let arrayTypeInfo = ctx.BaseTypes.Array
|
||||
|
||||
// Check if we've already concretized this array type
|
||||
match
|
||||
findExistingType
|
||||
ctx.ConcreteTypes
|
||||
arrayTypeInfo.Assembly
|
||||
arrayTypeInfo.Namespace
|
||||
arrayTypeInfo.Name
|
||||
[ elementHandle ]
|
||||
with
|
||||
| Some handle -> handle, ctx
|
||||
| None ->
|
||||
// Create and add the concrete array type
|
||||
createAndAddConcreteType
|
||||
ctx
|
||||
arrayTypeInfo.Assembly
|
||||
(ComparableTypeDefinitionHandle.Make arrayTypeInfo.TypeDefHandle)
|
||||
arrayTypeInfo.Namespace
|
||||
arrayTypeInfo.Name
|
||||
[ elementHandle ] // Array<T> has one generic parameter
|
||||
|
||||
let concretizeTypeDefinition
|
||||
(ctx : ConcretizationContext)
|
||||
(assemblyName : AssemblyName)
|
||||
(typeDefHandle : ComparableTypeDefinitionHandle)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
=
|
||||
|
||||
// Look up the type definition in the assembly
|
||||
let assembly =
|
||||
match ctx.LoadedAssemblies.TryGetValue assemblyName.FullName with
|
||||
| false, _ -> failwithf "Cannot concretize type definition - assembly %s not loaded" assemblyName.FullName
|
||||
| true, assy -> assy
|
||||
|
||||
let typeInfo = assembly.TypeDefs.[typeDefHandle.Get]
|
||||
|
||||
// Check if this type has generic parameters
|
||||
if not typeInfo.Generics.IsEmpty then
|
||||
failwithf
|
||||
"Cannot concretize open generic type %s.%s - it has %d generic parameters"
|
||||
typeInfo.Namespace
|
||||
typeInfo.Name
|
||||
typeInfo.Generics.Length
|
||||
|
||||
// Check if we've already concretized this type
|
||||
match findExistingType ctx.ConcreteTypes assemblyName typeInfo.Namespace typeInfo.Name [] with
|
||||
| Some handle -> handle, ctx
|
||||
| None ->
|
||||
// Create and add the concrete type (no generic arguments since it's not generic)
|
||||
createAndAddConcreteType ctx assemblyName typeDefHandle typeInfo.Namespace typeInfo.Name [] // No generic parameters
|
||||
|
||||
let private concretizeTypeReference
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly)
|
||||
(ctx : ConcretizationContext)
|
||||
(currentAssembly : AssemblyName)
|
||||
(typeRef : TypeRef)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
=
|
||||
// Use the helper to load assembly and resolve the type reference
|
||||
let (targetAssy, typeInfo), ctx =
|
||||
loadAssemblyAndResolveTypeRef loadAssembly ctx currentAssembly typeRef
|
||||
|
||||
// Check if this type has generic parameters
|
||||
if not typeInfo.Generics.IsEmpty then
|
||||
failwithf
|
||||
"Cannot concretize type reference to open generic type %s.%s - it has %d generic parameters"
|
||||
typeInfo.Namespace
|
||||
typeInfo.Name
|
||||
typeInfo.Generics.Length
|
||||
|
||||
// Create or find the concrete type
|
||||
concretizeTypeDefinition ctx targetAssy.Name (ComparableTypeDefinitionHandle.Make typeInfo.TypeDefHandle)
|
||||
|
||||
/// Concretize a type in a specific generic context
|
||||
let rec concretizeType
|
||||
(ctx : ConcretizationContext)
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
|
||||
(assembly : AssemblyName)
|
||||
(typeGenerics : ConcreteTypeHandle ImmutableArray)
|
||||
(methodGenerics : ConcreteTypeHandle ImmutableArray)
|
||||
(typeDefn : TypeDefn)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
=
|
||||
|
||||
let key = (assembly, typeDefn)
|
||||
|
||||
// Check if we're already processing this type (cycle detection)
|
||||
match ctx.InProgress.TryGetValue key with
|
||||
| true, handle -> handle, ctx
|
||||
| false, _ ->
|
||||
|
||||
match typeDefn with
|
||||
| TypeDefn.PrimitiveType prim -> concretizePrimitive ctx prim
|
||||
|
||||
| TypeDefn.Array (elementType, shape) ->
|
||||
let elementHandle, ctx =
|
||||
concretizeType ctx loadAssembly assembly typeGenerics methodGenerics elementType
|
||||
|
||||
concretizeArray ctx elementHandle shape
|
||||
|
||||
| TypeDefn.OneDimensionalArrayLowerBoundZero elementType ->
|
||||
let elementHandle, ctx =
|
||||
concretizeType ctx loadAssembly assembly typeGenerics methodGenerics elementType
|
||||
|
||||
concretizeOneDimArray ctx elementHandle
|
||||
|
||||
| TypeDefn.GenericTypeParameter index ->
|
||||
if index < typeGenerics.Length then
|
||||
typeGenerics.[index], ctx
|
||||
else
|
||||
failwithf "Generic type parameter %d out of range" index
|
||||
|
||||
| TypeDefn.GenericMethodParameter index ->
|
||||
if index < methodGenerics.Length then
|
||||
methodGenerics.[index], ctx
|
||||
else
|
||||
failwithf "Generic method parameter %d out of range" index
|
||||
|
||||
| TypeDefn.GenericInstantiation (genericDef, args) ->
|
||||
concretizeGenericInstantiation ctx loadAssembly assembly typeGenerics methodGenerics genericDef args
|
||||
|
||||
| TypeDefn.FromDefinition (typeDefHandle, targetAssembly, _) ->
|
||||
concretizeTypeDefinition ctx (AssemblyName targetAssembly) typeDefHandle
|
||||
|
||||
| TypeDefn.FromReference (typeRef, _) -> concretizeTypeReference loadAssembly ctx assembly typeRef
|
||||
|
||||
| TypeDefn.Byref elementType ->
|
||||
// Byref types are managed references to other types
|
||||
// First concretize the element type
|
||||
let elementHandle, ctx =
|
||||
concretizeType ctx loadAssembly assembly typeGenerics methodGenerics elementType
|
||||
|
||||
// Return a Byref constructor wrapping the element type
|
||||
ConcreteTypeHandle.Byref elementHandle, ctx
|
||||
|
||||
| TypeDefn.Pointer elementType ->
|
||||
// Pointer types are unmanaged pointers to other types
|
||||
// First concretize the element type
|
||||
let elementHandle, ctx =
|
||||
concretizeType ctx loadAssembly assembly typeGenerics methodGenerics elementType
|
||||
|
||||
// Return a Pointer constructor wrapping the element type
|
||||
ConcreteTypeHandle.Pointer elementHandle, ctx
|
||||
|
||||
| TypeDefn.Void ->
|
||||
// Void isn't a real runtime type, but we assign it a concretization entry anyway
|
||||
// Use System.Void from the base class types
|
||||
let voidTypeInfo = ctx.BaseTypes.Void
|
||||
|
||||
match
|
||||
findExistingType ctx.ConcreteTypes voidTypeInfo.Assembly voidTypeInfo.Namespace voidTypeInfo.Name []
|
||||
with
|
||||
| Some handle -> handle, ctx
|
||||
| None ->
|
||||
// Create and add the concrete Void type
|
||||
createAndAddConcreteType
|
||||
ctx
|
||||
voidTypeInfo.Assembly
|
||||
(ComparableTypeDefinitionHandle.Make voidTypeInfo.TypeDefHandle)
|
||||
voidTypeInfo.Namespace
|
||||
voidTypeInfo.Name
|
||||
[] // Void has no generic parameters
|
||||
|
||||
| _ -> failwithf "TODO: Concretization of %A not implemented" typeDefn
|
||||
|
||||
and private concretizeGenericInstantiation
|
||||
(ctx : ConcretizationContext)
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
|
||||
(assembly : AssemblyName)
|
||||
(typeGenerics : ConcreteTypeHandle ImmutableArray)
|
||||
(methodGenerics : ConcreteTypeHandle ImmutableArray)
|
||||
(genericDef : TypeDefn)
|
||||
(args : ImmutableArray<TypeDefn>)
|
||||
: ConcreteTypeHandle * ConcretizationContext
|
||||
=
|
||||
// First, concretize all type arguments
|
||||
let argHandles, ctxAfterArgs =
|
||||
args
|
||||
|> Seq.fold
|
||||
(fun (handles, ctx) arg ->
|
||||
let handle, ctx =
|
||||
concretizeType ctx loadAssembly assembly typeGenerics methodGenerics arg
|
||||
|
||||
handle :: handles, ctx
|
||||
)
|
||||
([], ctx)
|
||||
|
||||
let argHandles = argHandles |> List.rev
|
||||
|
||||
// Get the base type definition
|
||||
let baseAssembly, baseTypeDefHandle, baseNamespace, baseName, ctxAfterArgs =
|
||||
match genericDef with
|
||||
| FromDefinition (handle, assy, _) ->
|
||||
// Look up the type definition to get namespace and name
|
||||
let currentAssy = ctxAfterArgs.LoadedAssemblies.[AssemblyName(assy).FullName]
|
||||
let typeDef = currentAssy.TypeDefs.[handle.Get]
|
||||
AssemblyName assy, handle, typeDef.Namespace, typeDef.Name, ctxAfterArgs
|
||||
| FromReference (typeRef, _) ->
|
||||
// For a type reference, we need to find where the type is defined
|
||||
// We're looking for the generic type definition, not an instantiation
|
||||
let currentAssy = ctxAfterArgs.LoadedAssemblies.[assembly.FullName]
|
||||
|
||||
// Helper to find the type definition without instantiating generics
|
||||
let rec findTypeDefinition (assy : DumpedAssembly) (ns : string) (name : string) =
|
||||
// First check if it's defined in this assembly
|
||||
match assy.TypeDef ns name with
|
||||
| Some typeDef -> Some (assy, typeDef)
|
||||
| None ->
|
||||
// Check if it's exported/forwarded
|
||||
match assy.ExportedType (Some ns) name with
|
||||
| Some export ->
|
||||
match export.Data with
|
||||
| NonForwarded _ -> None // Shouldn't happen
|
||||
| ForwardsTo assyRef ->
|
||||
let forwardedAssy = assy.AssemblyReferences.[assyRef]
|
||||
|
||||
match ctxAfterArgs.LoadedAssemblies.TryGetValue forwardedAssy.Name.FullName with
|
||||
| true, targetAssy -> findTypeDefinition targetAssy ns name
|
||||
| false, _ -> None // Assembly not loaded yet
|
||||
| None -> None
|
||||
|
||||
// First try to resolve without loading new assemblies
|
||||
match typeRef.ResolutionScope with
|
||||
| TypeRefResolutionScope.Assembly assyRef ->
|
||||
let targetAssyRef = currentAssy.AssemblyReferences.[assyRef]
|
||||
let targetAssyName = targetAssyRef.Name
|
||||
|
||||
match ctxAfterArgs.LoadedAssemblies.TryGetValue targetAssyName.FullName with
|
||||
| true, targetAssy ->
|
||||
// Try to find the type
|
||||
match findTypeDefinition targetAssy typeRef.Namespace typeRef.Name with
|
||||
| Some (foundAssy, typeDef) ->
|
||||
foundAssy.Name,
|
||||
ComparableTypeDefinitionHandle.Make typeDef.TypeDefHandle,
|
||||
typeDef.Namespace,
|
||||
typeDef.Name,
|
||||
ctxAfterArgs
|
||||
| None ->
|
||||
failwithf
|
||||
"Type %s.%s not found in assembly %s or its forwards"
|
||||
typeRef.Namespace
|
||||
typeRef.Name
|
||||
targetAssyName.FullName
|
||||
|
||||
| false, _ ->
|
||||
// Need to load the assembly
|
||||
let newAssemblies, loadedAssy = loadAssembly assembly assyRef
|
||||
|
||||
let ctxWithNewAssy =
|
||||
{ ctxAfterArgs with
|
||||
LoadedAssemblies = newAssemblies
|
||||
}
|
||||
|
||||
// Now try to find the type in the loaded assembly
|
||||
match findTypeDefinition loadedAssy typeRef.Namespace typeRef.Name with
|
||||
| Some (foundAssy, typeDef) ->
|
||||
foundAssy.Name,
|
||||
ComparableTypeDefinitionHandle.Make typeDef.TypeDefHandle,
|
||||
typeDef.Namespace,
|
||||
typeDef.Name,
|
||||
ctxWithNewAssy
|
||||
| None ->
|
||||
failwithf
|
||||
"Type %s.%s not found in loaded assembly %s or its forwards"
|
||||
typeRef.Namespace
|
||||
typeRef.Name
|
||||
loadedAssy.Name.FullName
|
||||
|
||||
| _ -> failwith "TODO: handle other resolution scopes for type refs in generic instantiation"
|
||||
| _ -> failwithf "Generic instantiation of %A not supported" genericDef
|
||||
|
||||
// Check if this exact generic instantiation already exists
|
||||
match findExistingType ctxAfterArgs.ConcreteTypes baseAssembly baseNamespace baseName argHandles with
|
||||
| Some existingHandle ->
|
||||
// Type already exists, return it
|
||||
existingHandle, ctxAfterArgs
|
||||
| None ->
|
||||
// Need to handle cycles: check if we're already processing this type
|
||||
let typeDefnKey = (assembly, GenericInstantiation (genericDef, args))
|
||||
|
||||
match ctxAfterArgs.InProgress.TryGetValue typeDefnKey with
|
||||
| true, handle ->
|
||||
// We're in a cycle, return the in-progress handle
|
||||
handle, ctxAfterArgs
|
||||
| false, _ ->
|
||||
// Pre-allocate a handle for this type to handle cycles
|
||||
let tempId = ctxAfterArgs.ConcreteTypes.NextHandle
|
||||
let tempHandle = ConcreteTypeHandle.Concrete tempId
|
||||
|
||||
// Create the concrete type
|
||||
let concreteType =
|
||||
{
|
||||
_AssemblyName = baseAssembly
|
||||
_Definition = baseTypeDefHandle
|
||||
_Namespace = baseNamespace
|
||||
_Name = baseName
|
||||
_Generics = argHandles
|
||||
}
|
||||
|
||||
// Add to the concrete types and mark as in progress
|
||||
let newCtx =
|
||||
{ ctxAfterArgs with
|
||||
ConcreteTypes =
|
||||
{ ctxAfterArgs.ConcreteTypes with
|
||||
NextHandle = ctxAfterArgs.ConcreteTypes.NextHandle + 1
|
||||
Mapping = ctxAfterArgs.ConcreteTypes.Mapping |> Map.add tempId concreteType
|
||||
}
|
||||
InProgress = ctxAfterArgs.InProgress.SetItem (typeDefnKey, tempHandle)
|
||||
}
|
||||
|
||||
// Remove from in-progress when done
|
||||
let finalCtx =
|
||||
{ newCtx with
|
||||
InProgress = newCtx.InProgress.Remove typeDefnKey
|
||||
}
|
||||
|
||||
tempHandle, finalCtx
|
||||
|
||||
/// High-level API for concretizing types
|
||||
[<RequireQualifiedAccess>]
|
||||
module Concretization =
|
||||
|
||||
/// Helper to concretize an array of types
|
||||
let private concretizeTypeArray
|
||||
(ctx : TypeConcretization.ConcretizationContext)
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
|
||||
(assembly : AssemblyName)
|
||||
(typeArgs : ConcreteTypeHandle ImmutableArray)
|
||||
(methodArgs : ConcreteTypeHandle ImmutableArray)
|
||||
(types : ImmutableArray<TypeDefn>)
|
||||
: ImmutableArray<ConcreteTypeHandle> * TypeConcretization.ConcretizationContext
|
||||
=
|
||||
|
||||
let handles = ImmutableArray.CreateBuilder (types.Length)
|
||||
let mutable ctx = ctx
|
||||
|
||||
for i = 0 to types.Length - 1 do
|
||||
let handle, newCtx =
|
||||
TypeConcretization.concretizeType ctx loadAssembly assembly typeArgs methodArgs types.[i]
|
||||
|
||||
handles.Add handle
|
||||
ctx <- newCtx
|
||||
|
||||
handles.ToImmutable (), ctx
|
||||
|
||||
/// Helper to concretize a method signature
|
||||
let private concretizeMethodSignature
|
||||
(ctx : TypeConcretization.ConcretizationContext)
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
|
||||
(assembly : AssemblyName)
|
||||
(typeArgs : ConcreteTypeHandle ImmutableArray)
|
||||
(methodArgs : ConcreteTypeHandle ImmutableArray)
|
||||
(signature : TypeMethodSignature<TypeDefn>)
|
||||
: TypeMethodSignature<ConcreteTypeHandle> * TypeConcretization.ConcretizationContext
|
||||
=
|
||||
|
||||
// Concretize return type
|
||||
let returnHandle, ctx =
|
||||
TypeConcretization.concretizeType ctx loadAssembly assembly typeArgs methodArgs signature.ReturnType
|
||||
|
||||
// Concretize parameter types
|
||||
let paramHandles = ResizeArray<ConcreteTypeHandle> ()
|
||||
let mutable ctx = ctx
|
||||
|
||||
for paramType in signature.ParameterTypes do
|
||||
let handle, newCtx =
|
||||
TypeConcretization.concretizeType ctx loadAssembly assembly typeArgs methodArgs paramType
|
||||
|
||||
paramHandles.Add (handle)
|
||||
ctx <- newCtx
|
||||
|
||||
let newSignature =
|
||||
{
|
||||
Header = signature.Header
|
||||
ReturnType = returnHandle
|
||||
ParameterTypes = paramHandles |> Seq.toList
|
||||
GenericParameterCount = signature.GenericParameterCount
|
||||
RequiredParameterCount = signature.RequiredParameterCount
|
||||
}
|
||||
|
||||
newSignature, ctx
|
||||
|
||||
/// Helper to ensure base type assembly is loaded
|
||||
let rec private ensureBaseTypeAssembliesLoaded
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(assyName : AssemblyName)
|
||||
(baseTypeInfo : BaseTypeInfo option)
|
||||
: ImmutableDictionary<string, DumpedAssembly>
|
||||
=
|
||||
match baseTypeInfo with
|
||||
| None -> assemblies
|
||||
| Some (BaseTypeInfo.TypeRef r) ->
|
||||
let assy = assemblies.[assyName.FullName]
|
||||
let typeRef = assy.TypeRefs.[r]
|
||||
|
||||
match typeRef.ResolutionScope with
|
||||
| TypeRefResolutionScope.Assembly assyRef ->
|
||||
let targetAssyRef = assy.AssemblyReferences.[assyRef]
|
||||
|
||||
match assemblies.TryGetValue targetAssyRef.Name.FullName with
|
||||
| true, _ -> assemblies
|
||||
| false, _ ->
|
||||
// Need to load the assembly - pass the assembly that contains the reference
|
||||
let newAssemblies, _ = loadAssembly assy.Name assyRef
|
||||
newAssemblies
|
||||
| _ -> assemblies
|
||||
| Some (BaseTypeInfo.TypeDef _)
|
||||
| Some (BaseTypeInfo.ForeignAssemblyType _)
|
||||
| Some (BaseTypeInfo.TypeSpec _) -> assemblies
|
||||
|
||||
/// Concretize a method's signature and body
|
||||
let concretizeMethod
|
||||
(ctx : AllConcreteTypes)
|
||||
(loadAssembly :
|
||||
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(baseTypes : BaseClassTypes<DumpedAssembly>)
|
||||
(method : WoofWare.PawPrint.MethodInfo<TypeDefn, WoofWare.PawPrint.GenericParameter, TypeDefn>)
|
||||
(typeArgs : ConcreteTypeHandle ImmutableArray)
|
||||
(methodArgs : ConcreteTypeHandle ImmutableArray)
|
||||
: WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle> *
|
||||
AllConcreteTypes *
|
||||
ImmutableDictionary<string, DumpedAssembly>
|
||||
=
|
||||
|
||||
// Ensure base type assemblies are loaded for the declaring type
|
||||
let assemblies =
|
||||
let assy = assemblies.[method.DeclaringType._AssemblyName.FullName]
|
||||
let typeDef = assy.TypeDefs.[method.DeclaringType._Definition.Get]
|
||||
ensureBaseTypeAssembliesLoaded loadAssembly assemblies assy.Name typeDef.BaseType
|
||||
|
||||
let concCtx =
|
||||
{
|
||||
TypeConcretization.ConcretizationContext.InProgress = ImmutableDictionary.Empty
|
||||
TypeConcretization.ConcretizationContext.ConcreteTypes = ctx
|
||||
TypeConcretization.ConcretizationContext.LoadedAssemblies = assemblies
|
||||
TypeConcretization.ConcretizationContext.BaseTypes = baseTypes
|
||||
}
|
||||
|
||||
// First, we need to create a TypeDefn for the declaring type with its generics instantiated
|
||||
let declaringTypeDefn =
|
||||
if method.DeclaringType._Generics.IsEmpty then
|
||||
// Non-generic type - determine the SignatureTypeKind
|
||||
let assy = concCtx.LoadedAssemblies.[method.DeclaringType._AssemblyName.FullName]
|
||||
let arg = assy.TypeDefs.[method.DeclaringType._Definition.Get]
|
||||
|
||||
let baseType =
|
||||
arg.BaseType
|
||||
|> DumpedAssembly.resolveBaseType baseTypes concCtx.LoadedAssemblies assy.Name
|
||||
|
||||
let signatureTypeKind =
|
||||
match baseType with
|
||||
| ResolvedBaseType.Enum
|
||||
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
||||
| ResolvedBaseType.Object
|
||||
| ResolvedBaseType.Delegate -> SignatureTypeKind.Class
|
||||
|
||||
TypeDefn.FromDefinition (
|
||||
method.DeclaringType._Definition,
|
||||
method.DeclaringType._AssemblyName.FullName,
|
||||
signatureTypeKind
|
||||
)
|
||||
else
|
||||
// Generic type - create a GenericInstantiation
|
||||
let assy = concCtx.LoadedAssemblies.[method.DeclaringType._AssemblyName.FullName]
|
||||
let arg = assy.TypeDefs.[method.DeclaringType._Definition.Get]
|
||||
|
||||
let baseTypeResolved =
|
||||
arg.BaseType
|
||||
|> DumpedAssembly.resolveBaseType baseTypes concCtx.LoadedAssemblies assy.Name
|
||||
|
||||
let signatureTypeKind =
|
||||
match baseTypeResolved with
|
||||
| ResolvedBaseType.Enum
|
||||
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
||||
| ResolvedBaseType.Object
|
||||
| ResolvedBaseType.Delegate -> SignatureTypeKind.Class
|
||||
|
||||
let baseType =
|
||||
TypeDefn.FromDefinition (
|
||||
method.DeclaringType._Definition,
|
||||
method.DeclaringType._AssemblyName.FullName,
|
||||
signatureTypeKind
|
||||
)
|
||||
|
||||
let genericArgsLength = method.DeclaringType.Generics.Length
|
||||
|
||||
if genericArgsLength > typeArgs.Length then
|
||||
failwithf
|
||||
"Method declaring type expects %d generic arguments but only %d provided"
|
||||
genericArgsLength
|
||||
typeArgs.Length
|
||||
|
||||
let genericArgs =
|
||||
typeArgs.Slice (0, genericArgsLength)
|
||||
|> Seq.mapi (fun i _ -> TypeDefn.GenericTypeParameter i)
|
||||
|> ImmutableArray.CreateRange
|
||||
|
||||
TypeDefn.GenericInstantiation (baseType, genericArgs)
|
||||
|
||||
// Concretize the declaring type
|
||||
let declaringHandle, concCtx =
|
||||
TypeConcretization.concretizeType
|
||||
concCtx
|
||||
loadAssembly
|
||||
method.DeclaringType._AssemblyName
|
||||
typeArgs
|
||||
methodArgs
|
||||
declaringTypeDefn
|
||||
|
||||
// Look up the concretized declaring type
|
||||
let concretizedDeclaringType =
|
||||
AllConcreteTypes.lookup declaringHandle concCtx.ConcreteTypes |> Option.get
|
||||
|
||||
// Concretize signature
|
||||
let signature, concCtx =
|
||||
concretizeMethodSignature
|
||||
concCtx
|
||||
loadAssembly
|
||||
method.DeclaringType._AssemblyName
|
||||
typeArgs
|
||||
methodArgs
|
||||
method.Signature
|
||||
|
||||
// Concretize local variables
|
||||
let instructions, concCtx2 =
|
||||
match method.Instructions with
|
||||
| None -> None, concCtx
|
||||
| Some instr ->
|
||||
let locals, updatedCtx =
|
||||
match instr.LocalVars with
|
||||
| None -> None, concCtx
|
||||
| Some vars ->
|
||||
let handles, ctx =
|
||||
concretizeTypeArray
|
||||
concCtx
|
||||
loadAssembly
|
||||
method.DeclaringType._AssemblyName
|
||||
typeArgs
|
||||
methodArgs
|
||||
vars
|
||||
|
||||
Some handles, ctx
|
||||
|
||||
Some (MethodInstructions.setLocalVars locals instr), updatedCtx
|
||||
|
||||
// Map generics to handles
|
||||
let genericHandles =
|
||||
method.Generics
|
||||
|> Seq.mapi (fun i _ -> methodArgs.[i])
|
||||
|> ImmutableArray.CreateRange
|
||||
|
||||
let concretizedMethod : MethodInfo<_, _, ConcreteTypeHandle> =
|
||||
{
|
||||
DeclaringType = concretizedDeclaringType
|
||||
Handle = method.Handle
|
||||
Name = method.Name
|
||||
Instructions = instructions
|
||||
Parameters = method.Parameters
|
||||
Generics = genericHandles
|
||||
Signature = signature
|
||||
RawSignature = method.RawSignature
|
||||
CustomAttributes = method.CustomAttributes
|
||||
MethodAttributes = method.MethodAttributes
|
||||
ImplAttributes = method.ImplAttributes
|
||||
IsStatic = method.IsStatic
|
||||
}
|
||||
|
||||
concretizedMethod, concCtx2.ConcreteTypes, concCtx2.LoadedAssemblies
|
||||
|
||||
let rec concreteHandleToTypeDefn
|
||||
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||
(handle : ConcreteTypeHandle)
|
||||
(concreteTypes : AllConcreteTypes)
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
: TypeDefn
|
||||
=
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Byref elementHandle ->
|
||||
let elementType =
|
||||
concreteHandleToTypeDefn baseClassTypes elementHandle concreteTypes assemblies
|
||||
|
||||
TypeDefn.Byref elementType
|
||||
| ConcreteTypeHandle.Pointer elementHandle ->
|
||||
let elementType =
|
||||
concreteHandleToTypeDefn baseClassTypes elementHandle concreteTypes assemblies
|
||||
|
||||
TypeDefn.Pointer elementType
|
||||
| ConcreteTypeHandle.Concrete _ ->
|
||||
match AllConcreteTypes.lookup handle concreteTypes with
|
||||
| None -> failwith "Logic error: handle not found"
|
||||
| Some concreteType ->
|
||||
|
||||
// Determine SignatureTypeKind
|
||||
let assy = assemblies.[concreteType.Assembly.FullName]
|
||||
let typeDef = assy.TypeDefs.[concreteType.Definition.Get]
|
||||
// Determine SignatureTypeKind from base type
|
||||
let baseType =
|
||||
typeDef.BaseType
|
||||
|> DumpedAssembly.resolveBaseType baseClassTypes assemblies assy.Name
|
||||
|
||||
let signatureTypeKind =
|
||||
match baseType with
|
||||
| ResolvedBaseType.Enum
|
||||
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
||||
| ResolvedBaseType.Object
|
||||
| ResolvedBaseType.Delegate -> SignatureTypeKind.Class
|
||||
|
||||
if concreteType.Generics.IsEmpty then
|
||||
TypeDefn.FromDefinition (concreteType.Definition, concreteType.Assembly.FullName, signatureTypeKind)
|
||||
else
|
||||
// Recursively convert generic arguments
|
||||
let genericArgs =
|
||||
concreteType.Generics
|
||||
|> List.map (fun h -> concreteHandleToTypeDefn baseClassTypes h concreteTypes assemblies)
|
||||
|> ImmutableArray.CreateRange
|
||||
|
||||
let baseDef =
|
||||
TypeDefn.FromDefinition (concreteType.Definition, concreteType.Assembly.FullName, signatureTypeKind)
|
||||
|
||||
TypeDefn.GenericInstantiation (baseDef, genericArgs)
|
@@ -55,6 +55,34 @@ module TypeMethodSignature =
|
||||
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
|
||||
type PrimitiveType =
|
||||
| Boolean
|
||||
@@ -134,7 +162,21 @@ type TypeDefn =
|
||||
| FromDefinition of ComparableTypeDefinitionHandle * assemblyFullName : string * SignatureTypeKind
|
||||
| GenericInstantiation of generic : TypeDefn * args : ImmutableArray<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
|
||||
/// <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
|
||||
/// Not really a type: this indicates the *absence* of a return value.
|
||||
| Void
|
||||
|
@@ -23,7 +23,7 @@ type MethodImplParsed =
|
||||
/// Represents detailed information about a type definition in a .NET assembly.
|
||||
/// This is a strongly-typed representation of TypeDefinition from System.Reflection.Metadata.
|
||||
/// </summary>
|
||||
type TypeInfo<'generic> =
|
||||
type TypeInfo<'generic, 'fieldGeneric> =
|
||||
{
|
||||
/// <summary>The namespace containing the type.</summary>
|
||||
Namespace : string
|
||||
@@ -34,7 +34,7 @@ type TypeInfo<'generic> =
|
||||
/// <summary>
|
||||
/// All methods defined within this type.
|
||||
/// </summary>
|
||||
Methods : WoofWare.PawPrint.MethodInfo<FakeUnit, WoofWare.PawPrint.GenericParameter> list
|
||||
Methods : WoofWare.PawPrint.MethodInfo<FakeUnit, WoofWare.PawPrint.GenericParameter, TypeDefn> list
|
||||
|
||||
/// <summary>
|
||||
/// Method implementation mappings for this type, often used for interface implementations
|
||||
@@ -45,7 +45,7 @@ type TypeInfo<'generic> =
|
||||
/// <summary>
|
||||
/// Fields defined in this type.
|
||||
/// </summary>
|
||||
Fields : WoofWare.PawPrint.FieldInfo<FakeUnit> list
|
||||
Fields : WoofWare.PawPrint.FieldInfo<FakeUnit, 'fieldGeneric> list
|
||||
|
||||
/// <summary>
|
||||
/// The base type that this type inherits from, or None for types that don't have a base type
|
||||
@@ -84,8 +84,18 @@ type TypeInfo<'generic> =
|
||||
override this.ToString () =
|
||||
$"%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> =
|
||||
abstract Eval<'a> : TypeInfo<'a> -> 'ret
|
||||
abstract Eval<'a, 'field> : TypeInfo<'a, 'field> -> 'ret
|
||||
|
||||
type TypeInfoCrate =
|
||||
abstract Apply<'ret> : TypeInfoEval<'ret> -> 'ret
|
||||
@@ -97,13 +107,13 @@ type TypeInfoCrate =
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module TypeInfoCrate =
|
||||
let make<'a> (t : TypeInfo<'a>) =
|
||||
let make<'a, 'field> (t : TypeInfo<'a, 'field>) : TypeInfoCrate =
|
||||
{ new TypeInfoCrate with
|
||||
member _.Apply e = e.Eval t
|
||||
|
||||
member this.ToString () =
|
||||
{ new TypeInfoEval<_> with
|
||||
member _.Eval this = string<TypeInfo<_>> this
|
||||
member _.Eval this = string<TypeInfo<_, _>> this
|
||||
}
|
||||
|> this.Apply
|
||||
|
||||
@@ -119,33 +129,37 @@ module TypeInfoCrate =
|
||||
type BaseClassTypes<'corelib> =
|
||||
{
|
||||
Corelib : 'corelib
|
||||
String : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
Boolean : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
Char : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
SByte : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
Byte : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
Int16 : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
UInt16 : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
Int32 : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
UInt32 : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
Int64 : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
UInt64 : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
Single : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
Double : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
Array : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
Enum : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
ValueType : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
DelegateType : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
Object : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
RuntimeMethodHandle : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
RuntimeFieldHandle : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
RuntimeTypeHandle : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
RuntimeType : TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
String : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Boolean : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Char : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
SByte : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Byte : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Int16 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
UInt16 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Int32 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
UInt32 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Int64 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
UInt64 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Single : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Double : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Array : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Enum : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
ValueType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
DelegateType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Object : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
RuntimeMethodHandle : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
RuntimeFieldHandle : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
RuntimeTypeHandle : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
RuntimeType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
Void : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
TypedReference : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
IntPtr : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
UIntPtr : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module TypeInfo =
|
||||
let withGenerics<'a, 'b> (gen : 'b ImmutableArray) (t : TypeInfo<'a>) : TypeInfo<'b> =
|
||||
let withGenerics<'a, 'b, 'field> (gen : 'b ImmutableArray) (t : TypeInfo<'a, 'field>) : TypeInfo<'b, 'field> =
|
||||
{
|
||||
Namespace = t.Namespace
|
||||
Name = t.Name
|
||||
@@ -161,7 +175,7 @@ module TypeInfo =
|
||||
Events = t.Events
|
||||
}
|
||||
|
||||
let mapGeneric<'a, 'b> (f : int -> 'a -> 'b) (t : TypeInfo<'a>) : TypeInfo<'b> =
|
||||
let mapGeneric<'a, 'b, 'field> (f : int -> 'a -> 'b) (t : TypeInfo<'a, 'field>) : TypeInfo<'b, 'field> =
|
||||
withGenerics (t.Generics |> Seq.mapi f |> ImmutableArray.CreateRange) t
|
||||
|
||||
let internal read
|
||||
@@ -170,7 +184,7 @@ module TypeInfo =
|
||||
(thisAssembly : AssemblyName)
|
||||
(metadataReader : MetadataReader)
|
||||
(typeHandle : TypeDefinitionHandle)
|
||||
: TypeInfo<WoofWare.PawPrint.GenericParameter>
|
||||
: TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
=
|
||||
let typeDef = metadataReader.GetTypeDefinition typeHandle
|
||||
let methods = typeDef.GetMethods ()
|
||||
@@ -275,11 +289,11 @@ module TypeInfo =
|
||||
else
|
||||
None
|
||||
|
||||
let rec resolveBaseType<'corelib, 'generic>
|
||||
let rec resolveBaseType<'corelib, 'generic, 'field>
|
||||
(baseClassTypes : BaseClassTypes<'corelib>)
|
||||
(getName : 'corelib -> AssemblyName)
|
||||
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic>)
|
||||
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic>)
|
||||
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic, 'field>)
|
||||
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic, 'field>)
|
||||
(sourceAssembly : AssemblyName)
|
||||
(value : BaseTypeInfo option)
|
||||
: ResolvedBaseType
|
||||
@@ -311,16 +325,23 @@ module TypeInfo =
|
||||
let toTypeDefn
|
||||
(corelib : BaseClassTypes<'corelib>)
|
||||
(getName : 'corelib -> AssemblyName)
|
||||
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic>)
|
||||
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic>)
|
||||
(ty : TypeInfo<'generic>)
|
||||
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic, 'field>)
|
||||
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic, 'field>)
|
||||
(ty : TypeInfo<TypeDefn, TypeDefn>)
|
||||
: TypeDefn
|
||||
=
|
||||
let stk =
|
||||
match resolveBaseType corelib getName getTypeDef getTypeRef ty.Assembly ty.BaseType with
|
||||
| ResolvedBaseType.Enum
|
||||
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
||||
| ResolvedBaseType.Object -> SignatureTypeKind.Class
|
||||
| ResolvedBaseType.Delegate -> failwith "todo"
|
||||
| ResolvedBaseType.Object
|
||||
| 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)
|
||||
|
@@ -26,6 +26,7 @@
|
||||
<Compile Include="ExportedType.fs" />
|
||||
<Compile Include="TypeSpec.fs" />
|
||||
<Compile Include="Assembly.fs" />
|
||||
<Compile Include="TypeConcretisation.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
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)
|
||||
|
||||
let compilationOptions =
|
||||
CSharpCompilationOptions(OutputKind.ConsoleApplication).WithAllowUnsafe (true)
|
||||
CSharpCompilationOptions(OutputKind.ConsoleApplication).WithAllowUnsafe true
|
||||
|
||||
let compilation =
|
||||
CSharpCompilation.Create (
|
||||
|
@@ -30,5 +30,5 @@ type TestCase =
|
||||
FileName : string
|
||||
ExpectedReturnCode : int
|
||||
NativeImpls : NativeImpls
|
||||
LocalVariablesOfMain : CliType list
|
||||
LocalVariablesOfMain : CliType list option
|
||||
}
|
||||
|
@@ -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 ()
|
123
WoofWare.PawPrint.Test/TestImpureCases.fs
Normal file
123
WoofWare.PawPrint.Test/TestImpureCases.fs
Normal file
@@ -0,0 +1,123 @@
|
||||
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 ()
|
||||
LocalVariablesOfMain = [] |> Some
|
||||
}
|
||||
]
|
||||
|
||||
let cases : TestCase 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)
|
||||
}
|
||||
}
|
||||
LocalVariablesOfMain = [] |> Some
|
||||
}
|
||||
]
|
||||
|
||||
[<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
|
||||
|
||||
match case.LocalVariablesOfMain with
|
||||
| None -> ()
|
||||
| Some expected -> finalVariables |> shouldEqual expected
|
||||
|
||||
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 ()
|
@@ -11,16 +11,40 @@ open WoofWare.PawPrint.Test
|
||||
|
||||
[<TestFixture>]
|
||||
[<Parallelizable(ParallelScope.All)>]
|
||||
module TestCases =
|
||||
module TestPureCases =
|
||||
let assy = typeof<RunResult>.Assembly
|
||||
|
||||
let unimplemented =
|
||||
[
|
||||
{
|
||||
FileName = "CrossAssemblyTypes.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "GenericEdgeCases.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "TestShl.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "TestShr.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "Threads.cs"
|
||||
ExpectedReturnCode = 3
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = []
|
||||
LocalVariablesOfMain = [] |> Some
|
||||
}
|
||||
{
|
||||
FileName = "ComplexTryCatch.cs"
|
||||
@@ -50,18 +74,13 @@ module TestCases =
|
||||
100001
|
||||
]
|
||||
|> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i))
|
||||
}
|
||||
{
|
||||
FileName = "WriteLine.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = NativeImpls.PassThru ()
|
||||
LocalVariablesOfMain = []
|
||||
|> Some
|
||||
}
|
||||
{
|
||||
FileName = "ResizeArray.cs"
|
||||
ExpectedReturnCode = 109
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ]
|
||||
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ] |> Some
|
||||
}
|
||||
]
|
||||
|
||||
@@ -71,7 +90,139 @@ module TestCases =
|
||||
FileName = "NoOp.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 1) ]
|
||||
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 1) ] |> Some
|
||||
}
|
||||
{
|
||||
FileName = "CastClassSimpleInheritance.cs"
|
||||
ExpectedReturnCode = 5
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "IsInstSimpleInheritance.cs"
|
||||
ExpectedReturnCode = 42
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "CastClassNull.cs"
|
||||
ExpectedReturnCode = 42
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "CastClassArrayCovariance.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "CastClassToObject.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "IsinstPatternMatching.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "CastClassMultipleInterfaces.cs"
|
||||
ExpectedReturnCode = 42
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "CastClassCrossAssembly.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "CastClassNestedTypes.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "CastClassGenerics.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "CastClassEnum.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "CastClassBoxing.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "IsinstBoxing.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "CastClassArray.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "IsinstArray.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "IsinstNull.cs"
|
||||
ExpectedReturnCode = 42
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "CastClassInvalid.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "IsinstFailed.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "IsinstFailedInterface.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "CastClassInterface.cs"
|
||||
ExpectedReturnCode = 1
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "IsinstInterface.cs"
|
||||
ExpectedReturnCode = 42
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "StaticVariables.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "Ldind.cs"
|
||||
@@ -84,6 +235,7 @@ module TestCases =
|
||||
// Return value
|
||||
CliType.Numeric (CliNumericType.Int32 0)
|
||||
]
|
||||
|> Some
|
||||
}
|
||||
{
|
||||
FileName = "CustomDelegate.cs"
|
||||
@@ -100,20 +252,13 @@ module TestCases =
|
||||
// ret
|
||||
CliType.Numeric (CliNumericType.Int32 8)
|
||||
]
|
||||
|> Some
|
||||
}
|
||||
{
|
||||
FileName = "ArgumentOrdering.cs"
|
||||
ExpectedReturnCode = 42
|
||||
ExpectedReturnCode = 0
|
||||
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)
|
||||
]
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "BasicLock.cs"
|
||||
@@ -136,6 +281,7 @@ module TestCases =
|
||||
// return value
|
||||
CliType.Numeric (CliNumericType.Int32 1)
|
||||
]
|
||||
|> Some
|
||||
}
|
||||
{
|
||||
FileName = "TriangleNumber.cs"
|
||||
@@ -152,29 +298,7 @@ module TestCases =
|
||||
// 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 = []
|
||||
|> Some
|
||||
}
|
||||
{
|
||||
FileName = "ExceptionWithNoOpFinally.cs"
|
||||
@@ -187,12 +311,19 @@ module TestCases =
|
||||
3
|
||||
]
|
||||
|> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i))
|
||||
|> Some
|
||||
}
|
||||
{
|
||||
FileName = "ExceptionWithNoOpCatch.cs"
|
||||
ExpectedReturnCode = 10
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ]
|
||||
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ] |> Some
|
||||
}
|
||||
{
|
||||
FileName = "Floats.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "TryCatchWithThrowInBody.cs"
|
||||
@@ -205,6 +336,25 @@ module TestCases =
|
||||
4
|
||||
]
|
||||
|> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i))
|
||||
|> Some
|
||||
}
|
||||
{
|
||||
FileName = "Ldelema.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "TypeConcretization.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
{
|
||||
FileName = "TestOr.cs"
|
||||
ExpectedReturnCode = 0
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = None
|
||||
}
|
||||
]
|
||||
|
||||
@@ -223,6 +373,8 @@ module TestCases =
|
||||
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"
|
||||
@@ -231,14 +383,17 @@ module TestCases =
|
||||
| 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
|
||||
|
||||
let finalVariables =
|
||||
terminalState.ThreadState.[terminatingThread].MethodState.LocalVariables
|
||||
|> Seq.toList
|
||||
|
||||
finalVariables |> shouldEqual case.LocalVariablesOfMain
|
||||
|
||||
match case.LocalVariablesOfMain with
|
||||
| None -> ()
|
||||
| Some expected -> finalVariables |> shouldEqual expected
|
||||
with _ ->
|
||||
for message in messages () do
|
||||
System.Console.Error.WriteLine $"{message}"
|
@@ -13,23 +13,58 @@
|
||||
<Compile Include="LoggerFactory.fs" />
|
||||
<Compile Include="Assembly.fs" />
|
||||
<Compile Include="Roslyn.fs" />
|
||||
<Compile Include="RealRuntime.fs" />
|
||||
<Compile Include="TestHarness.fs"/>
|
||||
<Compile Include="TestCases.fs" />
|
||||
<Compile Include="TestHelloWorld.fs" />
|
||||
<EmbeddedResource Include="sources\BasicLock.cs" />
|
||||
<EmbeddedResource Include="sources\NoOp.cs" />
|
||||
<EmbeddedResource Include="sources\ExceptionWithNoOpCatch.cs" />
|
||||
<EmbeddedResource Include="sources\ExceptionWithNoOpFinally.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" />
|
||||
<Compile Include="TestPureCases.fs" />
|
||||
<Compile Include="TestImpureCases.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="sourcesPure\BasicLock.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\Floats.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\NoOp.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\StaticVariables.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\Ldelema.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\ExceptionWithNoOpCatch.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\ExceptionWithNoOpFinally.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\TryCatchWithThrowInBody.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\ComplexTryCatch.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\TriangleNumber.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\Threads.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\ResizeArray.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\ArgumentOrdering.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CastClassSimpleInheritance.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\IsInstSimpleInheritance.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CastClassNull.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CastClassArrayCovariance.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CastClassToObject.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\IsinstPatternMatching.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CastClassMultipleInterfaces.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CastClassCrossAssembly.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CastClassNestedTypes.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CastClassGenerics.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CastClassEnum.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CastClassBoxing.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\IsinstBoxing.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CastClassArray.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\IsinstArray.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\IsinstNull.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CastClassInvalid.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\IsinstFailed.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\IsinstFailedInterface.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CastClassInterface.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\IsinstInterface.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\TestShl.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\TestShr.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\TestOr.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CustomDelegate.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\Ldind.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\TypeConcretization.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\CrossAssemblyTypes.cs" />
|
||||
<EmbeddedResource Include="sourcesPure\GenericEdgeCases.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="sourcesImpure\WriteLine.cs" />
|
||||
<EmbeddedResource Include="sourcesImpure\InstaQuit.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -37,14 +72,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FsUnit" Version="7.0.1"/>
|
||||
<PackageReference Include="FsUnit" Version="7.1.1"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
|
||||
<PackageReference Include="NUnit" Version="4.3.2"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.6" />
|
||||
<PackageReference Include="WoofWare.DotnetRuntimeLocator" Version="0.3.2"/>
|
||||
</ItemGroup>
|
||||
|
||||
</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;
|
||||
}
|
||||
}
|
12
WoofWare.PawPrint.Test/sourcesPure/CastClassArray.cs
Normal file
12
WoofWare.PawPrint.Test/sourcesPure/CastClassArray.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
public class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
int[] numbers = new int[] { 1, 2, 3, 4, 5 };
|
||||
|
||||
// Cast array to System.Array - should succeed
|
||||
System.Array array = (System.Array)numbers;
|
||||
|
||||
return array.Length;
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
public class Program
|
||||
{
|
||||
public struct Counter
|
||||
{
|
||||
public int Count;
|
||||
|
||||
public Counter(int count)
|
||||
{
|
||||
Count = count;
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Counter counter = new Counter(42);
|
||||
|
||||
// Box the value type
|
||||
object boxed = counter;
|
||||
|
||||
// Check if boxed value is System.ValueType
|
||||
if (boxed is System.ValueType)
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
30
WoofWare.PawPrint.Test/sourcesPure/CastClassBoxing.cs
Normal file
30
WoofWare.PawPrint.Test/sourcesPure/CastClassBoxing.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
public class Program
|
||||
{
|
||||
public struct Point
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
|
||||
public Point(int x, int y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Point p = new Point(10, 32);
|
||||
|
||||
// Box the value type
|
||||
object boxed = p;
|
||||
|
||||
// Cast boxed value type to object (should succeed)
|
||||
object obj = (object)boxed;
|
||||
|
||||
// Unbox
|
||||
Point unboxed = (Point)obj;
|
||||
|
||||
return unboxed.X + unboxed.Y;
|
||||
}
|
||||
}
|
22
WoofWare.PawPrint.Test/sourcesPure/CastClassCrossAssembly.cs
Normal file
22
WoofWare.PawPrint.Test/sourcesPure/CastClassCrossAssembly.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
// Using types from System.Collections.Generic assembly
|
||||
List<int> list = new List<int> { 1, 2, 3, 4, 5 };
|
||||
|
||||
// Cast to interface from another assembly
|
||||
IEnumerable<int> enumerable = (IEnumerable<int>)list;
|
||||
|
||||
int count = 0;
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
return count == 5 ? 42 : 0;
|
||||
}
|
||||
}
|
25
WoofWare.PawPrint.Test/sourcesPure/CastClassEnum.cs
Normal file
25
WoofWare.PawPrint.Test/sourcesPure/CastClassEnum.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
public class Program
|
||||
{
|
||||
public enum Color
|
||||
{
|
||||
Red = 1,
|
||||
Green = 2,
|
||||
Blue = 42
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Color myColor = Color.Blue;
|
||||
|
||||
// Box enum value
|
||||
object boxed = myColor;
|
||||
|
||||
// Cast to System.Enum
|
||||
System.Enum enumValue = (System.Enum)boxed;
|
||||
|
||||
// Cast back to specific enum
|
||||
Color unboxed = (Color)enumValue;
|
||||
|
||||
return (int)unboxed;
|
||||
}
|
||||
}
|
23
WoofWare.PawPrint.Test/sourcesPure/CastClassGenerics.cs
Normal file
23
WoofWare.PawPrint.Test/sourcesPure/CastClassGenerics.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
public class Program
|
||||
{
|
||||
public class Container<T>
|
||||
{
|
||||
public T Value { get; set; }
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Container<int> intContainer = new Container<int> { Value = 42 };
|
||||
|
||||
// Cast generic type to object
|
||||
object obj = (object)intContainer;
|
||||
|
||||
// Check type and cast back
|
||||
if (obj is Container<int> container)
|
||||
{
|
||||
return container.Value;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
25
WoofWare.PawPrint.Test/sourcesPure/CastClassInterface.cs
Normal file
25
WoofWare.PawPrint.Test/sourcesPure/CastClassInterface.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
public class Program
|
||||
{
|
||||
public interface ICalculator
|
||||
{
|
||||
int Calculate(int x, int y);
|
||||
}
|
||||
|
||||
public class Adder : ICalculator
|
||||
{
|
||||
public int Calculate(int x, int y)
|
||||
{
|
||||
return x + y;
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Adder adder = new Adder();
|
||||
|
||||
// Cast to interface - should succeed
|
||||
ICalculator calc = (ICalculator)adder;
|
||||
|
||||
return calc.Calculate(10, 32);
|
||||
}
|
||||
}
|
31
WoofWare.PawPrint.Test/sourcesPure/CastClassInvalid.cs
Normal file
31
WoofWare.PawPrint.Test/sourcesPure/CastClassInvalid.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
public class Program
|
||||
{
|
||||
public class Cat
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class Dog
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
object cat = new Cat { Name = "Whiskers" };
|
||||
|
||||
// Invalid cast - should throw InvalidCastException
|
||||
Dog dog = (Dog)cat;
|
||||
|
||||
// Should not reach here
|
||||
return 0;
|
||||
}
|
||||
catch (System.InvalidCastException)
|
||||
{
|
||||
// Expected exception caught
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
public class Program
|
||||
{
|
||||
public interface IReadable
|
||||
{
|
||||
string Read();
|
||||
}
|
||||
|
||||
public interface IWritable
|
||||
{
|
||||
void Write(string data);
|
||||
}
|
||||
|
||||
public class File : IReadable, IWritable
|
||||
{
|
||||
private string content = "Hello";
|
||||
|
||||
public string Read()
|
||||
{
|
||||
return content;
|
||||
}
|
||||
|
||||
public void Write(string data)
|
||||
{
|
||||
content = data;
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
File file = new File();
|
||||
|
||||
// Cast to first interface
|
||||
IReadable readable = (IReadable)file;
|
||||
|
||||
// Cast to second interface
|
||||
IWritable writable = (IWritable)file;
|
||||
|
||||
return readable != null && writable != null ? 42 : 0;
|
||||
}
|
||||
}
|
23
WoofWare.PawPrint.Test/sourcesPure/CastClassNestedTypes.cs
Normal file
23
WoofWare.PawPrint.Test/sourcesPure/CastClassNestedTypes.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
public class Program
|
||||
{
|
||||
public class Outer
|
||||
{
|
||||
public class Inner
|
||||
{
|
||||
public int Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Outer.Inner inner = new Outer.Inner { Value = 42 };
|
||||
|
||||
// Cast nested type to object
|
||||
object obj = (object)inner;
|
||||
|
||||
// Cast back
|
||||
Outer.Inner casted = (Outer.Inner)obj;
|
||||
|
||||
return casted.Value;
|
||||
}
|
||||
}
|
17
WoofWare.PawPrint.Test/sourcesPure/CastClassNull.cs
Normal file
17
WoofWare.PawPrint.Test/sourcesPure/CastClassNull.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
public class Program
|
||||
{
|
||||
public class MyClass
|
||||
{
|
||||
public int Value { get; set; }
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
MyClass obj = null;
|
||||
|
||||
// Cast null reference - should succeed and remain null
|
||||
object result = (object)obj;
|
||||
|
||||
return result == null ? 42 : 0;
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
public class Program
|
||||
{
|
||||
public class Animal
|
||||
{
|
||||
public int Age { get; set; }
|
||||
}
|
||||
|
||||
public class Dog : Animal
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Dog myDog = new Dog { Age = 5, Name = "Rex" };
|
||||
|
||||
// Cast to base class - should succeed
|
||||
Animal animal = (Animal)myDog;
|
||||
|
||||
return animal.Age;
|
||||
}
|
||||
}
|
18
WoofWare.PawPrint.Test/sourcesPure/CastClassToObject.cs
Normal file
18
WoofWare.PawPrint.Test/sourcesPure/CastClassToObject.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
public class Program
|
||||
{
|
||||
public class CustomClass
|
||||
{
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
CustomClass custom = new CustomClass { Id = 42 };
|
||||
|
||||
// Everything can be cast to System.Object
|
||||
System.Object obj = (System.Object)custom;
|
||||
|
||||
// Verify it's the same object
|
||||
return obj != null && obj == custom ? 42 : 0;
|
||||
}
|
||||
}
|
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
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
public class Program
|
||||
{
|
||||
public class Vehicle
|
||||
{
|
||||
public int Wheels { get; set; }
|
||||
}
|
||||
|
||||
public class Car : Vehicle
|
||||
{
|
||||
public string Model { get; set; }
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Car myCar = new Car { Wheels = 4, Model = "Tesla" };
|
||||
|
||||
// 'is' operator uses isinst instruction
|
||||
if (myCar is Vehicle)
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
15
WoofWare.PawPrint.Test/sourcesPure/IsinstArray.cs
Normal file
15
WoofWare.PawPrint.Test/sourcesPure/IsinstArray.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
public class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
string[] names = new string[] { "Alice", "Bob", "Charlie" };
|
||||
|
||||
// Check if array is System.Array
|
||||
if (names is System.Array)
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
28
WoofWare.PawPrint.Test/sourcesPure/IsinstBoxing.cs
Normal file
28
WoofWare.PawPrint.Test/sourcesPure/IsinstBoxing.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
public class Program
|
||||
{
|
||||
public struct Counter
|
||||
{
|
||||
public int Count;
|
||||
|
||||
public Counter(int count)
|
||||
{
|
||||
Count = count;
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Counter counter = new Counter(42);
|
||||
|
||||
// Box the value type
|
||||
object boxed = counter;
|
||||
|
||||
// Check if boxed value is System.ValueType
|
||||
if (boxed is System.ValueType)
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
25
WoofWare.PawPrint.Test/sourcesPure/IsinstFailed.cs
Normal file
25
WoofWare.PawPrint.Test/sourcesPure/IsinstFailed.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
public class Program
|
||||
{
|
||||
public class Bird
|
||||
{
|
||||
public bool CanFly { get; set; }
|
||||
}
|
||||
|
||||
public class Fish
|
||||
{
|
||||
public bool CanSwim { get; set; }
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Bird sparrow = new Bird { CanFly = true };
|
||||
|
||||
// Cast to object first to bypass compile-time checking
|
||||
object obj = sparrow;
|
||||
|
||||
// This should fail at runtime and return null (not throw)
|
||||
Fish fish = obj as Fish;
|
||||
|
||||
return fish == null ? 42 : 0;
|
||||
}
|
||||
}
|
30
WoofWare.PawPrint.Test/sourcesPure/IsinstFailedInterface.cs
Normal file
30
WoofWare.PawPrint.Test/sourcesPure/IsinstFailedInterface.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
public class Program
|
||||
{
|
||||
public interface IAnimal
|
||||
{
|
||||
string Name { get; set; }
|
||||
}
|
||||
|
||||
public class Bird : IAnimal
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public bool CanFly { get; set; }
|
||||
}
|
||||
|
||||
public class Fish : IAnimal
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public bool CanSwim { get; set; }
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
IAnimal animal = new Bird { Name = "Sparrow", CanFly = true };
|
||||
|
||||
// This should fail at runtime and return null (not throw)
|
||||
// because the actual object is Bird, not Fish
|
||||
Fish fish = animal as Fish;
|
||||
|
||||
return fish == null ? 42 : 0;
|
||||
}
|
||||
}
|
27
WoofWare.PawPrint.Test/sourcesPure/IsinstInterface.cs
Normal file
27
WoofWare.PawPrint.Test/sourcesPure/IsinstInterface.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
public class Program
|
||||
{
|
||||
public interface IDisposable
|
||||
{
|
||||
void Dispose();
|
||||
}
|
||||
|
||||
public class Resource : IDisposable
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Cleanup
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Resource resource = new Resource { Id = 42 };
|
||||
|
||||
// 'as' operator uses isinst instruction
|
||||
IDisposable disposable = resource as IDisposable;
|
||||
|
||||
return disposable != null ? resource.Id : 0;
|
||||
}
|
||||
}
|
17
WoofWare.PawPrint.Test/sourcesPure/IsinstNull.cs
Normal file
17
WoofWare.PawPrint.Test/sourcesPure/IsinstNull.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
public class Program
|
||||
{
|
||||
public class MyType
|
||||
{
|
||||
public int Value { get; set; }
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
MyType obj = null;
|
||||
|
||||
// isinst on null should return null
|
||||
object result = obj as object;
|
||||
|
||||
return result == null ? 42 : 0;
|
||||
}
|
||||
}
|
40
WoofWare.PawPrint.Test/sourcesPure/IsinstPatternMatching.cs
Normal file
40
WoofWare.PawPrint.Test/sourcesPure/IsinstPatternMatching.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
public class Program
|
||||
{
|
||||
public abstract class Shape
|
||||
{
|
||||
public abstract double GetArea();
|
||||
}
|
||||
|
||||
public class Circle : Shape
|
||||
{
|
||||
public double Radius { get; set; }
|
||||
|
||||
public override double GetArea()
|
||||
{
|
||||
return 3.14 * Radius * Radius;
|
||||
}
|
||||
}
|
||||
|
||||
public class Square : Shape
|
||||
{
|
||||
public double Side { get; set; }
|
||||
|
||||
public override double GetArea()
|
||||
{
|
||||
return Side * Side;
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Shape shape = new Circle { Radius = 10 };
|
||||
|
||||
// Pattern matching uses isinst
|
||||
if (shape is Circle circle)
|
||||
{
|
||||
return (int)circle.Radius;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
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
|
||||
}
|
||||
}
|
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]
|
||||
|
||||
if delegateToRun.Fields.["_target"] <> CliType.ObjectRef None then
|
||||
failwith "TODO: delegate target wasn't None"
|
||||
let target =
|
||||
match delegateToRun.Fields.["_target"] with
|
||||
| CliType.ObjectRef addr -> addr
|
||||
| x -> failwith $"TODO: delegate target wasn't an object ref: %O{x}"
|
||||
|
||||
let methodPtr =
|
||||
match delegateToRun.Fields.["_methodPtr"] with
|
||||
| CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer mi)) -> mi
|
||||
| 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 methodPtr =
|
||||
methodPtr |> MethodInfo.mapTypeGenerics (fun i _ -> typeGenerics.[i])
|
||||
|
||||
// When we return, we need to go back up the stack
|
||||
match state |> IlMachineState.returnStackFrame loggerFactory baseClassTypes thread with
|
||||
| None -> failwith "unexpectedly nowhere to return from delegate"
|
||||
@@ -86,23 +82,41 @@ module AbstractMachine =
|
||||
(state, instruction.Arguments)
|
||||
||> 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, _ =
|
||||
state.WithThreadSwitchedToAssembly methodPtr.DeclaringType.Assembly thread
|
||||
|
||||
// Don't advance the program counter again on return; that was already done by the Callvirt that
|
||||
// caused this delegate to be invoked.
|
||||
let state, result =
|
||||
state
|
||||
|> IlMachineState.callMethodInActiveAssembly
|
||||
let currentThreadState = state.ThreadState.[thread]
|
||||
|
||||
let state =
|
||||
IlMachineState.callMethod
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
thread
|
||||
false
|
||||
(Some methodGenerics)
|
||||
methodPtr
|
||||
None
|
||||
constructing
|
||||
false
|
||||
false
|
||||
methodGenerics
|
||||
methodPtr
|
||||
thread
|
||||
currentThreadState
|
||||
state
|
||||
|
||||
ExecutionResult.Stepped (state, result)
|
||||
ExecutionResult.Stepped (state, WhatWeDid.Executed)
|
||||
| _ ->
|
||||
|
||||
let outcome =
|
||||
@@ -111,8 +125,8 @@ module AbstractMachine =
|
||||
targetType.Namespace,
|
||||
targetType.Name,
|
||||
instruction.ExecutingMethod.Name,
|
||||
instruction.ExecutingMethod.Signature.ParameterTypes,
|
||||
instruction.ExecutingMethod.Signature.ReturnType
|
||||
instruction.ExecutingMethod.RawSignature.ParameterTypes,
|
||||
instruction.ExecutingMethod.RawSignature.ReturnType
|
||||
with
|
||||
| "System.Private.CoreLib",
|
||||
"System",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection
|
||||
open System.Reflection.Metadata
|
||||
@@ -41,6 +40,7 @@ type ManagedPointerSource =
|
||||
| LocalVariable of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
|
||||
| Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
|
||||
| Heap of ManagedHeapAddress
|
||||
| ArrayIndex of arr : ManagedHeapAddress * index : int
|
||||
| Null
|
||||
|
||||
override this.ToString () =
|
||||
@@ -51,6 +51,7 @@ type ManagedPointerSource =
|
||||
$"<variable %i{var} in method frame %i{method} of thread %O{source}>"
|
||||
| ManagedPointerSource.Argument (source, method, var) ->
|
||||
$"<argument %i{var} in method frame %i{method} of thread %O{source}>"
|
||||
| ManagedPointerSource.ArrayIndex (arr, index) -> $"<index %i{index} of array %O{arr}>"
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type UnsignedNativeIntSource =
|
||||
@@ -61,7 +62,7 @@ type UnsignedNativeIntSource =
|
||||
type NativeIntSource =
|
||||
| Verbatim of int64
|
||||
| ManagedPointer of ManagedPointerSource
|
||||
| FunctionPointer of MethodInfo<FakeUnit, WoofWare.PawPrint.GenericParameter>
|
||||
| FunctionPointer of MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>
|
||||
| TypeHandlePtr of int64<typeHandle>
|
||||
|
||||
override this.ToString () : string =
|
||||
@@ -129,6 +130,7 @@ type CliRuntimePointerSource =
|
||||
| LocalVariable of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
|
||||
| Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
|
||||
| Heap of ManagedHeapAddress
|
||||
| ArrayIndex of arr : ManagedHeapAddress * index : int
|
||||
| Null
|
||||
|
||||
type CliRuntimePointer =
|
||||
@@ -149,7 +151,7 @@ type CliType =
|
||||
| RuntimePointer of CliRuntimePointer
|
||||
/// This is *not* a CLI type as such. I don't actually know its status. A value type is represented simply
|
||||
/// as a concatenated list of its fields.
|
||||
| ValueType of CliType list
|
||||
| ValueType of (string * CliType) list
|
||||
|
||||
/// In fact any non-zero value will do for True, but we'll use 1
|
||||
static member OfBool (b : bool) = CliType.Bool (if b then 1uy else 0uy)
|
||||
@@ -165,7 +167,6 @@ type CliTypeResolutionResult =
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module CliType =
|
||||
|
||||
let zeroOfPrimitive (primitiveType : PrimitiveType) : CliType =
|
||||
match primitiveType with
|
||||
| PrimitiveType.Boolean -> CliType.Bool 0uy
|
||||
@@ -191,92 +192,208 @@ module CliType =
|
||||
| PrimitiveType.Object -> CliType.ObjectRef None
|
||||
|
||||
let rec zeroOf
|
||||
(concreteTypes : AllConcreteTypes)
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||
(assy : DumpedAssembly)
|
||||
(typeGenerics : TypeDefn ImmutableArray option)
|
||||
(methodGenerics : TypeDefn ImmutableArray option)
|
||||
(ty : TypeDefn)
|
||||
: CliTypeResolutionResult
|
||||
(handle : ConcreteTypeHandle)
|
||||
: CliType * AllConcreteTypes
|
||||
=
|
||||
match ty with
|
||||
| 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
|
||||
zeroOfWithVisited concreteTypes assemblies corelib handle Set.empty
|
||||
|
||||
match fields with
|
||||
| Error (_, []) -> failwith "logic error"
|
||||
| Error (_, f :: _) -> CliTypeResolutionResult.FirstLoad f
|
||||
| Ok fields -> CliType.ValueType fields |> CliTypeResolutionResult.Resolved
|
||||
| TypeResolutionResult.FirstLoadAssy assy -> CliTypeResolutionResult.FirstLoad assy
|
||||
| SignatureTypeKind.Class -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
|
||||
| _ -> raise (ArgumentOutOfRangeException ())
|
||||
| TypeDefn.FromDefinition (typeDefinitionHandle, _, signatureTypeKind) ->
|
||||
let typeDef = assy.TypeDefs.[typeDefinitionHandle.Get]
|
||||
and zeroOfWithVisited
|
||||
(concreteTypes : AllConcreteTypes)
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||
(handle : ConcreteTypeHandle)
|
||||
(visited : Set<ConcreteTypeHandle>)
|
||||
: CliType * AllConcreteTypes
|
||||
=
|
||||
|
||||
if typeDef = corelib.Int32 then
|
||||
zeroOfPrimitive PrimitiveType.Int32 |> CliTypeResolutionResult.Resolved
|
||||
elif typeDef = corelib.Int64 then
|
||||
zeroOfPrimitive PrimitiveType.Int64 |> CliTypeResolutionResult.Resolved
|
||||
elif typeDef = corelib.UInt32 then
|
||||
zeroOfPrimitive PrimitiveType.UInt32 |> CliTypeResolutionResult.Resolved
|
||||
elif typeDef = corelib.UInt64 then
|
||||
zeroOfPrimitive PrimitiveType.UInt64 |> CliTypeResolutionResult.Resolved
|
||||
// Handle constructed types first
|
||||
match handle with
|
||||
| ConcreteTypeHandle.Byref _ ->
|
||||
// Byref types are managed references - the zero value is a null reference
|
||||
CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null), concreteTypes
|
||||
|
||||
| ConcreteTypeHandle.Pointer _ ->
|
||||
// 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
|
||||
// TODO: the rest
|
||||
match signatureTypeKind with
|
||||
| SignatureTypeKind.Unknown -> failwith "todo"
|
||||
| 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
|
||||
let visited = Set.add handle visited
|
||||
// Custom type - need to determine if it's a value type or reference type
|
||||
determineZeroForCustomType concreteTypes assemblies corelib handle concreteType typeDef visited
|
||||
|
||||
match fields with
|
||||
| Error (_, []) -> failwith "logic error"
|
||||
| Error (_, f :: _) -> CliTypeResolutionResult.FirstLoad f
|
||||
| Ok fields ->
|
||||
and private determineZeroForCustomType
|
||||
(concreteTypes : AllConcreteTypes)
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||
(handle : ConcreteTypeHandle)
|
||||
(concreteType : ConcreteType<ConcreteTypeHandle>)
|
||||
(typeDef : WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>)
|
||||
(visited : Set<ConcreteTypeHandle>)
|
||||
: CliType * AllConcreteTypes
|
||||
=
|
||||
|
||||
CliType.ValueType fields |> CliTypeResolutionResult.Resolved
|
||||
| SignatureTypeKind.Class -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
|
||||
| _ -> raise (ArgumentOutOfRangeException ())
|
||||
| TypeDefn.GenericInstantiation (generic, args) ->
|
||||
zeroOf assemblies corelib assy (Some args) methodGenerics generic
|
||||
| TypeDefn.FunctionPointer typeMethodSignature -> failwith "todo"
|
||||
| TypeDefn.GenericTypeParameter index ->
|
||||
// TODO: can generics depend on other generics? presumably, so we pass the array down again
|
||||
match typeGenerics with
|
||||
| None -> failwith "asked for a type parameter of generic type, but no generics in scope"
|
||||
| Some generics -> zeroOf assemblies corelib assy (Some generics) methodGenerics generics.[index]
|
||||
| TypeDefn.GenericMethodParameter index ->
|
||||
match methodGenerics with
|
||||
| None -> failwith "asked for a method parameter of generic type, but no generics in scope"
|
||||
| Some generics -> zeroOf assemblies corelib assy typeGenerics (Some generics) generics.[index]
|
||||
| TypeDefn.Void -> failwith "should never construct an element of type Void"
|
||||
// Determine if this is a value type by checking inheritance
|
||||
let isValueType =
|
||||
match DumpedAssembly.resolveBaseType corelib assemblies typeDef.Assembly typeDef.BaseType with
|
||||
| ResolvedBaseType.ValueType
|
||||
| ResolvedBaseType.Enum -> true
|
||||
| ResolvedBaseType.Delegate -> false // Delegates are reference types
|
||||
| ResolvedBaseType.Object -> false
|
||||
|
||||
if isValueType then
|
||||
// It's a value type - need to create zero values for all non-static fields
|
||||
let mutable currentConcreteTypes = concreteTypes
|
||||
|
||||
let fieldZeros =
|
||||
typeDef.Fields
|
||||
|> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static))
|
||||
|> 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)
|
||||
)
|
||||
|
||||
CliType.ValueType fieldZeros, 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 typeGenerics = declaringType.Generics |> ImmutableArray.CreateRange
|
||||
let methodGenerics = ImmutableArray.Empty // Fields don't have method generics
|
||||
|
||||
let loadAssembly
|
||||
(assyName : AssemblyName)
|
||||
(ref : AssemblyReferenceHandle)
|
||||
: (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly)
|
||||
=
|
||||
match assemblies.TryGetValue assyName.FullName with
|
||||
| true, currentAssy ->
|
||||
let targetAssyRef = currentAssy.AssemblyReferences.[ref]
|
||||
|
||||
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
|
||||
typeGenerics
|
||||
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,26 @@ module Corelib =
|
||||
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "RuntimeFieldHandle" then Some v else None)
|
||||
|> 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
|
||||
|
||||
{
|
||||
Corelib = corelib
|
||||
String = stringType
|
||||
@@ -138,4 +158,8 @@ module Corelib =
|
||||
RuntimeMethodHandle = runtimeMethodHandleType
|
||||
RuntimeFieldHandle = runtimeFieldHandleType
|
||||
RuntimeType = runtimeTypeType
|
||||
Void = voidType
|
||||
TypedReference = typedReferenceType
|
||||
IntPtr = intPtrType
|
||||
UIntPtr = uintPtrType
|
||||
}
|
||||
|
@@ -10,7 +10,8 @@ type EvalStackValue =
|
||||
| ObjectRef of ManagedHeapAddress
|
||||
// Fraser thinks this isn't really a thing in CoreCLR
|
||||
// | TransientPointer of TransientPointerSource
|
||||
| UserDefinedValueType of EvalStackValue list
|
||||
/// Mapping of field name to value
|
||||
| UserDefinedValueType of (string * EvalStackValue) list
|
||||
|
||||
override this.ToString () =
|
||||
match this with
|
||||
@@ -21,7 +22,11 @@ type EvalStackValue =
|
||||
| EvalStackValue.ManagedPointer managedPointerSource -> $"Pointer(%O{managedPointerSource})"
|
||||
| EvalStackValue.ObjectRef managedHeapAddress -> $"ObjectRef(%O{managedHeapAddress})"
|
||||
| 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})"
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -87,8 +92,8 @@ module EvalStackValue =
|
||||
/// Then truncates to int64.
|
||||
let convToUInt64 (value : EvalStackValue) : int64 option =
|
||||
match value with
|
||||
| EvalStackValue.Int32 i -> if i >= 0 then Some (int64 i) else failwith "TODO"
|
||||
| EvalStackValue.Int64 int64 -> failwith "todo"
|
||||
| EvalStackValue.Int32 i -> Some (int64 (uint32 i))
|
||||
| EvalStackValue.Int64 int64 -> Some int64
|
||||
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
|
||||
| EvalStackValue.Float f -> failwith "todo"
|
||||
| EvalStackValue.ManagedPointer managedPointerSource -> failwith "todo"
|
||||
@@ -102,7 +107,7 @@ module EvalStackValue =
|
||||
| CliNumericType.Int32 _ ->
|
||||
match popped with
|
||||
| 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}"
|
||||
| CliNumericType.Int64 _ ->
|
||||
match popped with
|
||||
@@ -162,6 +167,8 @@ module EvalStackValue =
|
||||
|> CliType.RuntimePointer
|
||||
| ManagedPointerSource.Heap managedHeapAddress -> CliType.ObjectRef (Some managedHeapAddress)
|
||||
| ManagedPointerSource.Null -> CliType.ObjectRef None
|
||||
| ManagedPointerSource.ArrayIndex (arr, ind) ->
|
||||
CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.ArrayIndex (arr, ind)))
|
||||
| EvalStackValue.NativeInt nativeIntSource ->
|
||||
match nativeIntSource with
|
||||
| NativeIntSource.Verbatim 0L -> CliType.ObjectRef None
|
||||
@@ -175,7 +182,7 @@ module EvalStackValue =
|
||||
| _ -> failwith "TODO"
|
||||
| EvalStackValue.UserDefinedValueType fields ->
|
||||
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"
|
||||
| _ -> failwith $"TODO: {popped}"
|
||||
| CliType.Bool _ ->
|
||||
@@ -200,6 +207,10 @@ module EvalStackValue =
|
||||
CliRuntimePointerSource.Argument (sourceThread, methodFrame, var)
|
||||
|> CliRuntimePointer.Managed
|
||||
|> CliType.RuntimePointer
|
||||
| ManagedPointerSource.ArrayIndex (arr, index) ->
|
||||
CliRuntimePointerSource.ArrayIndex (arr, index)
|
||||
|> CliRuntimePointer.Managed
|
||||
|> CliType.RuntimePointer
|
||||
| EvalStackValue.NativeInt intSrc ->
|
||||
match intSrc with
|
||||
| NativeIntSource.Verbatim i -> CliType.RuntimePointer (CliRuntimePointer.Unmanaged i)
|
||||
@@ -215,6 +226,7 @@ module EvalStackValue =
|
||||
)
|
||||
| ManagedPointerSource.Argument (a, b, c) ->
|
||||
CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Argument (a, b, c)))
|
||||
| ManagedPointerSource.ArrayIndex _ -> failwith "TODO"
|
||||
| NativeIntSource.FunctionPointer methodInfo ->
|
||||
CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer methodInfo))
|
||||
| NativeIntSource.TypeHandlePtr int64 -> failwith "todo"
|
||||
@@ -230,12 +242,22 @@ module EvalStackValue =
|
||||
match popped with
|
||||
| EvalStackValue.UserDefinedValueType popped ->
|
||||
if fields.Length <> popped.Length then
|
||||
failwith "mismatch"
|
||||
failwith
|
||||
$"mismatch: popped value type {popped} (length %i{popped.Length}) into {fields} (length %i{fields.Length})"
|
||||
|
||||
List.map2 toCliTypeCoerced fields popped |> CliType.ValueType
|
||||
List.map2
|
||||
(fun (name1, v1) (name2, v2) ->
|
||||
if name1 <> name2 then
|
||||
failwith $"TODO: name mismatch, {name1} vs {name2}"
|
||||
|
||||
name1, toCliTypeCoerced v1 v2
|
||||
)
|
||||
fields
|
||||
popped
|
||||
|> CliType.ValueType
|
||||
| popped ->
|
||||
match fields with
|
||||
| [ target ] -> toCliTypeCoerced target popped
|
||||
| [ _, target ] -> toCliTypeCoerced target popped
|
||||
| _ -> failwith $"TODO: {popped} into value type {target}"
|
||||
|
||||
let rec ofCliType (v : CliType) : EvalStackValue =
|
||||
@@ -269,12 +291,17 @@ module EvalStackValue =
|
||||
| CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, var) ->
|
||||
ManagedPointerSource.LocalVariable (sourceThread, methodFrame, var)
|
||||
|> EvalStackValue.ManagedPointer
|
||||
| CliRuntimePointerSource.ArrayIndex (arr, ind) ->
|
||||
ManagedPointerSource.ArrayIndex (arr, ind) |> EvalStackValue.ManagedPointer
|
||||
| CliRuntimePointerSource.Argument (sourceThread, methodFrame, var) ->
|
||||
ManagedPointerSource.Argument (sourceThread, methodFrame, var)
|
||||
|> EvalStackValue.ManagedPointer
|
||||
| CliRuntimePointerSource.Heap addr -> EvalStackValue.ObjectRef addr
|
||||
| CliRuntimePointerSource.Null -> EvalStackValue.ManagedPointer ManagedPointerSource.Null
|
||||
| CliType.ValueType fields -> fields |> List.map ofCliType |> EvalStackValue.UserDefinedValueType
|
||||
| CliType.ValueType fields ->
|
||||
fields
|
||||
|> List.map (fun (name, f) -> name, ofCliType f)
|
||||
|> EvalStackValue.UserDefinedValueType
|
||||
|
||||
type EvalStack =
|
||||
{
|
||||
|
154
WoofWare.PawPrint/EvalStackValueComparisons.fs
Normal file
154
WoofWare.PawPrint/EvalStackValueComparisons.fs
Normal file
@@ -0,0 +1,154 @@
|
||||
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 ceq (var1 : EvalStackValue) (var2 : EvalStackValue) : bool =
|
||||
// Table III.4
|
||||
match var1, var2 with
|
||||
| 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}"
|
||||
| EvalStackValue.UserDefinedValueType _, _ -> failwith $"bad ceq: {var1} vs {var2}"
|
@@ -1,29 +1,32 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System
|
||||
open System.Collections.Immutable
|
||||
|
||||
/// 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
|
||||
IlOffset : int
|
||||
}
|
||||
|
||||
/// 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
|
||||
ExceptionObject : ManagedHeapAddress
|
||||
/// Stack trace built during unwinding
|
||||
StackTrace : ExceptionStackFrame list
|
||||
StackTrace : ExceptionStackFrame<'typeGen, 'methodGen, 'methodVar> list
|
||||
}
|
||||
|
||||
/// 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
|
||||
| PropagatingException of exn : CliException
|
||||
| ResumeAfterFilter of handlerPC : int * exn : CliException
|
||||
| PropagatingException of exn : CliException<'typeGen, 'methodGen, 'methodVar>
|
||||
| ResumeAfterFilter of handlerPC : int * exn : CliException<'typeGen, 'methodGen, 'methodVar>
|
||||
|
||||
/// Helper functions for exception handling
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -44,7 +47,7 @@ module ExceptionHandling =
|
||||
let findExceptionHandler
|
||||
(currentPC : int)
|
||||
(exceptionTypeCrate : TypeInfoCrate)
|
||||
(method : WoofWare.PawPrint.MethodInfo<TypeDefn, 'methodGeneric>)
|
||||
(method : WoofWare.PawPrint.MethodInfo<'typeGen, 'methodGeneric, 'methodVar>)
|
||||
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
: (WoofWare.PawPrint.ExceptionRegion * bool) option // handler, isFinally
|
||||
=
|
||||
@@ -92,7 +95,7 @@ module ExceptionHandling =
|
||||
let findFinallyBlocksToRun
|
||||
(currentPC : int)
|
||||
(targetPC : int)
|
||||
(method : WoofWare.PawPrint.MethodInfo<TypeDefn, 'methodGeneric>)
|
||||
(method : WoofWare.PawPrint.MethodInfo<'typeGeneric, 'methodGeneric, 'methodVar>)
|
||||
: ExceptionOffset list
|
||||
=
|
||||
match method.Instructions with
|
||||
@@ -122,7 +125,7 @@ module ExceptionHandling =
|
||||
/// Get the active exception regions at a given offset
|
||||
let getActiveRegionsAtOffset
|
||||
(offset : int)
|
||||
(method : WoofWare.PawPrint.MethodInfo<TypeDefn, 'methodGeneric>)
|
||||
(method : WoofWare.PawPrint.MethodInfo<'a, 'b, 'c>)
|
||||
: WoofWare.PawPrint.ExceptionRegion list
|
||||
=
|
||||
match method.Instructions with
|
||||
|
@@ -86,6 +86,7 @@ module System_Threading_Monitor =
|
||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
|
||||
failwith "not really expecting to *edit* an argument..."
|
||||
| ManagedPointerSource.Heap addr -> failwith "todo: managed heap"
|
||||
| ManagedPointerSource.ArrayIndex _ -> failwith "todo: array index"
|
||||
|
||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -109,6 +109,16 @@ type ManagedHeap =
|
||||
|
||||
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 SetArrayValue
|
||||
(alloc : ManagedHeapAddress)
|
||||
(offset : int)
|
||||
@@ -124,6 +134,9 @@ type ManagedHeap =
|
||||
match arr with
|
||||
| None -> failwith "tried to change element of nonexistent array"
|
||||
| Some arr ->
|
||||
if offset < 0 || offset >= arr.Elements.Length then
|
||||
failwith "TODO: throw somehow"
|
||||
|
||||
{ arr with
|
||||
Elements = arr.Elements.SetItem (offset, v)
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ type MethodReturnState =
|
||||
{
|
||||
/// Index in the MethodStates array of a ThreadState
|
||||
JumpTo : int
|
||||
WasInitialisingType : RuntimeConcreteType option
|
||||
WasInitialisingType : ConcreteTypeHandle option
|
||||
/// The Newobj instruction means we need to push a reference immediately after Ret.
|
||||
WasConstructingObj : ManagedHeapAddress option
|
||||
}
|
||||
@@ -19,16 +19,16 @@ and MethodState =
|
||||
_IlOpIndex : int
|
||||
EvaluationStack : EvalStack
|
||||
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
|
||||
LocalMemoryPool : unit
|
||||
/// On return, we restore this state. This should be Some almost always; an exception is the entry point.
|
||||
ReturnState : MethodReturnState option
|
||||
Generics : ImmutableArray<TypeDefn> option
|
||||
Generics : ImmutableArray<ConcreteTypeHandle>
|
||||
/// Track which exception regions are currently active (innermost first)
|
||||
ActiveExceptionRegions : ExceptionRegion list
|
||||
/// 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
|
||||
@@ -64,7 +64,7 @@ and MethodState =
|
||||
EvaluationStack = EvalStack.Empty
|
||||
}
|
||||
|
||||
static member setExceptionContinuation (cont : ExceptionContinuation) (state : MethodState) : MethodState =
|
||||
static member setExceptionContinuation (cont : ExceptionContinuation<_, _, _>) (state : MethodState) : MethodState =
|
||||
{ state with
|
||||
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 static, `args` must be of length numParams.
|
||||
static member Empty
|
||||
(concreteTypes : AllConcreteTypes)
|
||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||
(loadedAssemblies : ImmutableDictionary<string, DumpedAssembly>)
|
||||
(containingAssembly : DumpedAssembly)
|
||||
(method : WoofWare.PawPrint.MethodInfo<TypeDefn, TypeDefn>)
|
||||
(methodGenerics : ImmutableArray<TypeDefn> option)
|
||||
(method : WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>)
|
||||
(methodGenerics : ImmutableArray<ConcreteTypeHandle>)
|
||||
(args : ImmutableArray<CliType>)
|
||||
(returnState : MethodReturnState option)
|
||||
: 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
|
||||
// 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 result = ImmutableArray.CreateBuilder ()
|
||||
|
||||
for var in localVariableSig do
|
||||
match CliType.zeroOf loadedAssemblies corelib containingAssembly typeGenerics methodGenerics var with
|
||||
| CliTypeResolutionResult.Resolved t -> result.Add t
|
||||
| CliTypeResolutionResult.FirstLoad (assy : WoofWare.PawPrint.AssemblyReference) ->
|
||||
requiredAssemblies.Add assy
|
||||
// Note: This assumes all types have already been concretized
|
||||
// If this fails with "ConcreteTypeHandle not found", it means
|
||||
// we need to ensure types are concretized before creating the MethodState
|
||||
let zero, _ = CliType.zeroOf concreteTypes loadedAssemblies corelib var
|
||||
result.Add zero
|
||||
|
||||
result.ToImmutable ()
|
||||
|
||||
if requiredAssemblies.Count > 0 then
|
||||
Error (requiredAssemblies |> Seq.toList)
|
||||
else
|
||||
|
||||
let activeRegions = ExceptionHandling.getActiveRegionsAtOffset 0 method
|
||||
|
||||
{
|
||||
|
@@ -1,33 +1,7 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
#nowarn "42"
|
||||
|
||||
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>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module NullaryIlOp =
|
||||
@@ -72,6 +46,7 @@ module NullaryIlOp =
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables.[int<uint16> whichVar]
|
||||
| ManagedPointerSource.Heap managedHeapAddress -> failwith "TODO: Heap pointer dereferencing not implemented"
|
||||
| ManagedPointerSource.ArrayIndex _ -> failwith "TODO: array index pointer dereferencing not implemented"
|
||||
|
||||
// Unified Ldind implementation
|
||||
let private executeLdind
|
||||
@@ -103,41 +78,6 @@ module NullaryIlOp =
|
||||
|
||||
(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 =
|
||||
// TODO: throw NullReferenceException if unaligned target
|
||||
let valueToStore, state = IlMachineState.popEvalStack currentThread state
|
||||
@@ -182,8 +122,84 @@ module NullaryIlOp =
|
||||
)
|
||||
}
|
||||
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
|
||||
| ManagedPointerSource.ArrayIndex _ -> 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
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||
@@ -346,92 +362,57 @@ module NullaryIlOp =
|
||||
let var2, state = state |> IlMachineState.popEvalStack currentThread
|
||||
let var1, state = state |> IlMachineState.popEvalStack currentThread
|
||||
|
||||
let comparisonResult =
|
||||
// Table III.4
|
||||
match var1, var2 with
|
||||
| EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> if var1 = var2 then 1 else 0
|
||||
| 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 -> if var1 = var2 then 1 else 0
|
||||
| EvalStackValue.Int64 _, _ -> failwith $"bad ceq: Int64 vs {var2}"
|
||||
| EvalStackValue.Float var1, EvalStackValue.Float var2 -> failwith "TODO: float CEQ float"
|
||||
| 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
|
||||
1
|
||||
else
|
||||
failwith $"TODO(CEQ): nativeint vs nativeint, {f1} vs {f2}"
|
||||
| NativeIntSource.TypeHandlePtr f1, NativeIntSource.TypeHandlePtr f2 -> if f1 = f2 then 1 else 0
|
||||
| NativeIntSource.Verbatim f1, NativeIntSource.Verbatim f2 -> if f1 = f2 then 1 else 0
|
||||
| NativeIntSource.ManagedPointer f1, NativeIntSource.ManagedPointer f2 -> if f1 = f2 then 1 else 0
|
||||
| _, _ -> 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 -> 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}"
|
||||
let comparisonResult = if EvalStackValueComparisons.ceq var1 var2 then 1 else 0
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 comparisonResult) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Cgt ->
|
||||
let var2, state = state |> IlMachineState.popEvalStack currentThread
|
||||
let var1, state = state |> IlMachineState.popEvalStack currentThread
|
||||
|
||||
let comparisonResult = if EvalStackValueComparisons.cgt var1 var2 then 1 else 0
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 comparisonResult) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Cgt_un ->
|
||||
let var2, state = state |> IlMachineState.popEvalStack currentThread
|
||||
let var1, state = state |> IlMachineState.popEvalStack currentThread
|
||||
|
||||
let comparisonResult = if EvalStackValueComparisons.cgtUn var1 var2 then 1 else 0
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 comparisonResult) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Cgt -> failwith "TODO: Cgt unimplemented"
|
||||
| Cgt_un -> failwith "TODO: Cgt_un unimplemented"
|
||||
| Clt ->
|
||||
let var2, state = state |> IlMachineState.popEvalStack currentThread
|
||||
let var1, state = state |> IlMachineState.popEvalStack currentThread
|
||||
|
||||
let comparisonResult =
|
||||
match var1, var2 with
|
||||
| EvalStackValue.Int64 var1, EvalStackValue.Int64 var2 -> if var1 < var2 then 1 else 0
|
||||
| EvalStackValue.Float var1, EvalStackValue.Float var2 ->
|
||||
failwith "TODO: Clt float comparison unimplemented"
|
||||
| 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 -> 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"
|
||||
let comparisonResult = if EvalStackValueComparisons.clt var1 var2 then 1 else 0
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 comparisonResult) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Clt_un ->
|
||||
let var2, state = state |> IlMachineState.popEvalStack currentThread
|
||||
let var1, state = state |> IlMachineState.popEvalStack currentThread
|
||||
|
||||
let comparisonResult = if EvalStackValueComparisons.cltUn var1 var2 then 1 else 0
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' (EvalStackValue.Int32 comparisonResult) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Clt_un -> failwith "TODO: Clt_un unimplemented"
|
||||
| Stloc_0 ->
|
||||
state
|
||||
|> IlMachineState.popFromStackToLocalVariable currentThread 0
|
||||
@@ -456,12 +437,22 @@ module NullaryIlOp =
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> 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_un -> failwith "TODO: Sub_ovf_un unimplemented"
|
||||
| Add ->
|
||||
let result, state =
|
||||
binaryArithmeticOperation ArithmeticOperation.add currentThread state
|
||||
let val1, state = IlMachineState.popEvalStack currentThread state
|
||||
let val2, state = IlMachineState.popEvalStack currentThread state
|
||||
let result = BinaryArithmetic.execute ArithmeticOperation.add val1 val2
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' result currentThread
|
||||
@@ -471,8 +462,9 @@ module NullaryIlOp =
|
||||
| Add_ovf -> failwith "TODO: Add_ovf unimplemented"
|
||||
| Add_ovf_un -> failwith "TODO: Add_ovf_un unimplemented"
|
||||
| Mul ->
|
||||
let result, state =
|
||||
binaryArithmeticOperation ArithmeticOperation.mul currentThread state
|
||||
let val1, state = IlMachineState.popEvalStack currentThread state
|
||||
let val2, state = IlMachineState.popEvalStack currentThread state
|
||||
let result = BinaryArithmetic.execute ArithmeticOperation.mul val1 val2
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' result currentThread
|
||||
@@ -483,11 +475,119 @@ module NullaryIlOp =
|
||||
| Mul_ovf_un -> failwith "TODO: Mul_ovf_un unimplemented"
|
||||
| Div -> failwith "TODO: Div 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"
|
||||
| Shl -> failwith "TODO: Shl unimplemented"
|
||||
| And -> failwith "TODO: And unimplemented"
|
||||
| Or -> failwith "TODO: Or unimplemented"
|
||||
| Shl ->
|
||||
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
|
||||
| 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"
|
||||
| Conv_I ->
|
||||
let popped, state = IlMachineState.popEvalStack currentThread state
|
||||
@@ -798,6 +898,7 @@ module NullaryIlOp =
|
||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
|
||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
|
||||
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
|
||||
| ManagedPointerSource.ArrayIndex _ -> failwith "todo"
|
||||
| a -> failwith $"TODO: {a}"
|
||||
|
||||
let state =
|
||||
@@ -807,7 +908,29 @@ module NullaryIlOp =
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
(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_i1 -> failwith "TODO: Ldelem_i1 unimplemented"
|
||||
| Ldelem_u1 -> failwith "TODO: Ldelem_u1 unimplemented"
|
||||
@@ -819,19 +942,54 @@ module NullaryIlOp =
|
||||
| Ldelem_u8 -> failwith "TODO: Ldelem_u8 unimplemented"
|
||||
| Ldelem_r4 -> failwith "TODO: Ldelem_r4 unimplemented"
|
||||
| Ldelem_r8 -> failwith "TODO: Ldelem_r8 unimplemented"
|
||||
| Ldelem_ref -> failwith "TODO: Ldelem_ref unimplemented"
|
||||
| Stelem_i -> failwith "TODO: Stelem_i unimplemented"
|
||||
| Stelem_i1 -> failwith "TODO: Stelem_i1 unimplemented"
|
||||
| Ldelem_ref ->
|
||||
let index, state = IlMachineState.popEvalStack currentThread state
|
||||
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_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_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_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_r4 -> failwith "TODO: Stelem_r4 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"
|
||||
| Initblk -> failwith "TODO: Initblk unimplemented"
|
||||
| Conv_ovf_u1 -> failwith "TODO: Conv_ovf_u1 unimplemented"
|
||||
|
@@ -17,11 +17,7 @@ module Program =
|
||||
let argsAllocations, state =
|
||||
(state, args)
|
||||
||> Seq.mapFold (fun state arg ->
|
||||
IlMachineState.allocateManagedObject
|
||||
(corelib.String
|
||||
|> TypeInfo.mapGeneric (fun _ _ -> failwith<unit> "there are no generics here"))
|
||||
(failwith "TODO: assert fields and populate")
|
||||
state
|
||||
IlMachineState.allocateManagedObject corelib.String (failwith "TODO: assert fields and populate") state
|
||||
// TODO: set the char values in memory
|
||||
)
|
||||
|
||||
@@ -83,71 +79,177 @@ module Program =
|
||||
| None -> failwith "No entry point in input DLL"
|
||||
| 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"
|
||||
|
||||
let mainMethod =
|
||||
mainMethod
|
||||
|> MethodInfo.mapTypeGenerics (fun _ -> failwith "Refusing to execute generic main method")
|
||||
|> MethodInfo.mapMethodGenerics (fun _ -> failwith "Refusing to execute generic main method")
|
||||
let state = IlMachineState.initial loggerFactory dotnetRuntimeDirs dumped
|
||||
|
||||
let rec computeState (baseClassTypes : BaseClassTypes<DumpedAssembly> option) (state : IlMachineState) =
|
||||
// The thread's state is slightly fake: we will need to put arguments onto the stack before actually
|
||||
// executing the main method.
|
||||
// We construct the thread here before we are entirely ready, because we need a thread from which to
|
||||
// initialise the class containing the main method.
|
||||
// Once we've obtained e.g. the String and Array classes, we can populate the args array.
|
||||
match
|
||||
MethodState.Empty
|
||||
(Option.toObj baseClassTypes)
|
||||
state._LoadedAssemblies
|
||||
dumped
|
||||
// pretend there are no instructions, so we avoid preparing anything
|
||||
{ mainMethod with
|
||||
Instructions = Some MethodInstructions.OnlyRet
|
||||
}
|
||||
None
|
||||
(ImmutableArray.CreateRange [ CliType.ObjectRef None ])
|
||||
None
|
||||
with
|
||||
| Ok meth -> IlMachineState.addThread meth dumped.Name state, baseClassTypes
|
||||
| Error requiresRefs ->
|
||||
let state =
|
||||
(state, requiresRefs)
|
||||
||> List.fold (fun state ref ->
|
||||
let handle, referencingAssy = ref.Handle
|
||||
let referencingAssy = state.LoadedAssembly referencingAssy |> Option.get
|
||||
// Find the core library by traversing the type hierarchy of the main method's declaring type
|
||||
// until we reach System.Object
|
||||
let rec handleBaseTypeInfo
|
||||
(state : IlMachineState)
|
||||
(baseTypeInfo : BaseTypeInfo)
|
||||
(currentAssembly : DumpedAssembly)
|
||||
(continueWithGeneric :
|
||||
IlMachineState
|
||||
-> TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
|
||||
-> DumpedAssembly
|
||||
-> IlMachineState * BaseClassTypes<DumpedAssembly> option)
|
||||
(continueWithResolved :
|
||||
IlMachineState
|
||||
-> TypeInfo<TypeDefn, TypeDefn>
|
||||
-> DumpedAssembly
|
||||
-> IlMachineState * BaseClassTypes<DumpedAssembly> option)
|
||||
: IlMachineState * BaseClassTypes<DumpedAssembly> option
|
||||
=
|
||||
match baseTypeInfo with
|
||||
| BaseTypeInfo.TypeRef typeRefHandle ->
|
||||
// Look up the TypeRef from the handle
|
||||
let typeRef = currentAssembly.TypeRefs.[typeRefHandle]
|
||||
|
||||
let rec go state =
|
||||
// Resolve the type reference to find which assembly it's in
|
||||
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, _, _ =
|
||||
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<WoofWare.PawPrint.GenericParameter, 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)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
let corelib =
|
||||
let coreLib =
|
||||
state._LoadedAssemblies.Keys
|
||||
|> Seq.tryFind (fun x -> x.StartsWith ("System.Private.CoreLib, ", StringComparison.Ordinal))
|
||||
// Create the method state with the concretized method
|
||||
match
|
||||
MethodState.Empty
|
||||
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
|
||||
|> Option.map (fun coreLib -> state._LoadedAssemblies.[coreLib] |> Corelib.getBaseTypes)
|
||||
let state, baseTypes =
|
||||
findCoreLibraryAssemblyFromGeneric state mainMethodType dumped
|
||||
|
||||
computeState corelib state
|
||||
computeState baseTypes state
|
||||
|
||||
let (state, mainThread), baseClassTypes =
|
||||
IlMachineState.initial loggerFactory dotnetRuntimeDirs dumped
|
||||
|> computeState None
|
||||
let (state, mainThread), baseClassTypes = state |> 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)
|
||||
|
||||
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) =
|
||||
match
|
||||
state
|
||||
|> IlMachineState.loadClass
|
||||
loggerFactory
|
||||
(Option.toObj baseClassTypes)
|
||||
mainMethod.DeclaringType
|
||||
mainThread
|
||||
|> IlMachineState.loadClass loggerFactory (Option.toObj baseClassTypes) mainTypeHandle mainThread
|
||||
with
|
||||
| StateLoadResult.NothingToDo ilMachineState -> ilMachineState
|
||||
| StateLoadResult.FirstLoadThis ilMachineState -> loadInitialState ilMachineState
|
||||
@@ -167,12 +269,12 @@ module Program =
|
||||
| Some c -> c
|
||||
|
||||
let arrayAllocation, state =
|
||||
match mainMethod.Signature.ParameterTypes |> Seq.toList with
|
||||
match mainMethodFromMetadata.Signature.ParameterTypes |> Seq.toList with
|
||||
| [ TypeDefn.OneDimensionalArrayLowerBoundZero (TypeDefn.PrimitiveType PrimitiveType.String) ] ->
|
||||
allocateArgs argv baseClassTypes state
|
||||
| _ -> 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 -> ()
|
||||
| _ -> failwith "Main method must return int32; other types not currently supported"
|
||||
|
||||
@@ -185,15 +287,16 @@ module Program =
|
||||
logger.LogInformation "Main method class now initialised"
|
||||
|
||||
// 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 =
|
||||
match
|
||||
MethodState.Empty
|
||||
state.ConcreteTypes
|
||||
baseClassTypes
|
||||
state._LoadedAssemblies
|
||||
dumped
|
||||
mainMethod
|
||||
None
|
||||
concretizedMainMethod
|
||||
ImmutableArray.Empty
|
||||
(ImmutableArray.Create (CliType.OfManagedObject arrayAllocation))
|
||||
None
|
||||
with
|
||||
@@ -210,11 +313,7 @@ module Program =
|
||||
{ state with
|
||||
ThreadState = state.ThreadState |> Map.add mainThread threadState
|
||||
}
|
||||
|> IlMachineState.ensureTypeInitialised
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
mainThread
|
||||
methodState.ExecutingMethod.DeclaringType
|
||||
|> IlMachineState.ensureTypeInitialised loggerFactory baseClassTypes mainThread mainTypeHandle
|
||||
|
||||
match init with
|
||||
| WhatWeDid.SuspendedForClassInit -> failwith "TODO: suspended for class init"
|
||||
|
@@ -4,3 +4,5 @@ namespace WoofWare.PawPrint
|
||||
module internal Tuple =
|
||||
let withLeft<'a, 'b> (x : 'a) (y : 'b) : 'a * 'b = 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
|
||||
|
@@ -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.
|
||||
// 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>]
|
||||
module TypeInitTable =
|
||||
let tryGet (ty : RuntimeConcreteType) (t : TypeInitTable) =
|
||||
let tryGet (ty : ConcreteTypeHandle) (t : TypeInitTable) =
|
||||
match t.TryGetValue ty with
|
||||
| true, v -> Some v
|
||||
| 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
|
||||
| false, _ -> t.Add (ty, TypeInitState.InProgress thread)
|
||||
| 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
|
||||
| false, _ -> failwith "Logic error: completing initialisation of a type which never started initialising"
|
||||
| true, TypeInitState.Initialized ->
|
||||
|
@@ -87,7 +87,11 @@ module internal UnaryConstIlOp =
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int8 b)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> 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 ->
|
||||
state
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
@@ -383,9 +387,111 @@ module internal UnaryConstIlOp =
|
||||
else
|
||||
id
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Bgt_un_s b -> failwith "TODO: Bgt_un_s unimplemented"
|
||||
| Ble_un_s b -> failwith "TODO: Ble_un_s unimplemented"
|
||||
| Blt_un_s b -> failwith "TODO: Blt_un_s unimplemented"
|
||||
| Bgt_un_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 ->
|
||||
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"
|
||||
| Bge_un i -> failwith "TODO: Bge_un unimplemented"
|
||||
| Bgt_un i -> failwith "TODO: Bgt_un unimplemented"
|
||||
|
@@ -20,7 +20,7 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
match op with
|
||||
| Call ->
|
||||
let state, methodToCall, methodGenerics =
|
||||
let state, methodToCall, methodGenerics, typeArgsFromMetadata =
|
||||
match metadataToken with
|
||||
| MetadataToken.MethodSpecification h ->
|
||||
let spec = activeAssy.MethodSpecs.[h]
|
||||
@@ -29,11 +29,11 @@ module internal UnaryMetadataIlOp =
|
||||
| MetadataToken.MethodDef token ->
|
||||
let method =
|
||||
activeAssy.Methods.[token]
|
||||
|> MethodInfo.mapTypeGenerics (fun i _ -> spec.Signature.[i])
|
||||
|> MethodInfo.mapTypeGenerics (fun i _ -> TypeDefn.GenericTypeParameter i)
|
||||
|
||||
state, method, Some spec.Signature
|
||||
state, method, Some spec.Signature, None
|
||||
| MetadataToken.MemberReference ref ->
|
||||
let state, _, method =
|
||||
let state, _, method, extractedTypeArgs =
|
||||
IlMachineState.resolveMember
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
@@ -44,10 +44,10 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
match method with
|
||||
| Choice2Of2 _field -> failwith "tried to Call a field"
|
||||
| Choice1Of2 method -> state, method, Some spec.Signature
|
||||
| Choice1Of2 method -> state, method, Some spec.Signature, Some extractedTypeArgs
|
||||
| k -> failwith $"Unrecognised kind: %O{k}"
|
||||
| MetadataToken.MemberReference h ->
|
||||
let state, _, method =
|
||||
let state, _, method, extractedTypeArgs =
|
||||
IlMachineState.resolveMember
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
@@ -58,34 +58,52 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
match method with
|
||||
| Choice2Of2 _field -> failwith "tried to Call a field"
|
||||
| Choice1Of2 method -> state, method, None
|
||||
| Choice1Of2 method -> state, method, None, Some extractedTypeArgs
|
||||
|
||||
| MetadataToken.MethodDef defn ->
|
||||
match activeAssy.Methods.TryGetValue defn with
|
||||
| true, method ->
|
||||
let method = method |> MethodInfo.mapTypeGenerics (fun _ -> failwith "not generic")
|
||||
state, method, None
|
||||
state, method, None, None
|
||||
| false, _ -> failwith $"could not find method in {activeAssy.Name}"
|
||||
| k -> failwith $"Unrecognised kind: %O{k}"
|
||||
|
||||
match IlMachineState.loadClass loggerFactory baseClassTypes methodToCall.DeclaringType thread state with
|
||||
| NothingToDo state ->
|
||||
state.WithThreadSwitchedToAssembly methodToCall.DeclaringType.Assembly thread
|
||||
|> fst
|
||||
|> IlMachineState.callMethodInActiveAssembly
|
||||
let state, concretizedMethod, declaringTypeHandle =
|
||||
IlMachineState.concretizeMethodForExecution
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
thread
|
||||
true
|
||||
methodGenerics
|
||||
methodToCall
|
||||
methodGenerics
|
||||
typeArgsFromMetadata
|
||||
state
|
||||
|
||||
match IlMachineState.loadClass loggerFactory baseClassTypes declaringTypeHandle thread state with
|
||||
| NothingToDo state ->
|
||||
let state, _ =
|
||||
state.WithThreadSwitchedToAssembly methodToCall.DeclaringType.Assembly thread
|
||||
|
||||
let threadState = state.ThreadState.[thread]
|
||||
|
||||
IlMachineState.callMethod
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
None
|
||||
None
|
||||
false
|
||||
true
|
||||
concretizedMethod.Generics
|
||||
concretizedMethod
|
||||
thread
|
||||
threadState
|
||||
state,
|
||||
WhatWeDid.Executed
|
||||
| FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit
|
||||
|
||||
| Callvirt ->
|
||||
|
||||
// TODO: this is presumably super incomplete
|
||||
let state, method, generics =
|
||||
let state, methodToCall, methodGenerics, typeArgsFromMetadata =
|
||||
match metadataToken with
|
||||
| MetadataToken.MethodSpecification h ->
|
||||
let spec = activeAssy.MethodSpecs.[h]
|
||||
@@ -96,9 +114,9 @@ module internal UnaryMetadataIlOp =
|
||||
activeAssy.Methods.[token]
|
||||
|> MethodInfo.mapTypeGenerics (fun i _ -> spec.Signature.[i])
|
||||
|
||||
state, method, Some spec.Signature
|
||||
state, method, Some spec.Signature, None
|
||||
| MetadataToken.MemberReference ref ->
|
||||
let state, _, method =
|
||||
let state, _, method, extractedTypeArgs =
|
||||
IlMachineState.resolveMember
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
@@ -109,10 +127,10 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
match method with
|
||||
| Choice2Of2 _field -> failwith "tried to Callvirt a field"
|
||||
| Choice1Of2 method -> state, method, Some spec.Signature
|
||||
| Choice1Of2 method -> state, method, Some spec.Signature, Some extractedTypeArgs
|
||||
| k -> failwith $"Unrecognised kind: %O{k}"
|
||||
| MetadataToken.MemberReference h ->
|
||||
let state, _, method =
|
||||
let state, _, method, extractedTypeArgs =
|
||||
IlMachineState.resolveMember
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
@@ -123,35 +141,58 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
match method with
|
||||
| Choice2Of2 _field -> failwith "tried to Callvirt a field"
|
||||
| Choice1Of2 method -> state, method, None
|
||||
| Choice1Of2 method -> state, method, None, Some extractedTypeArgs
|
||||
|
||||
| MetadataToken.MethodDef defn ->
|
||||
match activeAssy.Methods.TryGetValue defn with
|
||||
| true, method ->
|
||||
let method = method |> MethodInfo.mapTypeGenerics (fun _ -> failwith "not generic")
|
||||
state, method, None
|
||||
state, method, None, None
|
||||
| false, _ -> failwith $"could not find method in {activeAssy.Name}"
|
||||
| k -> failwith $"Unrecognised kind: %O{k}"
|
||||
|
||||
match IlMachineState.loadClass loggerFactory baseClassTypes method.DeclaringType thread state with
|
||||
// TODO: this is pretty inefficient, we're concretising here and then immediately after in callMethodInActiveAssembly
|
||||
let state, concretizedMethod, declaringTypeHandle =
|
||||
IlMachineState.concretizeMethodForExecution
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
thread
|
||||
methodToCall
|
||||
methodGenerics
|
||||
typeArgsFromMetadata
|
||||
state
|
||||
|
||||
match IlMachineState.loadClass loggerFactory baseClassTypes declaringTypeHandle thread state with
|
||||
| FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit
|
||||
| NothingToDo state ->
|
||||
|
||||
state.WithThreadSwitchedToAssembly method.DeclaringType.Assembly thread
|
||||
state.WithThreadSwitchedToAssembly methodToCall.DeclaringType.Assembly thread
|
||||
|> fst
|
||||
|> IlMachineState.callMethodInActiveAssembly loggerFactory baseClassTypes thread true generics method None
|
||||
|> IlMachineState.callMethodInActiveAssembly
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
thread
|
||||
true
|
||||
methodGenerics
|
||||
methodToCall
|
||||
None
|
||||
typeArgsFromMetadata
|
||||
|
||||
| Castclass -> failwith "TODO: Castclass unimplemented"
|
||||
| Newobj ->
|
||||
let logger = loggerFactory.CreateLogger "Newobj"
|
||||
|
||||
let state, assy, ctor =
|
||||
let state, assy, ctor, typeArgsFromMetadata =
|
||||
match metadataToken with
|
||||
| MethodDef md ->
|
||||
let method = activeAssy.Methods.[md]
|
||||
state, activeAssy.Name, MethodInfo.mapTypeGenerics (fun _ -> failwith "non-generic method") method
|
||||
|
||||
state,
|
||||
activeAssy.Name,
|
||||
MethodInfo.mapTypeGenerics (fun _ -> failwith "non-generic method") method,
|
||||
None
|
||||
| MemberReference mr ->
|
||||
let state, name, method =
|
||||
let state, name, method, extractedTypeArgs =
|
||||
IlMachineState.resolveMember
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
@@ -161,12 +202,24 @@ module internal UnaryMetadataIlOp =
|
||||
state
|
||||
|
||||
match method with
|
||||
| Choice1Of2 mr -> state, name, mr
|
||||
| Choice1Of2 mr -> state, name, mr, Some extractedTypeArgs
|
||||
| Choice2Of2 _field -> failwith "unexpectedly NewObj found a constructor which is a field"
|
||||
| x -> failwith $"Unexpected metadata token for constructor: %O{x}"
|
||||
|
||||
let currentMethod = state.ThreadState.[thread].MethodState.ExecutingMethod
|
||||
|
||||
let state, concretizedCtor, declaringTypeHandle =
|
||||
IlMachineState.concretizeMethodForExecution
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
thread
|
||||
ctor
|
||||
None
|
||||
typeArgsFromMetadata
|
||||
state
|
||||
|
||||
let state, init =
|
||||
IlMachineState.ensureTypeInitialised loggerFactory baseClassTypes thread ctor.DeclaringType state
|
||||
IlMachineState.ensureTypeInitialised loggerFactory baseClassTypes thread declaringTypeHandle state
|
||||
|
||||
match init with
|
||||
| WhatWeDid.BlockedOnClassInit state -> failwith "TODO: another thread is running the initialiser"
|
||||
@@ -184,9 +237,7 @@ module internal UnaryMetadataIlOp =
|
||||
)
|
||||
|
||||
let typeGenerics =
|
||||
match ctor.DeclaringType.Generics with
|
||||
| [] -> None
|
||||
| l -> Some (ImmutableArray.CreateRange l)
|
||||
concretizedCtor.DeclaringType.Generics |> ImmutableArray.CreateRange
|
||||
|
||||
let state, fieldZeros =
|
||||
((state, []), ctorType.Fields)
|
||||
@@ -199,7 +250,7 @@ module internal UnaryMetadataIlOp =
|
||||
ctorAssembly
|
||||
field.Signature
|
||||
typeGenerics
|
||||
None
|
||||
ImmutableArray.Empty
|
||||
state
|
||||
|
||||
state, (field.Name, zero) :: zeros
|
||||
@@ -232,6 +283,7 @@ module internal UnaryMetadataIlOp =
|
||||
None
|
||||
ctor
|
||||
(Some allocatedAddr)
|
||||
typeArgsFromMetadata
|
||||
|
||||
match whatWeDid with
|
||||
| SuspendedForClassInit -> failwith "unexpectedly suspended while initialising constructor"
|
||||
@@ -255,9 +307,8 @@ module internal UnaryMetadataIlOp =
|
||||
| popped -> failwith $"unexpectedly popped value %O{popped} to serve as array len"
|
||||
|
||||
let typeGenerics =
|
||||
match newMethodState.ExecutingMethod.DeclaringType.Generics with
|
||||
| [] -> None
|
||||
| l -> Some (ImmutableArray.CreateRange l)
|
||||
newMethodState.ExecutingMethod.DeclaringType.Generics
|
||||
|> ImmutableArray.CreateRange
|
||||
|
||||
let state, elementType, assy =
|
||||
match metadataToken with
|
||||
@@ -274,14 +325,30 @@ module internal UnaryMetadataIlOp =
|
||||
| ResolvedBaseType.Enum
|
||||
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
||||
| ResolvedBaseType.Object -> SignatureTypeKind.Class
|
||||
| ResolvedBaseType.Delegate -> failwith "TODO: delegate"
|
||||
| ResolvedBaseType.Delegate -> SignatureTypeKind.Class
|
||||
|
||||
let result =
|
||||
TypeDefn.FromDefinition (
|
||||
ComparableTypeDefinitionHandle.Make defn.TypeDefHandle,
|
||||
defn.Assembly.Name,
|
||||
signatureTypeKind
|
||||
)
|
||||
if defn.Generics.IsEmpty then
|
||||
TypeDefn.FromDefinition (
|
||||
ComparableTypeDefinitionHandle.Make defn.TypeDefHandle,
|
||||
defn.Assembly.FullName,
|
||||
signatureTypeKind
|
||||
)
|
||||
else
|
||||
// Preserve the generic instantiation by converting GenericParameters to TypeDefn.GenericTypeParameter
|
||||
let genericDef =
|
||||
TypeDefn.FromDefinition (
|
||||
ComparableTypeDefinitionHandle.Make defn.TypeDefHandle,
|
||||
defn.Assembly.FullName,
|
||||
signatureTypeKind
|
||||
)
|
||||
|
||||
let genericArgs =
|
||||
defn.Generics
|
||||
|> Seq.mapi (fun i _ -> TypeDefn.GenericTypeParameter i)
|
||||
|> ImmutableArray.CreateRange
|
||||
|
||||
TypeDefn.GenericInstantiation (genericDef, genericArgs)
|
||||
|
||||
state, result, assy
|
||||
| MetadataToken.TypeSpecification spec ->
|
||||
@@ -290,6 +357,18 @@ module internal UnaryMetadataIlOp =
|
||||
| MetadataToken.TypeReference ref ->
|
||||
let ref = state.ActiveAssembly(thread).TypeRefs.[ref]
|
||||
|
||||
// Convert ConcreteTypeHandles back to TypeDefn for metadata operations
|
||||
let typeGenerics =
|
||||
newMethodState.ExecutingMethod.DeclaringType.Generics
|
||||
|> Seq.map (fun handle ->
|
||||
Concretization.concreteHandleToTypeDefn
|
||||
baseClassTypes
|
||||
handle
|
||||
state.ConcreteTypes
|
||||
state._LoadedAssemblies
|
||||
)
|
||||
|> ImmutableArray.CreateRange
|
||||
|
||||
let state, assy, resolved =
|
||||
IlMachineState.resolveTypeFromRef
|
||||
loggerFactory
|
||||
@@ -307,7 +386,7 @@ module internal UnaryMetadataIlOp =
|
||||
| ResolvedBaseType.Enum
|
||||
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
||||
| ResolvedBaseType.Object -> SignatureTypeKind.Class
|
||||
| ResolvedBaseType.Delegate -> failwith "TODO: delegate"
|
||||
| ResolvedBaseType.Delegate -> SignatureTypeKind.Class
|
||||
|
||||
let result =
|
||||
TypeDefn.FromDefinition (
|
||||
@@ -340,7 +419,38 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
state, WhatWeDid.Executed
|
||||
| Box -> failwith "TODO: Box unimplemented"
|
||||
| Ldelema -> failwith "TODO: Ldelema unimplemented"
|
||||
| Ldelema ->
|
||||
let index, state = IlMachineState.popEvalStack thread state
|
||||
let arr, state = IlMachineState.popEvalStack thread state
|
||||
|
||||
let index =
|
||||
match index with
|
||||
| EvalStackValue.Int32 i -> i
|
||||
| _ -> failwith $"TODO: {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 result =
|
||||
ManagedPointerSource.ArrayIndex (arrAddr, index)
|
||||
|> EvalStackValue.ManagedPointer
|
||||
|
||||
let state =
|
||||
IlMachineState.pushToEvalStack' result thread state
|
||||
|> IlMachineState.advanceProgramCounter thread
|
||||
|
||||
state, WhatWeDid.Executed
|
||||
| Isinst ->
|
||||
let actualObj, state = IlMachineState.popEvalStack thread state
|
||||
|
||||
@@ -362,7 +472,7 @@ module internal UnaryMetadataIlOp =
|
||||
| ResolvedBaseType.Enum
|
||||
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
||||
| ResolvedBaseType.Object -> SignatureTypeKind.Class
|
||||
| ResolvedBaseType.Delegate -> failwith "todo"
|
||||
| ResolvedBaseType.Delegate -> SignatureTypeKind.Class
|
||||
|
||||
TypeDefn.FromDefinition (ComparableTypeDefinitionHandle.Make td, activeAssy.Name.FullName, sigType)
|
||||
| MetadataToken.TypeSpecification handle -> state.ActiveAssembly(thread).TypeSpecs.[handle].Signature
|
||||
@@ -404,7 +514,7 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
state, field
|
||||
| MetadataToken.MemberReference mr ->
|
||||
let state, _, field =
|
||||
let state, _, field, _ =
|
||||
IlMachineState.resolveMember loggerFactory baseClassTypes thread activeAssy mr state
|
||||
|
||||
match field with
|
||||
@@ -425,10 +535,8 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
let valueToStore, state = IlMachineState.popEvalStack thread state
|
||||
|
||||
let typeGenerics =
|
||||
match field.DeclaringType.Generics with
|
||||
| [] -> None
|
||||
| l -> Some (ImmutableArray.CreateRange l)
|
||||
let state, declaringTypeHandle, typeGenerics =
|
||||
IlMachineState.concretizeFieldForExecution loggerFactory baseClassTypes thread field state
|
||||
|
||||
let state, zero =
|
||||
IlMachineState.cliTypeZeroOf
|
||||
@@ -437,7 +545,7 @@ module internal UnaryMetadataIlOp =
|
||||
(state.ActiveAssembly thread)
|
||||
field.Signature
|
||||
typeGenerics
|
||||
None // field can't have its own generics
|
||||
ImmutableArray.Empty // field can't have its own generics
|
||||
state
|
||||
|
||||
let valueToStore = EvalStackValue.toCliTypeCoerced zero valueToStore
|
||||
@@ -446,7 +554,7 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
if field.Attributes.HasFlag FieldAttributes.Static then
|
||||
let state =
|
||||
IlMachineState.setStatic field.DeclaringType field.Name valueToStore state
|
||||
IlMachineState.setStatic declaringTypeHandle field.Name valueToStore state
|
||||
|
||||
state, WhatWeDid.Executed
|
||||
else
|
||||
@@ -464,6 +572,8 @@ module internal UnaryMetadataIlOp =
|
||||
state
|
||||
|> IlMachineState.setLocalVariable sourceThread methodFrame whichVar valueToStore
|
||||
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> failwith "todo"
|
||||
| ManagedPointerSource.ArrayIndex (arr, index) ->
|
||||
state |> IlMachineState.setArrayValue arr valueToStore index
|
||||
| ManagedPointerSource.Heap addr ->
|
||||
match state.ManagedHeap.NonArrayObjects.TryGetValue addr with
|
||||
| false, _ -> failwith $"todo: array {addr}"
|
||||
@@ -501,7 +611,7 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
state, field
|
||||
| MetadataToken.MemberReference mr ->
|
||||
let state, _, method =
|
||||
let state, _, method, _ =
|
||||
IlMachineState.resolveMember
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
@@ -530,17 +640,15 @@ module internal UnaryMetadataIlOp =
|
||||
field.Signature
|
||||
)
|
||||
|
||||
match IlMachineState.loadClass loggerFactory baseClassTypes field.DeclaringType thread state with
|
||||
let state, declaringTypeHandle, typeGenerics =
|
||||
IlMachineState.concretizeFieldForExecution loggerFactory baseClassTypes thread field state
|
||||
|
||||
match IlMachineState.loadClass loggerFactory baseClassTypes declaringTypeHandle thread state with
|
||||
| FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit
|
||||
| NothingToDo state ->
|
||||
|
||||
let popped, state = IlMachineState.popEvalStack thread state
|
||||
|
||||
let typeGenerics =
|
||||
match field.DeclaringType.Generics with
|
||||
| [] -> None
|
||||
| l -> Some (ImmutableArray.CreateRange l)
|
||||
|
||||
let state, zero =
|
||||
IlMachineState.cliTypeZeroOf
|
||||
loggerFactory
|
||||
@@ -548,13 +656,13 @@ module internal UnaryMetadataIlOp =
|
||||
activeAssy
|
||||
field.Signature
|
||||
typeGenerics
|
||||
None // field can't have its own generics
|
||||
ImmutableArray.Empty // field can't have its own generics
|
||||
state
|
||||
|
||||
let toStore = EvalStackValue.toCliTypeCoerced zero popped
|
||||
|
||||
let state =
|
||||
IlMachineState.setStatic field.DeclaringType field.Name toStore state
|
||||
IlMachineState.setStatic declaringTypeHandle field.Name toStore state
|
||||
|> IlMachineState.advanceProgramCounter thread
|
||||
|
||||
state, WhatWeDid.Executed
|
||||
@@ -569,7 +677,7 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
state, field
|
||||
| MetadataToken.MemberReference mr ->
|
||||
let state, assyName, field =
|
||||
let state, assyName, field, _ =
|
||||
IlMachineState.resolveMember loggerFactory baseClassTypes thread activeAssy mr state
|
||||
|
||||
match field with
|
||||
@@ -591,14 +699,15 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
let currentObj, state = IlMachineState.popEvalStack thread state
|
||||
|
||||
let typeGenerics =
|
||||
match field.DeclaringType.Generics with
|
||||
| [] -> None
|
||||
| l -> Some (ImmutableArray.CreateRange l)
|
||||
let state, declaringTypeHandle, typeGenerics =
|
||||
IlMachineState.concretizeFieldForExecution loggerFactory baseClassTypes thread field state
|
||||
|
||||
if field.Attributes.HasFlag FieldAttributes.Static then
|
||||
let declaringTypeHandle, state =
|
||||
IlMachineState.concretizeFieldDeclaringType loggerFactory baseClassTypes field.DeclaringType state
|
||||
|
||||
let state, staticField =
|
||||
match IlMachineState.getStatic field.DeclaringType field.Name state with
|
||||
match IlMachineState.getStatic declaringTypeHandle field.Name state with
|
||||
| Some v -> state, v
|
||||
| None ->
|
||||
let state, zero =
|
||||
@@ -608,10 +717,10 @@ module internal UnaryMetadataIlOp =
|
||||
(state.LoadedAssembly(field.DeclaringType.Assembly).Value)
|
||||
field.Signature
|
||||
typeGenerics
|
||||
None // field can't have its own generics
|
||||
ImmutableArray.Empty // field can't have its own generics
|
||||
state
|
||||
|
||||
let state = IlMachineState.setStatic field.DeclaringType field.Name zero state
|
||||
let state = IlMachineState.setStatic declaringTypeHandle field.Name zero state
|
||||
state, zero
|
||||
|
||||
let state = state |> IlMachineState.pushToEvalStack staticField thread
|
||||
@@ -641,9 +750,16 @@ module internal UnaryMetadataIlOp =
|
||||
match state.ManagedHeap.NonArrayObjects.TryGetValue managedHeapAddress with
|
||||
| false, _ -> failwith $"todo: array {managedHeapAddress}"
|
||||
| true, v -> IlMachineState.pushToEvalStack v.Fields.[field.Name] thread state
|
||||
| ManagedPointerSource.ArrayIndex (arr, index) ->
|
||||
let currentValue = state |> IlMachineState.getArrayValue arr index
|
||||
IlMachineState.pushToEvalStack currentValue thread state
|
||||
| ManagedPointerSource.Null -> failwith "TODO: raise NullReferenceException"
|
||||
| EvalStackValue.ObjectRef managedHeapAddress -> failwith $"todo: {managedHeapAddress}"
|
||||
| EvalStackValue.UserDefinedValueType _ as udvt -> IlMachineState.pushToEvalStack' udvt thread state
|
||||
| EvalStackValue.UserDefinedValueType fields ->
|
||||
let result =
|
||||
fields |> List.pick (fun (k, v) -> if k = field.Name then Some v else None)
|
||||
|
||||
IlMachineState.pushToEvalStack' result thread state
|
||||
|
||||
state
|
||||
|> IlMachineState.advanceProgramCounter thread
|
||||
@@ -665,7 +781,7 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
state, field
|
||||
| MetadataToken.MemberReference mr ->
|
||||
let state, _, field =
|
||||
let state, _, field, _ =
|
||||
IlMachineState.resolveMember loggerFactory baseClassTypes thread activeAssy mr state
|
||||
|
||||
match field with
|
||||
@@ -687,17 +803,15 @@ module internal UnaryMetadataIlOp =
|
||||
field.Signature
|
||||
)
|
||||
|
||||
match IlMachineState.loadClass loggerFactory baseClassTypes field.DeclaringType thread state with
|
||||
let state, declaringTypeHandle, typeGenerics =
|
||||
IlMachineState.concretizeFieldForExecution loggerFactory baseClassTypes thread field state
|
||||
|
||||
match IlMachineState.loadClass loggerFactory baseClassTypes declaringTypeHandle thread state with
|
||||
| FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit
|
||||
| NothingToDo state ->
|
||||
|
||||
let typeGenerics =
|
||||
match field.DeclaringType.Generics with
|
||||
| [] -> None
|
||||
| l -> Some (ImmutableArray.CreateRange l)
|
||||
|
||||
let fieldValue, state =
|
||||
match IlMachineState.getStatic field.DeclaringType field.Name state with
|
||||
match IlMachineState.getStatic declaringTypeHandle field.Name state with
|
||||
| None ->
|
||||
let state, newVal =
|
||||
IlMachineState.cliTypeZeroOf
|
||||
@@ -706,10 +820,10 @@ module internal UnaryMetadataIlOp =
|
||||
activeAssy
|
||||
field.Signature
|
||||
typeGenerics
|
||||
None // field can't have its own generics
|
||||
ImmutableArray.Empty // field can't have its own generics
|
||||
state
|
||||
|
||||
newVal, IlMachineState.setStatic field.DeclaringType field.Name newVal state
|
||||
newVal, IlMachineState.setStatic declaringTypeHandle field.Name newVal state
|
||||
| Some v -> v, state
|
||||
|
||||
do
|
||||
@@ -743,9 +857,7 @@ module internal UnaryMetadataIlOp =
|
||||
let currentMethod = state.ThreadState.[thread].MethodState.ExecutingMethod
|
||||
|
||||
let declaringTypeGenerics =
|
||||
match currentMethod.DeclaringType.Generics with
|
||||
| [] -> None
|
||||
| x -> Some (ImmutableArray.CreateRange x)
|
||||
currentMethod.DeclaringType.Generics |> ImmutableArray.CreateRange
|
||||
|
||||
let state, assy, elementType =
|
||||
match metadataToken with
|
||||
@@ -753,16 +865,19 @@ module internal UnaryMetadataIlOp =
|
||||
state,
|
||||
assy,
|
||||
assy.TypeDefs.[defn]
|
||||
|> TypeInfo.mapGeneric (fun _ i -> declaringTypeGenerics.Value.[i.SequenceNumber])
|
||||
|> TypeInfo.mapGeneric (fun _ p -> TypeDefn.GenericTypeParameter p.SequenceNumber)
|
||||
| MetadataToken.TypeSpecification spec ->
|
||||
IlMachineState.resolveTypeFromSpec
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
spec
|
||||
assy
|
||||
declaringTypeGenerics
|
||||
currentMethod.Generics
|
||||
state
|
||||
let state, assy, ty =
|
||||
IlMachineState.resolveTypeFromSpecConcrete
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
spec
|
||||
assy
|
||||
declaringTypeGenerics
|
||||
currentMethod.Generics
|
||||
state
|
||||
|
||||
state, assy, ty
|
||||
| x -> failwith $"TODO: Stelem element type resolution unimplemented for {x}"
|
||||
|
||||
let contents, state = IlMachineState.popEvalStack thread state
|
||||
@@ -787,13 +902,7 @@ module internal UnaryMetadataIlOp =
|
||||
_.Name
|
||||
(fun x y -> x.TypeDefs.[y])
|
||||
(fun x y -> x.TypeRefs.[y] |> failwithf "%+A")
|
||||
(elementType
|
||||
|> TypeInfo.mapGeneric (fun i _ ->
|
||||
{
|
||||
Name = "<unknown>"
|
||||
SequenceNumber = i
|
||||
}
|
||||
))
|
||||
elementType
|
||||
|
||||
let state, zeroOfType =
|
||||
IlMachineState.cliTypeZeroOf
|
||||
@@ -802,7 +911,7 @@ module internal UnaryMetadataIlOp =
|
||||
assy
|
||||
elementType
|
||||
declaringTypeGenerics
|
||||
None
|
||||
ImmutableArray.Empty
|
||||
state
|
||||
|
||||
let contents = EvalStackValue.toCliTypeCoerced zeroOfType contents
|
||||
@@ -818,9 +927,7 @@ module internal UnaryMetadataIlOp =
|
||||
let currentMethod = state.ThreadState.[thread].MethodState.ExecutingMethod
|
||||
|
||||
let declaringTypeGenerics =
|
||||
match currentMethod.DeclaringType.Generics with
|
||||
| [] -> None
|
||||
| x -> Some (ImmutableArray.CreateRange x)
|
||||
currentMethod.DeclaringType.Generics |> ImmutableArray.CreateRange
|
||||
|
||||
let state, assy, elementType =
|
||||
match metadataToken with
|
||||
@@ -828,16 +935,19 @@ module internal UnaryMetadataIlOp =
|
||||
state,
|
||||
assy,
|
||||
assy.TypeDefs.[defn]
|
||||
|> TypeInfo.mapGeneric (fun _ i -> declaringTypeGenerics.Value.[i.SequenceNumber])
|
||||
|> TypeInfo.mapGeneric (fun _ p -> TypeDefn.GenericTypeParameter p.SequenceNumber)
|
||||
| MetadataToken.TypeSpecification spec ->
|
||||
IlMachineState.resolveTypeFromSpec
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
spec
|
||||
assy
|
||||
declaringTypeGenerics
|
||||
currentMethod.Generics
|
||||
state
|
||||
let state, assy, ty =
|
||||
IlMachineState.resolveTypeFromSpecConcrete
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
spec
|
||||
assy
|
||||
declaringTypeGenerics
|
||||
currentMethod.Generics
|
||||
state
|
||||
|
||||
state, assy, ty
|
||||
| x -> failwith $"TODO: Ldelem element type resolution unimplemented for {x}"
|
||||
|
||||
let index, state = IlMachineState.popEvalStack thread state
|
||||
@@ -864,7 +974,7 @@ module internal UnaryMetadataIlOp =
|
||||
else
|
||||
failwith "TODO: raise an out of bounds"
|
||||
|
||||
failwith $"TODO: Ldelem {index} {arr} resulted in {toPush}"
|
||||
IlMachineState.pushToEvalStack toPush thread state
|
||||
|> IlMachineState.advanceProgramCounter thread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Initobj -> failwith "TODO: Initobj unimplemented"
|
||||
@@ -882,22 +992,20 @@ module internal UnaryMetadataIlOp =
|
||||
|> FieldInfo.mapTypeGenerics (fun _ _ -> failwith "generics not allowed on FieldDefinition")
|
||||
| t -> failwith $"Unexpectedly asked to load a non-field: {t}"
|
||||
|
||||
match IlMachineState.loadClass loggerFactory baseClassTypes field.DeclaringType thread state with
|
||||
let state, declaringTypeHandle, typeGenerics =
|
||||
IlMachineState.concretizeFieldForExecution loggerFactory baseClassTypes thread field state
|
||||
|
||||
match IlMachineState.loadClass loggerFactory baseClassTypes declaringTypeHandle thread state with
|
||||
| FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit
|
||||
| NothingToDo state ->
|
||||
|
||||
if TypeDefn.isManaged field.Signature then
|
||||
match IlMachineState.getStatic field.DeclaringType field.Name state with
|
||||
match IlMachineState.getStatic declaringTypeHandle field.Name state with
|
||||
| Some v ->
|
||||
IlMachineState.pushToEvalStack v thread state
|
||||
|> IlMachineState.advanceProgramCounter thread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| None ->
|
||||
let typeGenerics =
|
||||
match field.DeclaringType.Generics with
|
||||
| [] -> None
|
||||
| l -> Some (ImmutableArray.CreateRange l)
|
||||
|
||||
// Field is not yet initialised
|
||||
let state, zero =
|
||||
IlMachineState.cliTypeZeroOf
|
||||
@@ -906,10 +1014,10 @@ module internal UnaryMetadataIlOp =
|
||||
activeAssy
|
||||
field.Signature
|
||||
typeGenerics
|
||||
None // field can't have its own generics
|
||||
ImmutableArray.Empty // field can't have its own generics
|
||||
state
|
||||
|
||||
IlMachineState.setStatic field.DeclaringType field.Name zero state
|
||||
IlMachineState.setStatic declaringTypeHandle field.Name zero state
|
||||
|> IlMachineState.pushToEvalStack (CliType.ObjectRef None) thread
|
||||
|> IlMachineState.advanceProgramCounter thread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
@@ -919,11 +1027,37 @@ module internal UnaryMetadataIlOp =
|
||||
| Ldftn ->
|
||||
let logger = loggerFactory.CreateLogger "Ldftn"
|
||||
|
||||
let method =
|
||||
let (method : MethodInfo<TypeDefn, WoofWare.PawPrint.GenericParameter, TypeDefn>), methodGenerics =
|
||||
match metadataToken with
|
||||
| MetadataToken.MethodDef handle -> activeAssy.Methods.[handle]
|
||||
| MetadataToken.MethodDef handle ->
|
||||
let method =
|
||||
activeAssy.Methods.[handle]
|
||||
|> MethodInfo.mapTypeGenerics (fun i _ -> TypeDefn.GenericTypeParameter i)
|
||||
|
||||
method, None
|
||||
| MetadataToken.MethodSpecification h ->
|
||||
let spec = activeAssy.MethodSpecs.[h]
|
||||
|
||||
match spec.Method with
|
||||
| MetadataToken.MethodDef token ->
|
||||
let method =
|
||||
activeAssy.Methods.[token]
|
||||
|> MethodInfo.mapTypeGenerics (fun i _ -> TypeDefn.GenericTypeParameter i)
|
||||
|
||||
method, Some spec.Signature
|
||||
| k -> failwith $"Unrecognised MethodSpecification kind: %O{k}"
|
||||
| t -> failwith $"Unexpectedly asked to Ldftn a non-method: {t}"
|
||||
|
||||
let state, concretizedMethod, _declaringTypeHandle =
|
||||
IlMachineState.concretizeMethodForExecution
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
thread
|
||||
method
|
||||
methodGenerics
|
||||
None
|
||||
state
|
||||
|
||||
logger.LogDebug (
|
||||
"Pushed pointer to function {LdFtnAssembly}.{LdFtnType}.{LdFtnMethodName}",
|
||||
method.DeclaringType.Assembly.Name,
|
||||
@@ -933,7 +1067,7 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack'
|
||||
(EvalStackValue.NativeInt (NativeIntSource.FunctionPointer method))
|
||||
(EvalStackValue.NativeInt (NativeIntSource.FunctionPointer concretizedMethod))
|
||||
thread
|
||||
|> IlMachineState.advanceProgramCounter thread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
@@ -965,8 +1099,7 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
let currentMethod = state.ThreadState.[thread].MethodState
|
||||
|
||||
let methodGenerics =
|
||||
currentMethod.Generics |> Option.defaultValue ImmutableArray.Empty
|
||||
let methodGenerics = currentMethod.Generics
|
||||
|
||||
let typeGenerics = currentMethod.ExecutingMethod.DeclaringType.Generics
|
||||
|
||||
@@ -980,7 +1113,10 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
let (_, alloc), state = IlMachineState.getOrAllocateType baseClassTypes handle state
|
||||
|
||||
IlMachineState.pushToEvalStack (CliType.ValueType [ CliType.ObjectRef (Some alloc) ]) thread state
|
||||
IlMachineState.pushToEvalStack
|
||||
(CliType.ValueType [ "m_type", CliType.ObjectRef (Some alloc) ])
|
||||
thread
|
||||
state
|
||||
| _ -> failwith $"Unexpected metadata token %O{metadataToken} in LdToken"
|
||||
|
||||
state
|
||||
|
@@ -52,11 +52,7 @@ module internal UnaryStringTokenIlOp =
|
||||
]
|
||||
|
||||
let addr, state =
|
||||
IlMachineState.allocateManagedObject
|
||||
(baseClassTypes.String
|
||||
|> TypeInfo.mapGeneric (fun _ _ -> failwith<unit> "string is not generic"))
|
||||
fields
|
||||
state
|
||||
IlMachineState.allocateManagedObject baseClassTypes.String fields state
|
||||
|
||||
addr,
|
||||
{ state with
|
||||
|
@@ -16,6 +16,8 @@
|
||||
<Compile Include="TypeInitialisation.fs" />
|
||||
<Compile Include="Exceptions.fs" />
|
||||
<Compile Include="EvalStack.fs" />
|
||||
<Compile Include="EvalStackValueComparisons.fs" />
|
||||
<Compile Include="BinaryArithmetic.fs" />
|
||||
<Compile Include="MethodState.fs" />
|
||||
<Compile Include="ThreadState.fs" />
|
||||
<Compile Include="IlMachineState.fs" />
|
||||
|
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1747467164,
|
||||
"narHash": "sha256-JBXbjJ0t6T6BbVc9iPVquQI9XSXCGQJD8c8SgnUquus=",
|
||||
"lastModified": 1750836778,
|
||||
"narHash": "sha256-sRLyRiC7TezRbbjGJwUFOgb2xMbSr3wQ0oJKfYlQ6s0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3fcbdcfc707e0aa42c541b7743e05820472bdaec",
|
||||
"rev": "d7bb1922f0bb3d0c990f56f9cdb767fdb20a5f22",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@@ -12,7 +12,10 @@
|
||||
...
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
pname = "WoofWare.PawPrint";
|
||||
dotnet-sdk = pkgs.dotnetCorePackages.sdk_9_0;
|
||||
dotnet-runtime = pkgs.dotnetCorePackages.runtime_9_0;
|
||||
@@ -67,6 +70,7 @@
|
||||
pkgs.nodePackages.markdown-link-check
|
||||
pkgs.shellcheck
|
||||
pkgs.xmlstarlet
|
||||
pkgs.claude-code
|
||||
];
|
||||
};
|
||||
});
|
||||
|
120
nix/deps.json
120
nix/deps.json
@@ -26,8 +26,8 @@
|
||||
},
|
||||
{
|
||||
"pname": "FsUnit",
|
||||
"version": "7.0.1",
|
||||
"hash": "sha256-K85CIdxMeFSHEKZk6heIXp/oFjWAn7dBILKrw49pJUY="
|
||||
"version": "7.1.1",
|
||||
"hash": "sha256-UMCEGKxQ4ytjmPuVpiNaAPbi3RQH9gqa61JJIUS/6hg="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.ApplicationInsights",
|
||||
@@ -36,108 +36,118 @@
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Ref",
|
||||
"version": "8.0.15",
|
||||
"hash": "sha256-kqHuc031x46OTy7Mr2C86lLgXK83Uo6swvEjU8yZJ6c="
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-NNGXfUV5RVt1VqLI99NlHoBkt2Vv/Hg3TAHzm8nGM8M="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Runtime.linux-arm64",
|
||||
"version": "8.0.15",
|
||||
"hash": "sha256-9E6nEfEMGqAHAv3GFNOrz7JqZnqskM++6w92YMyDUT0="
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-Eunz3nZF5r8a9nqwdeorQPgqd5G+Z4ddofMeAk6VmnA="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Runtime.linux-x64",
|
||||
"version": "8.0.15",
|
||||
"hash": "sha256-bAUGsjDxo5VHXo/IydzmjTixAhWh4lYpCmuUFpEo8ok="
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-SWdah72tC5i2CQL4mRUYfHC0Kh8+C2jiskIIeC74smY="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Runtime.osx-arm64",
|
||||
"version": "8.0.15",
|
||||
"hash": "sha256-fS/n2UQViU1TJqQbLWk3waURqPQWn1zd5ad5L7i8ZGM="
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-y55EGfQ2FzrY2X5+Ne5N3dqi5WNHkFTGVW1hEMrh6OI="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Runtime.osx-x64",
|
||||
"version": "8.0.15",
|
||||
"hash": "sha256-IZBqDYmUVPuujfhKGwXnbsNhrMSDy/olXDjVWKGb5Y0="
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-uRCCNPevPemvKIuUxy/VtQlgskChbiAauMWVK/xhoc0="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.CodeAnalysis.Analyzers",
|
||||
"version": "3.3.4",
|
||||
"hash": "sha256-qDzTfZBSCvAUu9gzq2k+LOvh6/eRvJ9++VCNck/ZpnE="
|
||||
"version": "3.11.0",
|
||||
"hash": "sha256-hQ2l6E6PO4m7i+ZsfFlEx+93UsLPo4IY3wDkNG11/Sw="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.CodeAnalysis.Common",
|
||||
"version": "4.8.0",
|
||||
"hash": "sha256-3IEinVTZq6/aajMVA8XTRO3LTIEt0PuhGyITGJLtqz4="
|
||||
"version": "4.14.0",
|
||||
"hash": "sha256-ne/zxH3GqoGB4OemnE8oJElG5mai+/67ASaKqwmL2BE="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.CodeAnalysis.CSharp",
|
||||
"version": "4.8.0",
|
||||
"hash": "sha256-MmOnXJvd/ezs5UPcqyGLnbZz5m+VedpRfB+kFZeeqkU="
|
||||
"version": "4.14.0",
|
||||
"hash": "sha256-5Mzj3XkYYLkwDWh17r1NEXSbXwwWYQPiOmkSMlgo1JY="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.CodeCoverage",
|
||||
"version": "17.13.0",
|
||||
"hash": "sha256-GKrIxeyQo5Az1mztfQgea1kGtJwonnNOrXK/0ULfu8o="
|
||||
"version": "17.14.1",
|
||||
"hash": "sha256-f8QytG8GvRoP47rO2KEmnDLxIpyesaq26TFjDdW40Gs="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.DependencyInjection.Abstractions",
|
||||
"version": "9.0.2",
|
||||
"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",
|
||||
"version": "9.0.2",
|
||||
"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",
|
||||
"version": "17.13.0",
|
||||
"hash": "sha256-sc2wvyV8cGm1FrNP2GGHEI584RCvRPu15erYCsgw5QY="
|
||||
"version": "17.14.1",
|
||||
"hash": "sha256-mZUzDFvFp7x1nKrcnRd0hhbNu5g8EQYt8SKnRgdhT/A="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Host.linux-arm64",
|
||||
"version": "8.0.15",
|
||||
"hash": "sha256-AKPshSm9pO8nG8ksSVfWOHUAv92B/dMS/QwE33rUiCI="
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-pzOqFCd+UrIXmWGDfds5GxkI+Asjx30yFtLIuHFu/h4="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Host.linux-x64",
|
||||
"version": "8.0.15",
|
||||
"hash": "sha256-EmkuXpbpKVyghOw5JcVP2l3gQUebYHw+i+6romHTQyo="
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-AGnEGHcO2hfvChG3xEGOTA6dX4MiYPB7FoBkmWz3dc8="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Host.osx-arm64",
|
||||
"version": "8.0.15",
|
||||
"hash": "sha256-fBucka/K+3dwVrPUU9RLTA4xKzJurWpaxI6OYH/F5WE="
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-fpMzkOWaA3OFNtHsqOk9s9xKVrcrqOyKHxE7jk8hebg="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Host.osx-x64",
|
||||
"version": "8.0.15",
|
||||
"hash": "sha256-uZywV0js9dMNRWipBRO5M/l+7dJpPJWQu6wa5rV2sKA="
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-Hrn01x+S+gnGEEHhr6mN6bPyqVAhp5u3CqgWwQbh4To="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Ref",
|
||||
"version": "8.0.15",
|
||||
"hash": "sha256-Y5/MxR0FnKnjgJ5p+7qk/VbabjCrA8bdR4Dkm20javU="
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-tKawpjkMjV0ysNIWWrgHTiLxncZJDRNiDkQBwl255l4="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Runtime.linux-arm64",
|
||||
"version": "8.0.15",
|
||||
"hash": "sha256-RksQhngGt+9J4d18K0oZT6L9ZpoiJ4T7D+DEEfhAXP0="
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-FutphE4bEjd8s6ZqpFXrD1zuCDkNCJ7Vnl0pBm86HBA="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Runtime.linux-x64",
|
||||
"version": "8.0.15",
|
||||
"hash": "sha256-iO6pkXm4UouTsUpCeXjStckUxomy6pxrbOnM+zaR9a0="
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-6YVEXiJ3b2gZAYri8iSRBdi/J+0DEl7FcwBX6h1Unkg="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Runtime.osx-arm64",
|
||||
"version": "8.0.15",
|
||||
"hash": "sha256-qUo70LnzZC+nVz1yvLg0R8u5gxuJawoMB8fzkcoCosw="
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-J3dfDial8GHyKQMFuBNFtOMD/mOK58vjrK2ZtrYObZg="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Runtime.osx-x64",
|
||||
"version": "8.0.15",
|
||||
"hash": "sha256-qraVbQtSG/gVKXAy2PHi1ua7SY6iasveIN2ayYFmE9U="
|
||||
"version": "8.0.17",
|
||||
"hash": "sha256-WnkJyhSBHMw/VtLHWy0AFwzzkbIC1YQugFuj3Adg+Ks="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.Platforms",
|
||||
@@ -191,13 +201,13 @@
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.TestPlatform.ObjectModel",
|
||||
"version": "17.13.0",
|
||||
"hash": "sha256-6S0fjfj8vA+h6dJVNwLi6oZhYDO/I/6hBZaq2VTW+Uk="
|
||||
"version": "17.14.1",
|
||||
"hash": "sha256-QMf6O+w0IT+16Mrzo7wn+N20f3L1/mDhs/qjmEo1rYs="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.TestPlatform.TestHost",
|
||||
"version": "17.13.0",
|
||||
"hash": "sha256-L/CJzou7dhmShUgXq3aXL3CaLTJll17Q+JY2DBdUUpo="
|
||||
"version": "17.14.1",
|
||||
"hash": "sha256-1cxHWcvHRD7orQ3EEEPPxVGEkTpxom1/zoICC9SInJs="
|
||||
},
|
||||
{
|
||||
"pname": "Myriad.Core",
|
||||
@@ -216,8 +226,8 @@
|
||||
},
|
||||
{
|
||||
"pname": "Newtonsoft.Json",
|
||||
"version": "13.0.1",
|
||||
"hash": "sha256-K2tSVW4n4beRPzPu3rlVaBEMdGvWSv/3Q1fxaDh4Mjo="
|
||||
"version": "13.0.3",
|
||||
"hash": "sha256-hy/BieY4qxBWVVsDqqOPaLy1QobiIapkbrESm6v2PHc="
|
||||
},
|
||||
{
|
||||
"pname": "NUnit",
|
||||
@@ -246,8 +256,8 @@
|
||||
},
|
||||
{
|
||||
"pname": "System.Collections.Immutable",
|
||||
"version": "7.0.0",
|
||||
"hash": "sha256-9an2wbxue2qrtugYES9awshQg+KfJqajhnhs45kQIdk="
|
||||
"version": "9.0.0",
|
||||
"hash": "sha256-+6q5VMeoc5bm4WFsoV6nBXA9dV5pa/O4yW+gOdi8yac="
|
||||
},
|
||||
{
|
||||
"pname": "System.Diagnostics.DiagnosticSource",
|
||||
@@ -281,19 +291,19 @@
|
||||
},
|
||||
{
|
||||
"pname": "System.Reflection.Metadata",
|
||||
"version": "7.0.0",
|
||||
"hash": "sha256-GwAKQhkhPBYTqmRdG9c9taqrKSKDwyUgOEhWLKxWNPI="
|
||||
"version": "8.0.0",
|
||||
"hash": "sha256-dQGC30JauIDWNWXMrSNOJncVa1umR1sijazYwUDdSIE="
|
||||
},
|
||||
{
|
||||
"pname": "System.Reflection.Metadata",
|
||||
"version": "9.0.0",
|
||||
"hash": "sha256-avEWbcCh7XgpsSesnR3/SgxWi/6C5OxjR89Jf/SfRjQ="
|
||||
},
|
||||
{
|
||||
"pname": "System.Runtime",
|
||||
"version": "4.3.1",
|
||||
"hash": "sha256-R9T68AzS1PJJ7v6ARz9vo88pKL1dWqLOANg4pkQjkA0="
|
||||
},
|
||||
{
|
||||
"pname": "System.Runtime.CompilerServices.Unsafe",
|
||||
"version": "6.0.0",
|
||||
"hash": "sha256-bEG1PnDp7uKYz/OgLOWs3RWwQSVYm+AnPwVmAmcgp2I="
|
||||
},
|
||||
{
|
||||
"pname": "TypeEquality",
|
||||
"version": "0.3.0",
|
||||
|
Reference in New Issue
Block a user