25 Commits

Author SHA1 Message Date
Smaug123
4816fcaef2 Merge branch 'main' into castclass 2025-07-02 22:45:12 +01:00
Patrick Stevens
f39e7c07bf Concrete types - lots of tech debt in here (#79) 2025-07-02 22:41:13 +01:00
Patrick Stevens
ad8e625678 Fix stelem of nested generic (#86) 2025-07-02 21:22:16 +00:00
Patrick Stevens
0fc4335760 Add another generic for MethodInfo (#85) 2025-07-02 17:49:24 +00:00
Patrick Stevens
c79f775ce4 Add a type parameter on FieldInfo to represent signature (#84) 2025-07-02 17:41:28 +00:00
Patrick Stevens
b5f4ed6dec Add more comments (#83) 2025-07-02 17:29:54 +00:00
Patrick Stevens
af3e4f20f2 Implement shl, shr, or (#82) 2025-07-02 16:42:36 +00:00
Patrick Stevens
3d5667ebba Implement another conversion (#81) 2025-07-01 21:55:18 +00:00
Patrick Stevens
4dbb737648 Module method IsJITIntrinsic (#77) 2025-06-30 22:21:00 +01:00
Patrick Stevens
ddd6374c72 More corelib (#76) 2025-06-30 22:15:33 +01:00
Patrick Stevens
711bfd5aad Add namespace info to types (#75) 2025-06-27 21:41:53 +01:00
Patrick Stevens
84df17295b Implement another comparison case (#74) 2025-06-27 18:49:20 +00:00
Smaug123
fcc778d7aa Fix one test 2025-06-27 19:48:08 +01:00
Smaug123
9905bbf436 Merge branch 'main' into castclass 2025-06-27 15:10:04 +01:00
Patrick Stevens
641040509f Test float comparisons (#73) 2025-06-27 14:06:01 +00:00
Smaug123
e3b797705d Merge branch 'main' into castclass 2025-06-27 15:04:28 +01:00
Patrick Stevens
7c636b61a7 Implement Ldelem etc (#72) 2025-06-27 13:28:38 +00:00
Patrick Stevens
4352bfa218 Split binary ops into another file (#71) 2025-06-27 11:19:14 +00:00
Smaug123
277f303431 WIP 2025-06-27 12:09:15 +01:00
Smaug123
c049313dd9 Merge branch 'main' into castclass 2025-06-27 12:02:33 +01:00
Patrick Stevens
477cb9b3fb Delete spare files (#70) 2025-06-27 11:02:18 +00:00
Smaug123
ca36bb3eba Merge branch 'main' into castclass 2025-06-27 11:56:26 +01:00
Patrick Stevens
c859f88f52 Rejig test harness (#68) 2025-06-27 10:54:00 +00:00
Smaug123
1ebcd0b4f5 Implement Castclass 2025-06-27 11:41:41 +01:00
Patrick Stevens
5cf0789439 Bump flake, add claude-code (#66) 2025-06-25 23:40:20 +01:00
86 changed files with 5508 additions and 973 deletions

View File

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

View File

@@ -4,6 +4,8 @@
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<WarningsAsErrors>false</WarningsAsErrors>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
</Project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,6 +15,17 @@ type MethodSpec =
/// </summary>
Method : MetadataToken
/// <summary>
/// The actual type arguments for generic instantiation.
/// </summary>
/// <example>
/// For <c>Volatile.Read&lt;System.IO.TextWriter&gt;</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
}

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

View File

@@ -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&lt;T&gt;</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&lt;'a, 'b&gt;</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

View File

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

View File

@@ -26,6 +26,7 @@
<Compile Include="ExportedType.fs" />
<Compile Include="TypeSpec.fs" />
<Compile Include="Assembly.fs" />
<Compile Include="TypeConcretisation.fs" />
</ItemGroup>
<ItemGroup>

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

View File

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

View File

@@ -30,5 +30,5 @@ type TestCase =
FileName : string
ExpectedReturnCode : int
NativeImpls : NativeImpls
LocalVariablesOfMain : CliType list
LocalVariablesOfMain : CliType list option
}

View File

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

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

View File

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

View File

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

View File

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

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

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

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

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

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

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

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

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

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

View File

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

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

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

View File

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

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

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

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

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

View File

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -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": {

View File

@@ -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
];
};
});

View File

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