mirror of
https://github.com/Smaug123/WoofWare.PawPrint
synced 2025-10-10 16:28:41 +00:00
1694 lines
68 KiB
Forth
1694 lines
68 KiB
Forth
namespace WoofWare.PawPrint
|
|
|
|
open System
|
|
open System.Collections.Immutable
|
|
open System.IO
|
|
open System.Reflection
|
|
open System.Reflection.Metadata
|
|
open Microsoft.Extensions.Logging
|
|
open Microsoft.FSharp.Core
|
|
|
|
type IlMachineState =
|
|
{
|
|
ConcreteTypes : AllConcreteTypes
|
|
Logger : ILogger
|
|
NextThreadId : int
|
|
// CallStack : StackFrame list
|
|
/// Multiple managed heaps are allowed, but we hopefully only need one.
|
|
ManagedHeap : ManagedHeap
|
|
ThreadState : Map<ThreadId, ThreadState>
|
|
InternedStrings : ImmutableDictionary<StringToken, ManagedHeapAddress>
|
|
/// Keyed by FullName. (Sometimes an assembly has a PublicKey when we read it from the disk, but we
|
|
/// only have a reference to it by an AssemblyName without a PublicKey.)
|
|
_LoadedAssemblies : ImmutableDictionary<string, DumpedAssembly>
|
|
/// Tracks initialization state of types across assemblies
|
|
TypeInitTable : TypeInitTable
|
|
/// For each type, specialised to each set of generic args, a map of string field name to static value contained therein.
|
|
_Statics : ImmutableDictionary<ConcreteTypeHandle, ImmutableDictionary<string, CliType>>
|
|
DotnetRuntimeDirs : string ImmutableArray
|
|
TypeHandles : TypeHandleRegistry
|
|
FieldHandles : FieldHandleRegistry
|
|
}
|
|
|
|
member this.WithTypeBeginInit (thread : ThreadId) (ty : ConcreteTypeHandle) =
|
|
let concreteType = AllConcreteTypes.lookup ty this.ConcreteTypes |> Option.get
|
|
|
|
this.Logger.LogDebug (
|
|
"Beginning initialisation of type {s_Assembly}.{TypeName}, handle {TypeDefinitionHandle}",
|
|
concreteType.Assembly.FullName,
|
|
this.LoadedAssembly(concreteType.Assembly).Value.TypeDefs.[concreteType.Definition.Get].Name,
|
|
concreteType.Definition.Get.GetHashCode ()
|
|
)
|
|
|
|
let typeInitTable = this.TypeInitTable |> TypeInitTable.beginInitialising thread ty
|
|
|
|
{ this with
|
|
TypeInitTable = typeInitTable
|
|
}
|
|
|
|
member this.WithTypeEndInit (thread : ThreadId) (ty : ConcreteTypeHandle) =
|
|
let concreteType = AllConcreteTypes.lookup ty this.ConcreteTypes |> Option.get
|
|
|
|
this.Logger.LogDebug (
|
|
"Marking complete initialisation of type {s_Assembly}.{TypeName}, handle {TypeDefinitionHandle}",
|
|
concreteType.Assembly.FullName,
|
|
this.LoadedAssembly(concreteType.Assembly).Value.TypeDefs.[concreteType.Definition.Get].Name,
|
|
concreteType.Definition.Get.GetHashCode ()
|
|
)
|
|
|
|
let typeInitTable = this.TypeInitTable |> TypeInitTable.markInitialised thread ty
|
|
|
|
{ this with
|
|
TypeInitTable = typeInitTable
|
|
}
|
|
|
|
member this.WithLoadedAssembly (name : AssemblyName) (value : DumpedAssembly) =
|
|
{ this with
|
|
_LoadedAssemblies = this._LoadedAssemblies.Add (name.FullName, value)
|
|
}
|
|
|
|
member this.LoadedAssembly' (fullName : string) : DumpedAssembly option =
|
|
match this._LoadedAssemblies.TryGetValue fullName with
|
|
| false, _ -> None
|
|
| true, v -> Some v
|
|
|
|
member this.LoadedAssembly (name : AssemblyName) : DumpedAssembly option = this.LoadedAssembly' name.FullName
|
|
|
|
/// Returns also the original assembly name.
|
|
member this.WithThreadSwitchedToAssembly (assy : AssemblyName) (thread : ThreadId) : IlMachineState * AssemblyName =
|
|
let mutable existing = Unchecked.defaultof<AssemblyName>
|
|
|
|
let newState =
|
|
this.ThreadState
|
|
|> Map.change
|
|
thread
|
|
(fun s ->
|
|
match s with
|
|
| None -> failwith $"expected thread {thread} to be in a state already; internal logic error"
|
|
| Some s ->
|
|
existing <- s.ActiveAssembly
|
|
|
|
{ s with
|
|
ActiveAssembly = assy
|
|
}
|
|
|> Some
|
|
)
|
|
|
|
{ this with
|
|
ThreadState = newState
|
|
},
|
|
existing
|
|
|
|
member this.ActiveAssembly (thread : ThreadId) =
|
|
let active = this.ThreadState.[thread].ActiveAssembly
|
|
|
|
match this.LoadedAssembly active with
|
|
| Some v -> v
|
|
| None ->
|
|
let available = this._LoadedAssemblies.Keys |> String.concat " ; "
|
|
|
|
failwith
|
|
$"Somehow we believe the active assembly is {active}, but only had the following available: {available}"
|
|
|
|
(*
|
|
Type load algorithm, from II.10.5.3.3
|
|
1. At class load-time (hence prior to initialization time) store zero or null into all static fields of the
|
|
type.
|
|
2. If the type is initialized, you are done.
|
|
2.1. If the type is not yet initialized, try to take an initialization lock.
|
|
2.2. If successful, record this thread as responsible for initializing the type and proceed to step 2.3.
|
|
2.2.1. If not successful, see whether this thread or any thread waiting for this thread to complete already
|
|
holds the lock.
|
|
2.2.2. If so, return since blocking would create a deadlock. This thread will now see an incompletely
|
|
initialized state for the type, but no deadlock will arise.
|
|
2.2.3 If not, block until the type is initialized then return.
|
|
2.3 Initialize the base class type and then all interfaces implemented by this type.
|
|
2.4 Execute the type initialization code for this type.
|
|
2.5 Mark the type as initialized, release the initialization lock, awaken any threads waiting for this type
|
|
to be initialized, and return.
|
|
*)
|
|
type WhatWeDid =
|
|
| Executed
|
|
/// We didn't run what you wanted, because we have to do class initialisation first.
|
|
| SuspendedForClassInit
|
|
/// We can't proceed until this thread has finished the class initialisation work it's doing.
|
|
| BlockedOnClassInit of threadBlockingUs : ThreadId
|
|
|
|
type ExecutionResult =
|
|
| Terminated of IlMachineState * terminatingThread : ThreadId
|
|
| Stepped of IlMachineState * WhatWeDid
|
|
|
|
type StateLoadResult =
|
|
/// The type is loaded; you can proceed.
|
|
| NothingToDo of IlMachineState
|
|
/// We didn't manage to load the requested type, because that type itself requires first loading something.
|
|
/// The state we give you is ready to load that something.
|
|
| FirstLoadThis of IlMachineState
|
|
|
|
[<RequireQualifiedAccess>]
|
|
module IlMachineState =
|
|
type private Dummy = class end
|
|
|
|
let private loadAssembly'
|
|
(loggerFactory : ILoggerFactory)
|
|
(dotnetRuntimeDirs : string seq)
|
|
(referencedInAssembly : DumpedAssembly)
|
|
(r : AssemblyReferenceHandle)
|
|
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
|
|
=
|
|
let assemblyRef = referencedInAssembly.AssemblyReferences.[r]
|
|
let assemblyName = assemblyRef.Name
|
|
|
|
match assemblies.TryGetValue assemblyName.FullName with
|
|
| true, v -> v, assemblyName
|
|
| false, _ ->
|
|
let logger = loggerFactory.CreateLogger typeof<Dummy>.DeclaringType
|
|
|
|
let assy =
|
|
dotnetRuntimeDirs
|
|
|> Seq.choose (fun dir ->
|
|
let file = Path.Combine (dir, assemblyName.Name + ".dll")
|
|
|
|
try
|
|
use f = File.OpenRead file
|
|
logger.LogInformation ("Loading assembly from file {AssemblyFileLoadPath}", file)
|
|
Assembly.read loggerFactory (Some file) f |> Some
|
|
with :? FileNotFoundException ->
|
|
None
|
|
)
|
|
|> Seq.toList
|
|
|
|
match assy |> List.tryHead with
|
|
| None -> failwith $"Could not find a readable DLL in any runtime dir with name %s{assemblyName.Name}.dll"
|
|
| Some assy -> assy, assemblyName
|
|
|
|
/// <summary>
|
|
/// Create a new IlMachineState which has loaded the given assembly.
|
|
/// This involves reading assemblies from the disk and doing a complete parse of them, so it might be quite slow!
|
|
///
|
|
/// This function doesn't do anything if the referenced assembly has already been loaded.
|
|
/// </summary>
|
|
/// <param name="loggerFactory">LoggerFactory into which to emit logs.</param>
|
|
/// <param name="referencedInAssembly">The assembly which contains an AssemblyReference which causes us to want to load a new assembly.</param>
|
|
/// <param name="r">The AssemblyReferenceHandle pointing at an assembly we want to load. *Important*: this is an AssemblyReferenceHandle from <c>referencedInAssembly</c>; in general, AssemblyReferenceHandles are only well-defined if you know what assembly they were defined in.</param>
|
|
/// <param name="state">The immutable state to augment with the new assembly.</param>
|
|
let loadAssembly
|
|
(loggerFactory : ILoggerFactory)
|
|
(referencedInAssembly : DumpedAssembly)
|
|
(r : AssemblyReferenceHandle)
|
|
(state : IlMachineState)
|
|
: IlMachineState * DumpedAssembly * AssemblyName
|
|
=
|
|
let dumped, assy =
|
|
loadAssembly' loggerFactory state.DotnetRuntimeDirs referencedInAssembly r state._LoadedAssemblies
|
|
|
|
state.WithLoadedAssembly assy dumped, dumped, assy
|
|
|
|
let private loader (loggerFactory : ILoggerFactory) (state : IlMachineState) : IAssemblyLoad =
|
|
{ new IAssemblyLoad with
|
|
member _.LoadAssembly loaded assyName ref =
|
|
let targetAssy, name =
|
|
loadAssembly' loggerFactory state.DotnetRuntimeDirs loaded.[assyName.FullName] ref loaded
|
|
|
|
let newAssys = loaded.SetItem (name.FullName, targetAssy)
|
|
newAssys, targetAssy
|
|
}
|
|
|
|
let concretizeType
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(state : IlMachineState)
|
|
(declaringAssembly : AssemblyName)
|
|
(typeGenerics : ImmutableArray<ConcreteTypeHandle>)
|
|
(methodGenerics : ImmutableArray<ConcreteTypeHandle>)
|
|
(ty : TypeDefn)
|
|
: IlMachineState * ConcreteTypeHandle
|
|
=
|
|
let ctx =
|
|
{
|
|
TypeConcretization.ConcretizationContext.InProgress = ImmutableDictionary.Empty
|
|
TypeConcretization.ConcretizationContext.ConcreteTypes = state.ConcreteTypes
|
|
TypeConcretization.ConcretizationContext.LoadedAssemblies = state._LoadedAssemblies
|
|
TypeConcretization.ConcretizationContext.BaseTypes = baseClassTypes
|
|
}
|
|
|
|
let handle, ctx =
|
|
TypeConcretization.concretizeType
|
|
ctx
|
|
(loader loggerFactory state)
|
|
declaringAssembly
|
|
typeGenerics
|
|
methodGenerics
|
|
ty
|
|
|
|
let state =
|
|
{ state with
|
|
_LoadedAssemblies = ctx.LoadedAssemblies
|
|
ConcreteTypes = ctx.ConcreteTypes
|
|
}
|
|
|
|
state, handle
|
|
|
|
let rec internal resolveTypeFromName
|
|
(loggerFactory : ILoggerFactory)
|
|
(ns : string option)
|
|
(name : string)
|
|
(genericArgs : ImmutableArray<TypeDefn>)
|
|
(assy : DumpedAssembly)
|
|
(state : IlMachineState)
|
|
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn, TypeDefn>
|
|
=
|
|
match Assembly.resolveTypeFromName assy state._LoadedAssemblies ns name genericArgs with
|
|
| TypeResolutionResult.Resolved (assy, typeDef) -> state, assy, typeDef
|
|
| TypeResolutionResult.FirstLoadAssy loadFirst ->
|
|
let state, _, _ =
|
|
loadAssembly
|
|
loggerFactory
|
|
state._LoadedAssemblies.[snd(loadFirst.Handle).FullName]
|
|
(fst loadFirst.Handle)
|
|
state
|
|
|
|
resolveTypeFromName loggerFactory ns name genericArgs assy state
|
|
|
|
and resolveTypeFromExport
|
|
(loggerFactory : ILoggerFactory)
|
|
(fromAssembly : DumpedAssembly)
|
|
(ty : WoofWare.PawPrint.ExportedType)
|
|
(genericArgs : ImmutableArray<TypeDefn>)
|
|
(state : IlMachineState)
|
|
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn, TypeDefn>
|
|
=
|
|
match Assembly.resolveTypeFromExport fromAssembly state._LoadedAssemblies ty genericArgs with
|
|
| TypeResolutionResult.Resolved (assy, typeDef) -> state, assy, typeDef
|
|
| TypeResolutionResult.FirstLoadAssy loadFirst ->
|
|
let state, targetAssy, _ =
|
|
loadAssembly
|
|
loggerFactory
|
|
state._LoadedAssemblies.[snd(loadFirst.Handle).FullName]
|
|
(fst loadFirst.Handle)
|
|
state
|
|
|
|
resolveTypeFromName loggerFactory ty.Namespace ty.Name genericArgs targetAssy state
|
|
|
|
and resolveTypeFromRef
|
|
(loggerFactory : ILoggerFactory)
|
|
(referencedInAssembly : DumpedAssembly)
|
|
(target : TypeRef)
|
|
(typeGenericArgs : ImmutableArray<TypeDefn>)
|
|
(state : IlMachineState)
|
|
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn, TypeDefn>
|
|
=
|
|
match Assembly.resolveTypeRef state._LoadedAssemblies referencedInAssembly typeGenericArgs target with
|
|
| TypeResolutionResult.Resolved (assy, typeDef) -> state, assy, typeDef
|
|
| TypeResolutionResult.FirstLoadAssy loadFirst ->
|
|
let state, _, _ =
|
|
loadAssembly
|
|
loggerFactory
|
|
state._LoadedAssemblies.[snd(loadFirst.Handle).FullName]
|
|
(fst loadFirst.Handle)
|
|
state
|
|
|
|
resolveTypeFromRef loggerFactory referencedInAssembly target typeGenericArgs state
|
|
|
|
and resolveType
|
|
(loggerFactory : ILoggerFactory)
|
|
(ty : TypeReferenceHandle)
|
|
(genericArgs : ImmutableArray<TypeDefn>)
|
|
(assy : DumpedAssembly)
|
|
(state : IlMachineState)
|
|
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn, TypeDefn>
|
|
=
|
|
let target = assy.TypeRefs.[ty]
|
|
|
|
resolveTypeFromRef loggerFactory assy target genericArgs state
|
|
|
|
let rec resolveTypeFromDefn
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(ty : TypeDefn)
|
|
(typeGenericArgs : ImmutableArray<TypeDefn>)
|
|
(methodGenericArgs : ImmutableArray<TypeDefn>)
|
|
(assy : DumpedAssembly)
|
|
(state : IlMachineState)
|
|
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn, TypeDefn>
|
|
=
|
|
match ty with
|
|
| TypeDefn.GenericInstantiation (generic, args) ->
|
|
let args' = ImmutableArray.CreateBuilder ()
|
|
|
|
let state =
|
|
(state, args)
|
|
||> Seq.fold (fun state arg ->
|
|
let state, assy, resolvedArg =
|
|
resolveTypeFromDefn
|
|
loggerFactory
|
|
baseClassTypes
|
|
arg
|
|
typeGenericArgs
|
|
methodGenericArgs
|
|
assy
|
|
state
|
|
|
|
let preservedArg =
|
|
DumpedAssembly.typeInfoToTypeDefn baseClassTypes state._LoadedAssemblies resolvedArg
|
|
|
|
args'.Add preservedArg
|
|
|
|
state
|
|
)
|
|
|
|
let args' = args'.ToImmutable ()
|
|
resolveTypeFromDefn loggerFactory baseClassTypes generic args' methodGenericArgs assy state
|
|
| TypeDefn.FromDefinition (defn, assy, _typeKind) ->
|
|
let assy = state._LoadedAssemblies.[assy]
|
|
|
|
let defn =
|
|
assy.TypeDefs.[defn.Get]
|
|
|> TypeInfo.mapGeneric (fun (param, _) -> typeGenericArgs.[param.SequenceNumber])
|
|
|
|
state, assy, defn
|
|
| TypeDefn.FromReference (ref, _typeKind) ->
|
|
let state, assy, ty =
|
|
resolveTypeFromRef loggerFactory assy ref typeGenericArgs state
|
|
|
|
state, assy, ty
|
|
| TypeDefn.PrimitiveType prim ->
|
|
let ty =
|
|
match prim with
|
|
| PrimitiveType.Boolean -> baseClassTypes.Boolean
|
|
| PrimitiveType.Char -> baseClassTypes.Char
|
|
| PrimitiveType.SByte -> baseClassTypes.SByte
|
|
| PrimitiveType.Byte -> baseClassTypes.Byte
|
|
| PrimitiveType.Int16 -> baseClassTypes.Int16
|
|
| PrimitiveType.UInt16 -> baseClassTypes.UInt16
|
|
| PrimitiveType.Int32 -> baseClassTypes.Int32
|
|
| PrimitiveType.UInt32 -> baseClassTypes.UInt32
|
|
| PrimitiveType.Int64 -> baseClassTypes.Int64
|
|
| PrimitiveType.UInt64 -> baseClassTypes.UInt64
|
|
| PrimitiveType.Single -> baseClassTypes.Single
|
|
| PrimitiveType.Double -> baseClassTypes.Double
|
|
| PrimitiveType.String -> baseClassTypes.String
|
|
| PrimitiveType.TypedReference -> failwith "todo"
|
|
| PrimitiveType.IntPtr -> baseClassTypes.IntPtr
|
|
| PrimitiveType.UIntPtr -> baseClassTypes.UIntPtr
|
|
| PrimitiveType.Object -> baseClassTypes.Object
|
|
|> TypeInfo.mapGeneric (fun _ -> failwith "none of these types are generic")
|
|
|
|
state, baseClassTypes.Corelib, ty
|
|
| TypeDefn.GenericTypeParameter param ->
|
|
let arg = typeGenericArgs.[param]
|
|
// TODO: this assembly is probably wrong?
|
|
resolveTypeFromDefn loggerFactory baseClassTypes arg typeGenericArgs methodGenericArgs assy state
|
|
| TypeDefn.GenericMethodParameter param ->
|
|
let arg = methodGenericArgs.[param]
|
|
// TODO: this assembly is probably wrong?
|
|
resolveTypeFromDefn loggerFactory baseClassTypes arg typeGenericArgs methodGenericArgs assy state
|
|
| s -> failwith $"TODO: resolveTypeFromDefn unimplemented for {s}"
|
|
|
|
let resolveTypeFromSpec
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(ty : TypeSpecificationHandle)
|
|
(assy : DumpedAssembly)
|
|
(typeGenericArgs : TypeDefn ImmutableArray)
|
|
(methodGenericArgs : TypeDefn ImmutableArray)
|
|
(state : IlMachineState)
|
|
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn, TypeDefn>
|
|
=
|
|
let sign = assy.TypeSpecs.[ty].Signature
|
|
resolveTypeFromDefn loggerFactory baseClassTypes sign typeGenericArgs methodGenericArgs assy state
|
|
|
|
/// Resolve a TypeSpecification using concrete type handles from execution context
|
|
let resolveTypeFromSpecConcrete
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(ty : TypeSpecificationHandle)
|
|
(assy : DumpedAssembly)
|
|
(typeGenericArgs : ConcreteTypeHandle ImmutableArray)
|
|
(methodGenericArgs : ConcreteTypeHandle ImmutableArray)
|
|
(state : IlMachineState)
|
|
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn, TypeDefn>
|
|
=
|
|
let sign = assy.TypeSpecs.[ty].Signature
|
|
|
|
// Convert ConcreteTypeHandle to TypeDefn
|
|
let typeGenericArgsAsDefn =
|
|
typeGenericArgs
|
|
|> ImmutableArray.map (fun handle ->
|
|
Concretization.concreteHandleToTypeDefn
|
|
baseClassTypes
|
|
handle
|
|
state.ConcreteTypes
|
|
state._LoadedAssemblies
|
|
)
|
|
|
|
let methodGenericArgsAsDefn =
|
|
methodGenericArgs
|
|
|> ImmutableArray.map (fun handle ->
|
|
Concretization.concreteHandleToTypeDefn
|
|
baseClassTypes
|
|
handle
|
|
state.ConcreteTypes
|
|
state._LoadedAssemblies
|
|
)
|
|
|
|
resolveTypeFromDefn loggerFactory baseClassTypes sign typeGenericArgsAsDefn methodGenericArgsAsDefn assy state
|
|
|
|
/// Resolve a TypeDefinition using concrete type handles from execution context
|
|
let resolveTypeFromDefnConcrete
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(ty : TypeDefinitionHandle)
|
|
(assy : DumpedAssembly)
|
|
(typeGenericArgs : ConcreteTypeHandle ImmutableArray)
|
|
(methodGenericArgs : ConcreteTypeHandle ImmutableArray)
|
|
(state : IlMachineState)
|
|
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn, TypeDefn>
|
|
=
|
|
let typeDef = assy.TypeDefs.[ty]
|
|
|
|
// Convert ConcreteTypeHandle to TypeDefn for the generics
|
|
let typeGenericArgsAsDefn =
|
|
typeGenericArgs
|
|
|> Seq.map (fun handle ->
|
|
Concretization.concreteHandleToTypeDefn
|
|
baseClassTypes
|
|
handle
|
|
state.ConcreteTypes
|
|
state._LoadedAssemblies
|
|
)
|
|
|> ImmutableArray.CreateRange
|
|
|
|
let methodGenericArgsAsDefn =
|
|
methodGenericArgs
|
|
|> Seq.map (fun handle ->
|
|
Concretization.concreteHandleToTypeDefn
|
|
baseClassTypes
|
|
handle
|
|
state.ConcreteTypes
|
|
state._LoadedAssemblies
|
|
)
|
|
|> ImmutableArray.CreateRange
|
|
|
|
// Map the type definition's generics using the provided type generic arguments
|
|
let resolvedTypeDef =
|
|
typeDef
|
|
|> TypeInfo.mapGeneric (fun (param, _) ->
|
|
if param.SequenceNumber < typeGenericArgsAsDefn.Length then
|
|
typeGenericArgsAsDefn.[param.SequenceNumber]
|
|
else
|
|
failwithf "Generic type parameter %d out of range" param.SequenceNumber
|
|
)
|
|
|
|
state, assy, resolvedTypeDef
|
|
|
|
/// Get zero value for a type that's already been concretized
|
|
let cliTypeZeroOfHandle
|
|
(state : IlMachineState)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(handle : ConcreteTypeHandle)
|
|
: CliType * IlMachineState
|
|
=
|
|
let zero, updatedConcreteTypes =
|
|
CliType.zeroOf state.ConcreteTypes state._LoadedAssemblies baseClassTypes handle
|
|
|
|
let newState =
|
|
{ state with
|
|
ConcreteTypes = updatedConcreteTypes
|
|
}
|
|
|
|
zero, newState
|
|
|
|
/// Helper to get ConcreteTypeHandle from ConcreteType<ConcreteTypeHandle> during migration
|
|
let getConcreteTypeHandle
|
|
(ct : ConcreteType<ConcreteTypeHandle>)
|
|
(state : IlMachineState)
|
|
: ConcreteTypeHandle option
|
|
=
|
|
AllConcreteTypes.lookup' ct state.ConcreteTypes
|
|
|
|
/// Concretize a ConcreteType<TypeDefn> to get a ConcreteTypeHandle for static field access
|
|
let concretizeFieldDeclaringType
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(declaringType : ConcreteType<TypeDefn>)
|
|
(state : IlMachineState)
|
|
: ConcreteTypeHandle * IlMachineState
|
|
=
|
|
// Create a concretization context from the current state
|
|
let ctx : TypeConcretization.ConcretizationContext<_> =
|
|
{
|
|
InProgress = ImmutableDictionary.Empty
|
|
ConcreteTypes = state.ConcreteTypes
|
|
LoadedAssemblies = state._LoadedAssemblies
|
|
BaseTypes = baseClassTypes
|
|
}
|
|
|
|
// Concretize each generic argument first
|
|
let mutable currentCtx = ctx
|
|
let genericHandles = ImmutableArray.CreateBuilder declaringType.Generics.Length
|
|
|
|
for genericArg in declaringType.Generics do
|
|
let handle, newCtx =
|
|
TypeConcretization.concretizeType
|
|
currentCtx
|
|
(loader loggerFactory state)
|
|
declaringType.Assembly
|
|
ImmutableArray.Empty // No type generics in this context
|
|
ImmutableArray.Empty // No method generics in this context
|
|
genericArg
|
|
|
|
currentCtx <- newCtx
|
|
genericHandles.Add handle
|
|
|
|
// Now we need to concretize the type definition itself
|
|
// If it's a non-generic type, we can use concretizeTypeDefinition directly
|
|
if declaringType.Generics.IsEmpty then
|
|
let handle, currentCtx =
|
|
TypeConcretization.concretizeTypeDefinition currentCtx declaringType.Assembly declaringType.Definition
|
|
|
|
let newState =
|
|
{ state with
|
|
ConcreteTypes = currentCtx.ConcreteTypes
|
|
_LoadedAssemblies = currentCtx.LoadedAssemblies
|
|
}
|
|
|
|
handle, newState
|
|
else
|
|
// For generic types, we need to check if this concrete instantiation already exists
|
|
let key =
|
|
(declaringType.Assembly, declaringType.Namespace, declaringType.Name, genericHandles.ToImmutable ())
|
|
|
|
match AllConcreteTypes.findExistingConcreteType currentCtx.ConcreteTypes key with
|
|
| Some handle ->
|
|
// Type already exists, just return it
|
|
handle,
|
|
{ state with
|
|
ConcreteTypes = currentCtx.ConcreteTypes
|
|
_LoadedAssemblies = currentCtx.LoadedAssemblies
|
|
}
|
|
| None ->
|
|
// Create the concrete type using mapGeneric to transform from TypeDefn to ConcreteTypeHandle
|
|
let concreteTypeWithHandles =
|
|
declaringType |> ConcreteType.mapGeneric (fun i _ -> genericHandles.[i])
|
|
|
|
// Add to the concrete types
|
|
let handle, newConcreteTypes =
|
|
AllConcreteTypes.add concreteTypeWithHandles currentCtx.ConcreteTypes
|
|
|
|
// Update the state with the new concrete types
|
|
let newState =
|
|
{ state with
|
|
ConcreteTypes = newConcreteTypes
|
|
_LoadedAssemblies = currentCtx.LoadedAssemblies
|
|
}
|
|
|
|
handle, newState
|
|
|
|
/// Get zero value for a TypeDefn, concretizing it first
|
|
let cliTypeZeroOf
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(assy : DumpedAssembly)
|
|
(ty : TypeDefn)
|
|
(typeGenerics : ConcreteTypeHandle ImmutableArray)
|
|
(methodGenerics : ConcreteTypeHandle ImmutableArray)
|
|
(state : IlMachineState)
|
|
: IlMachineState * CliType
|
|
=
|
|
|
|
// First concretize the type
|
|
// Make sure the current assembly is included in the state for concretization
|
|
let state =
|
|
if state.LoadedAssembly assy.Name |> Option.isSome then
|
|
state
|
|
else
|
|
state.WithLoadedAssembly assy.Name assy
|
|
|
|
let state, handle =
|
|
concretizeType loggerFactory baseClassTypes state assy.Name typeGenerics methodGenerics ty
|
|
|
|
// Now get the zero value
|
|
let zero, state = cliTypeZeroOfHandle state baseClassTypes handle
|
|
state, zero
|
|
|
|
let pushToEvalStack' (o : EvalStackValue) (thread : ThreadId) (state : IlMachineState) =
|
|
let activeThreadState = state.ThreadState.[thread]
|
|
|
|
let newThreadState =
|
|
activeThreadState
|
|
|> ThreadState.pushToEvalStack' o activeThreadState.ActiveMethodState
|
|
|
|
{ state with
|
|
ThreadState = state.ThreadState |> Map.add thread newThreadState
|
|
}
|
|
|
|
let pushToEvalStack (o : CliType) (thread : ThreadId) (state : IlMachineState) : IlMachineState =
|
|
let activeThreadState = state.ThreadState.[thread]
|
|
|
|
let newThreadState =
|
|
activeThreadState
|
|
|> ThreadState.pushToEvalStack o activeThreadState.ActiveMethodState
|
|
|
|
{ state with
|
|
ThreadState = state.ThreadState |> Map.add thread newThreadState
|
|
}
|
|
|
|
let peekEvalStack (thread : ThreadId) (state : IlMachineState) : EvalStackValue option =
|
|
ThreadState.peekEvalStack state.ThreadState.[thread]
|
|
|
|
let popEvalStack (thread : ThreadId) (state : IlMachineState) : EvalStackValue * IlMachineState =
|
|
let ret, popped = ThreadState.popFromEvalStack state.ThreadState.[thread]
|
|
|
|
let state =
|
|
{ state with
|
|
ThreadState = state.ThreadState |> Map.add thread popped
|
|
}
|
|
|
|
ret, state
|
|
|
|
let advanceProgramCounter (thread : ThreadId) (state : IlMachineState) : IlMachineState =
|
|
{ state with
|
|
ThreadState =
|
|
state.ThreadState
|
|
|> Map.change
|
|
thread
|
|
(fun state ->
|
|
match state with
|
|
| None -> failwith "expected state"
|
|
| Some (state : ThreadState) -> state |> ThreadState.advanceProgramCounter |> Some
|
|
)
|
|
}
|
|
|
|
let setArrayValue
|
|
(arrayAllocation : ManagedHeapAddress)
|
|
(v : CliType)
|
|
(index : int)
|
|
(state : IlMachineState)
|
|
: IlMachineState
|
|
=
|
|
let heap = ManagedHeap.setArrayValue arrayAllocation index v state.ManagedHeap
|
|
|
|
{ state with
|
|
ManagedHeap = heap
|
|
}
|
|
|
|
let getArrayValue (arrayAllocation : ManagedHeapAddress) (index : int) (state : IlMachineState) : CliType =
|
|
ManagedHeap.getArrayValue arrayAllocation index state.ManagedHeap
|
|
|
|
/// There might be no stack frame to return to, so you might get None.
|
|
let returnStackFrame
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(currentThread : ThreadId)
|
|
(state : IlMachineState)
|
|
: IlMachineState option
|
|
=
|
|
let threadStateAtEndOfMethod = state.ThreadState.[currentThread]
|
|
|
|
match threadStateAtEndOfMethod.MethodState.ReturnState with
|
|
| None -> None
|
|
| Some returnState ->
|
|
|
|
let state =
|
|
match returnState.WasInitialisingType with
|
|
| None -> state
|
|
| Some finishedInitialising -> state.WithTypeEndInit currentThread finishedInitialising
|
|
|
|
// Return to previous stack frame
|
|
let state =
|
|
{ state with
|
|
ThreadState =
|
|
state.ThreadState
|
|
|> Map.add
|
|
currentThread
|
|
{ threadStateAtEndOfMethod with
|
|
ActiveMethodState = returnState.JumpTo
|
|
ActiveAssembly =
|
|
threadStateAtEndOfMethod.MethodStates.[returnState.JumpTo].ExecutingMethod.DeclaringType
|
|
.Assembly
|
|
}
|
|
}
|
|
|
|
match returnState.WasConstructingObj with
|
|
| Some constructing ->
|
|
// Assumption: a constructor can't also return a value.
|
|
// If we were constructing a reference type, we push a reference to it.
|
|
// Otherwise, extract the now-complete object from the heap and push it to the stack directly.
|
|
let constructed = state.ManagedHeap.NonArrayObjects.[constructing]
|
|
|
|
let ty =
|
|
AllConcreteTypes.lookup constructed.ConcreteType state.ConcreteTypes
|
|
|> Option.get
|
|
|
|
let ty' =
|
|
state.LoadedAssembly (ty.Assembly)
|
|
|> Option.get
|
|
|> fun a -> a.TypeDefs.[ty.Definition.Get]
|
|
|
|
let resolvedBaseType =
|
|
DumpedAssembly.resolveBaseType baseClassTypes state._LoadedAssemblies ty.Assembly ty'.BaseType
|
|
|
|
match resolvedBaseType with
|
|
| ResolvedBaseType.Delegate
|
|
| ResolvedBaseType.Object -> state |> pushToEvalStack (CliType.ofManagedObject constructing) currentThread
|
|
| ResolvedBaseType.ValueType ->
|
|
state
|
|
// TODO: ordering of fields probably important
|
|
|> pushToEvalStack (CliType.ValueType constructed.Contents) currentThread
|
|
| ResolvedBaseType.Enum -> failwith "TODO"
|
|
| None ->
|
|
match threadStateAtEndOfMethod.MethodState.EvaluationStack.Values with
|
|
| [] ->
|
|
// no return value
|
|
state
|
|
| [ retVal ] ->
|
|
let retType =
|
|
threadStateAtEndOfMethod.MethodState.ExecutingMethod.Signature.ReturnType
|
|
|
|
match retType with
|
|
// TODO: Claude, don't worry about this one for now, I need to think harder about what's going on here.
|
|
// | TypeDefn.Void -> state
|
|
| retType ->
|
|
// TODO: generics
|
|
let zero, state = cliTypeZeroOfHandle state baseClassTypes retType
|
|
|
|
let toPush = EvalStackValue.toCliTypeCoerced zero retVal
|
|
|
|
state |> pushToEvalStack toPush currentThread
|
|
| _ ->
|
|
failwith
|
|
"Unexpected interpretation result has a local evaluation stack with more than one element on RET"
|
|
|
|
|> Some
|
|
|
|
let concretizeMethodWithAllGenerics
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(typeGenerics : ImmutableArray<ConcreteTypeHandle>)
|
|
(methodToCall : WoofWare.PawPrint.MethodInfo<'ty, GenericParamFromMetadata, TypeDefn>)
|
|
(methodGenerics : ImmutableArray<ConcreteTypeHandle>)
|
|
(state : IlMachineState)
|
|
: IlMachineState *
|
|
WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle> *
|
|
ConcreteTypeHandle
|
|
=
|
|
// Now concretize the entire method
|
|
let concretizedMethod, newConcreteTypes, newAssemblies =
|
|
Concretization.concretizeMethod
|
|
state.ConcreteTypes
|
|
(loader loggerFactory state)
|
|
state._LoadedAssemblies
|
|
baseClassTypes
|
|
methodToCall
|
|
typeGenerics
|
|
methodGenerics
|
|
|
|
let state =
|
|
{ state with
|
|
ConcreteTypes = newConcreteTypes
|
|
_LoadedAssemblies = newAssemblies
|
|
}
|
|
|
|
// Get the handle for the declaring type
|
|
let declaringTypeHandle =
|
|
match AllConcreteTypes.lookup' concretizedMethod.DeclaringType state.ConcreteTypes with
|
|
| Some handle -> handle
|
|
| None -> failwith "Concretized method's declaring type not found in ConcreteTypes"
|
|
|
|
state, concretizedMethod, declaringTypeHandle
|
|
|
|
let concretizeMethodWithTypeGenerics
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(typeGenerics : ImmutableArray<ConcreteTypeHandle>)
|
|
(methodToCall : WoofWare.PawPrint.MethodInfo<'ty, GenericParamFromMetadata, TypeDefn>)
|
|
(methodGenerics : TypeDefn ImmutableArray option)
|
|
(callingAssembly : AssemblyName)
|
|
(currentExecutingMethodGenerics : ImmutableArray<ConcreteTypeHandle>)
|
|
(state : IlMachineState)
|
|
: IlMachineState *
|
|
WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle> *
|
|
ConcreteTypeHandle
|
|
=
|
|
|
|
// Concretize method generics if any
|
|
let state, concretizedMethodGenerics =
|
|
match methodGenerics with
|
|
| None -> state, ImmutableArray.Empty
|
|
| Some generics ->
|
|
let handles = ImmutableArray.CreateBuilder ()
|
|
let mutable state = state
|
|
|
|
for i = 0 to generics.Length - 1 do
|
|
let state2, handle =
|
|
concretizeType
|
|
loggerFactory
|
|
baseClassTypes
|
|
state
|
|
callingAssembly
|
|
typeGenerics
|
|
currentExecutingMethodGenerics
|
|
generics.[i]
|
|
|
|
state <- state2
|
|
handles.Add handle
|
|
|
|
state, handles.ToImmutable ()
|
|
|
|
// Now concretize the entire method
|
|
concretizeMethodWithAllGenerics
|
|
loggerFactory
|
|
baseClassTypes
|
|
typeGenerics
|
|
methodToCall
|
|
concretizedMethodGenerics
|
|
state
|
|
|
|
/// Returns also the declaring type.
|
|
let concretizeMethodForExecution
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(thread : ThreadId)
|
|
(methodToCall : WoofWare.PawPrint.MethodInfo<'ty, GenericParamFromMetadata, TypeDefn>)
|
|
(methodGenerics : TypeDefn ImmutableArray option)
|
|
(typeArgsFromMetadata : TypeDefn ImmutableArray option)
|
|
(state : IlMachineState)
|
|
: IlMachineState *
|
|
WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle> *
|
|
ConcreteTypeHandle
|
|
=
|
|
// Use type generics from metadata if available, otherwise fall back to current execution context
|
|
let typeGenerics =
|
|
match typeArgsFromMetadata with
|
|
| Some args when not args.IsEmpty ->
|
|
// We have concrete type arguments from the IL metadata
|
|
// Need to concretize them to ConcreteTypeHandle first
|
|
let handles = ImmutableArray.CreateBuilder args.Length
|
|
let mutable state = state
|
|
|
|
for i = 0 to args.Length - 1 do
|
|
let ctx =
|
|
{
|
|
TypeConcretization.ConcretizationContext.InProgress = ImmutableDictionary.Empty
|
|
TypeConcretization.ConcretizationContext.ConcreteTypes = state.ConcreteTypes
|
|
TypeConcretization.ConcretizationContext.LoadedAssemblies = state._LoadedAssemblies
|
|
TypeConcretization.ConcretizationContext.BaseTypes = baseClassTypes
|
|
}
|
|
|
|
let handle, newCtx =
|
|
TypeConcretization.concretizeType
|
|
ctx
|
|
(loader loggerFactory state)
|
|
(state.ActiveAssembly thread).Name
|
|
ImmutableArray.Empty // No type generics for the concretization context
|
|
ImmutableArray.Empty // No method generics for the concretization context
|
|
args.[i]
|
|
|
|
handles.Add handle
|
|
|
|
state <-
|
|
{ state with
|
|
ConcreteTypes = newCtx.ConcreteTypes
|
|
_LoadedAssemblies = newCtx.LoadedAssemblies
|
|
}
|
|
|
|
handles.ToImmutable (), state
|
|
| _ ->
|
|
// Fall back to current execution context
|
|
let currentMethod = state.ThreadState.[thread].MethodState.ExecutingMethod
|
|
currentMethod.DeclaringType.Generics, state
|
|
|
|
let typeGenerics, state = typeGenerics
|
|
|
|
let callingAssembly = (state.ActiveAssembly thread).Name
|
|
let currentMethod = state.ThreadState.[thread].MethodState.ExecutingMethod
|
|
|
|
concretizeMethodWithTypeGenerics
|
|
loggerFactory
|
|
baseClassTypes
|
|
typeGenerics
|
|
methodToCall
|
|
methodGenerics
|
|
callingAssembly
|
|
currentMethod.Generics
|
|
state
|
|
|
|
// Add to IlMachineState module
|
|
let concretizeFieldForExecution
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(thread : ThreadId)
|
|
(field : WoofWare.PawPrint.FieldInfo<TypeDefn, TypeDefn>)
|
|
(state : IlMachineState)
|
|
: IlMachineState * ConcreteTypeHandle * ImmutableArray<ConcreteTypeHandle>
|
|
=
|
|
// Get type and method generics from current execution context
|
|
let currentMethod = state.ThreadState.[thread].MethodState.ExecutingMethod
|
|
|
|
let contextTypeGenerics = currentMethod.DeclaringType.Generics
|
|
|
|
let contextMethodGenerics = currentMethod.Generics |> ImmutableArray.CreateRange
|
|
|
|
// Create a concretization context
|
|
let ctx =
|
|
{
|
|
TypeConcretization.ConcretizationContext.InProgress = ImmutableDictionary.Empty
|
|
TypeConcretization.ConcretizationContext.ConcreteTypes = state.ConcreteTypes
|
|
TypeConcretization.ConcretizationContext.LoadedAssemblies = state._LoadedAssemblies
|
|
TypeConcretization.ConcretizationContext.BaseTypes = baseClassTypes
|
|
}
|
|
|
|
// Create a TypeDefn for the field's declaring type
|
|
let declaringTypeDefn =
|
|
if field.DeclaringType.Generics.IsEmpty then
|
|
// Non-generic type - determine the SignatureTypeKind
|
|
let assy = state._LoadedAssemblies.[field.DeclaringType.Assembly.FullName]
|
|
let typeDef = assy.TypeDefs.[field.DeclaringType.Definition.Get]
|
|
|
|
let baseType =
|
|
typeDef.BaseType
|
|
|> DumpedAssembly.resolveBaseType baseClassTypes state._LoadedAssemblies assy.Name
|
|
|
|
let signatureTypeKind =
|
|
match baseType with
|
|
| ResolvedBaseType.Enum
|
|
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
|
| ResolvedBaseType.Object
|
|
| ResolvedBaseType.Delegate -> SignatureTypeKind.Class
|
|
|
|
TypeDefn.FromDefinition (
|
|
field.DeclaringType.Definition,
|
|
field.DeclaringType.Assembly.FullName,
|
|
signatureTypeKind
|
|
)
|
|
else
|
|
// Generic type - the field's declaring type already has the generic arguments
|
|
let assy = state._LoadedAssemblies.[field.DeclaringType.Assembly.FullName]
|
|
let typeDef = assy.TypeDefs.[field.DeclaringType.Definition.Get]
|
|
|
|
let baseTypeResolved =
|
|
typeDef.BaseType
|
|
|> DumpedAssembly.resolveBaseType baseClassTypes state._LoadedAssemblies assy.Name
|
|
|
|
let signatureTypeKind =
|
|
match baseTypeResolved with
|
|
| ResolvedBaseType.Enum
|
|
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
|
|
| ResolvedBaseType.Object -> SignatureTypeKind.Class
|
|
| ResolvedBaseType.Delegate -> failwith "TODO: delegate"
|
|
|
|
let baseType =
|
|
TypeDefn.FromDefinition (
|
|
field.DeclaringType.Definition,
|
|
field.DeclaringType.Assembly.FullName,
|
|
signatureTypeKind
|
|
)
|
|
|
|
// Use the actual type arguments from the field's declaring type
|
|
// These should already be correctly instantiated (e.g., GenericMethodParameter 0 for Array.Empty<T>)
|
|
let genericArgs = field.DeclaringType.Generics
|
|
|
|
TypeDefn.GenericInstantiation (baseType, genericArgs)
|
|
|
|
// Concretize the declaring type
|
|
let declaringHandle, newCtx =
|
|
TypeConcretization.concretizeType
|
|
ctx
|
|
(loader loggerFactory state)
|
|
field.DeclaringType.Assembly
|
|
contextTypeGenerics
|
|
contextMethodGenerics
|
|
declaringTypeDefn
|
|
|
|
let state =
|
|
{ state with
|
|
ConcreteTypes = newCtx.ConcreteTypes
|
|
_LoadedAssemblies = newCtx.LoadedAssemblies
|
|
}
|
|
|
|
// Get the concretized type's generics
|
|
let concretizedType =
|
|
AllConcreteTypes.lookup declaringHandle state.ConcreteTypes |> Option.get
|
|
|
|
let typeGenerics = concretizedType.Generics
|
|
|
|
state, declaringHandle, typeGenerics
|
|
|
|
let initial
|
|
(lf : ILoggerFactory)
|
|
(dotnetRuntimeDirs : ImmutableArray<string>)
|
|
(entryAssembly : DumpedAssembly)
|
|
: IlMachineState
|
|
=
|
|
let assyName = entryAssembly.ThisAssemblyDefinition.Name
|
|
let logger = lf.CreateLogger "IlMachineState"
|
|
|
|
let state =
|
|
{
|
|
ConcreteTypes = AllConcreteTypes.Empty
|
|
Logger = logger
|
|
NextThreadId = 0
|
|
// CallStack = []
|
|
ManagedHeap = ManagedHeap.empty
|
|
ThreadState = Map.empty
|
|
InternedStrings = ImmutableDictionary.Empty
|
|
_LoadedAssemblies = ImmutableDictionary.Empty
|
|
_Statics = ImmutableDictionary.Empty
|
|
TypeInitTable = ImmutableDictionary.Empty
|
|
DotnetRuntimeDirs = dotnetRuntimeDirs
|
|
TypeHandles = TypeHandleRegistry.empty ()
|
|
FieldHandles = FieldHandleRegistry.empty ()
|
|
}
|
|
|
|
state.WithLoadedAssembly assyName entryAssembly
|
|
|
|
let addThread
|
|
(newThreadState : MethodState)
|
|
(newThreadAssy : AssemblyName)
|
|
(state : IlMachineState)
|
|
: IlMachineState * ThreadId
|
|
=
|
|
let thread = ThreadId state.NextThreadId
|
|
|
|
let newState =
|
|
{ state with
|
|
NextThreadId = state.NextThreadId + 1
|
|
ThreadState =
|
|
state.ThreadState
|
|
|> Map.add thread (ThreadState.New newThreadAssy newThreadState)
|
|
}
|
|
|
|
newState, thread
|
|
|
|
let allocateArray
|
|
(zeroOfType : unit -> CliType)
|
|
(len : int)
|
|
(state : IlMachineState)
|
|
: ManagedHeapAddress * IlMachineState
|
|
=
|
|
let initialisation =
|
|
(fun _ -> zeroOfType ()) |> Seq.init len |> ImmutableArray.CreateRange
|
|
|
|
let o : AllocatedArray =
|
|
{
|
|
Length = len
|
|
Elements = initialisation
|
|
}
|
|
|
|
let alloc, heap = state.ManagedHeap |> ManagedHeap.allocateArray o
|
|
|
|
let state =
|
|
{ state with
|
|
ManagedHeap = heap
|
|
}
|
|
|
|
alloc, state
|
|
|
|
let allocateStringData (len : int) (state : IlMachineState) : int * IlMachineState =
|
|
let addr, heap = state.ManagedHeap |> ManagedHeap.allocateString len
|
|
|
|
let state =
|
|
{ state with
|
|
ManagedHeap = heap
|
|
}
|
|
|
|
addr, state
|
|
|
|
let setStringData (addr : int) (contents : string) (state : IlMachineState) : IlMachineState =
|
|
let heap = ManagedHeap.setStringData addr contents state.ManagedHeap
|
|
|
|
{ state with
|
|
ManagedHeap = heap
|
|
}
|
|
|
|
let allocateManagedObject
|
|
(ty : ConcreteTypeHandle)
|
|
(fields : CliValueType)
|
|
(state : IlMachineState)
|
|
: ManagedHeapAddress * IlMachineState
|
|
=
|
|
let o =
|
|
{
|
|
Contents = fields
|
|
ConcreteType = ty
|
|
SyncBlock = SyncBlock.Free
|
|
}
|
|
|
|
let alloc, heap = state.ManagedHeap |> ManagedHeap.allocateNonArray o
|
|
|
|
let state =
|
|
{ state with
|
|
ManagedHeap = heap
|
|
}
|
|
|
|
alloc, state
|
|
|
|
let popFromStackToLocalVariable
|
|
(thread : ThreadId)
|
|
(localVariableIndex : int)
|
|
(state : IlMachineState)
|
|
: IlMachineState
|
|
=
|
|
let threadState =
|
|
match Map.tryFind thread state.ThreadState with
|
|
| None -> failwith "Logic error: tried to pop from stack of nonexistent thread"
|
|
| Some threadState -> threadState
|
|
|
|
let methodState =
|
|
MethodState.popFromStackToVariable
|
|
localVariableIndex
|
|
threadState.MethodStates.[threadState.ActiveMethodState]
|
|
|
|
{ state with
|
|
ThreadState =
|
|
state.ThreadState
|
|
|> Map.add
|
|
thread
|
|
{ threadState with
|
|
MethodStates = threadState.MethodStates.SetItem (threadState.ActiveMethodState, methodState)
|
|
}
|
|
}
|
|
|
|
let jumpProgramCounter (thread : ThreadId) (bytes : int) (state : IlMachineState) : IlMachineState =
|
|
{ state with
|
|
ThreadState =
|
|
state.ThreadState
|
|
|> Map.change
|
|
thread
|
|
(fun state ->
|
|
match state with
|
|
| None -> failwith "expected state"
|
|
| Some (state : ThreadState) -> state |> ThreadState.jumpProgramCounter bytes |> Some
|
|
)
|
|
}
|
|
|
|
let loadArgument (thread : ThreadId) (index : int) (state : IlMachineState) : IlMachineState =
|
|
{ state with
|
|
ThreadState =
|
|
state.ThreadState
|
|
|> Map.change
|
|
thread
|
|
(fun state ->
|
|
match state with
|
|
| None -> failwith "expected state"
|
|
| Some state -> state |> ThreadState.loadArgument index |> Some
|
|
)
|
|
}
|
|
|
|
let resolveMember
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(currentThread : ThreadId)
|
|
(assy : DumpedAssembly)
|
|
(genericMethodTypeArgs : ImmutableArray<ConcreteTypeHandle>)
|
|
(m : MemberReferenceHandle)
|
|
(state : IlMachineState)
|
|
: IlMachineState *
|
|
AssemblyName *
|
|
Choice<
|
|
WoofWare.PawPrint.MethodInfo<TypeDefn, GenericParamFromMetadata, TypeDefn>,
|
|
WoofWare.PawPrint.FieldInfo<TypeDefn, TypeDefn>
|
|
> *
|
|
TypeDefn ImmutableArray
|
|
=
|
|
// TODO: do we need to initialise the parent class here?
|
|
let mem = assy.Members.[m]
|
|
|
|
let memberName : string = assy.Strings mem.Name
|
|
|
|
let executing = state.ThreadState.[currentThread].MethodState.ExecutingMethod
|
|
// Create synthetic TypeDefn generics based on the arity of the concrete generics
|
|
let typeGenerics =
|
|
executing.DeclaringType.Generics
|
|
|> Seq.map (fun handle ->
|
|
Concretization.concreteHandleToTypeDefn
|
|
baseClassTypes
|
|
handle
|
|
state.ConcreteTypes
|
|
state._LoadedAssemblies
|
|
)
|
|
|> ImmutableArray.CreateRange
|
|
|
|
let state, assy, targetType, extractedTypeArgs =
|
|
match mem.Parent with
|
|
| MetadataToken.TypeReference parent ->
|
|
// TODO: generics here?
|
|
let state, assy, targetType =
|
|
resolveType loggerFactory parent ImmutableArray.Empty assy state
|
|
|
|
state, assy, targetType, ImmutableArray.Empty // No type args from TypeReference
|
|
| MetadataToken.TypeSpecification parent ->
|
|
let methodGenerics =
|
|
executing.Generics
|
|
|> Seq.map (fun handle ->
|
|
Concretization.concreteHandleToTypeDefn
|
|
baseClassTypes
|
|
handle
|
|
state.ConcreteTypes
|
|
state._LoadedAssemblies
|
|
)
|
|
|> ImmutableArray.CreateRange
|
|
|
|
let state, assy, targetType =
|
|
resolveTypeFromSpec loggerFactory baseClassTypes parent assy typeGenerics methodGenerics state
|
|
|
|
// Extract type arguments from the resolved type
|
|
let extractedTypeArgs = targetType.Generics
|
|
|
|
state, assy, targetType, extractedTypeArgs
|
|
| parent -> failwith $"Unexpected: {parent}"
|
|
|
|
let state, concreteExtractedTypeArgs =
|
|
((state, ImmutableArray.CreateBuilder ()), extractedTypeArgs)
|
|
||> Seq.fold (fun (state, acc) ty ->
|
|
// TODO: generics?
|
|
let state, t =
|
|
concretizeType
|
|
loggerFactory
|
|
baseClassTypes
|
|
state
|
|
targetType.Assembly
|
|
ImmutableArray.Empty
|
|
ImmutableArray.Empty
|
|
ty
|
|
|
|
acc.Add t
|
|
state, acc
|
|
)
|
|
|> Tuple.rmap (fun x -> x.ToImmutable ())
|
|
|
|
match mem.Signature with
|
|
| MemberSignature.Field fieldSig ->
|
|
// Concretize the field signature from the member reference
|
|
let state, concreteFieldSig =
|
|
concretizeType
|
|
loggerFactory
|
|
baseClassTypes
|
|
state
|
|
(state.ActiveAssembly(currentThread).Name)
|
|
concreteExtractedTypeArgs
|
|
ImmutableArray.Empty
|
|
fieldSig
|
|
|
|
// Find matching fields by comparing concretized signatures
|
|
let state, availableFields =
|
|
((state, []), targetType.Fields)
|
|
||> List.fold (fun (state, acc) fi ->
|
|
if fi.Name <> memberName then
|
|
state, acc
|
|
else
|
|
// Concretize the field's signature for comparison
|
|
let state, fieldSigConcrete =
|
|
concretizeType
|
|
loggerFactory
|
|
baseClassTypes
|
|
state
|
|
assy.Name
|
|
concreteExtractedTypeArgs
|
|
ImmutableArray.Empty
|
|
fi.Signature
|
|
|
|
if fieldSigConcrete = concreteFieldSig then
|
|
state, fi :: acc
|
|
else
|
|
state, acc
|
|
)
|
|
|
|
let field =
|
|
match availableFields with
|
|
| [] ->
|
|
failwith
|
|
$"Could not find field member {memberName} with the right signature on {targetType.Namespace}.{targetType.Name}"
|
|
| [ x ] ->
|
|
x
|
|
|> FieldInfo.mapTypeGenerics (fun _ (par, md) -> targetType.Generics.[par.SequenceNumber])
|
|
| _ ->
|
|
failwith
|
|
$"Multiple overloads matching signature for {targetType.Namespace}.{targetType.Name}'s field {memberName}!"
|
|
|
|
state, assy.Name, Choice2Of2 field, extractedTypeArgs
|
|
|
|
| MemberSignature.Method memberSig ->
|
|
let availableMethods =
|
|
targetType.Methods |> List.filter (fun mi -> mi.Name = memberName)
|
|
|
|
let state, memberSig =
|
|
memberSig
|
|
|> TypeMethodSignature.map
|
|
state
|
|
(fun state ty ->
|
|
concretizeType
|
|
loggerFactory
|
|
baseClassTypes
|
|
state
|
|
(state.ActiveAssembly(currentThread).Name)
|
|
concreteExtractedTypeArgs
|
|
genericMethodTypeArgs
|
|
ty
|
|
)
|
|
|
|
let state, availableMethods =
|
|
((state, []), availableMethods)
|
|
||> List.fold (fun (state, acc) meth ->
|
|
let state, methSig =
|
|
meth.Signature
|
|
|> TypeMethodSignature.map
|
|
state
|
|
(fun state ty ->
|
|
concretizeType
|
|
loggerFactory
|
|
baseClassTypes
|
|
state
|
|
assy.Name
|
|
concreteExtractedTypeArgs
|
|
genericMethodTypeArgs
|
|
ty
|
|
)
|
|
|
|
if methSig = memberSig then
|
|
state, meth :: acc
|
|
else
|
|
state, acc
|
|
)
|
|
|
|
let method =
|
|
match availableMethods with
|
|
| [] ->
|
|
failwith
|
|
$"Could not find member {memberName} with the right signature {memberSig} on {targetType.Namespace}.{targetType.Name}"
|
|
| [ x ] ->
|
|
x
|
|
|> MethodInfo.mapTypeGenerics (fun (par, _) -> targetType.Generics.[par.SequenceNumber])
|
|
| _ ->
|
|
failwith
|
|
$"Multiple overloads matching signature for call to {targetType.Namespace}.{targetType.Name}'s {memberName}!"
|
|
|
|
state, assy.Name, Choice1Of2 method, extractedTypeArgs
|
|
|
|
let getLocalVariable (thread : ThreadId) (stackFrame : int) (varIndex : uint16) (state : IlMachineState) : CliType =
|
|
state.ThreadState.[thread].MethodStates.[stackFrame].LocalVariables.[int<uint16> varIndex]
|
|
|
|
let setLocalVariable
|
|
(thread : ThreadId)
|
|
(stackFrame : int)
|
|
(varIndex : uint16)
|
|
(value : CliType)
|
|
(state : IlMachineState)
|
|
: IlMachineState
|
|
=
|
|
{ state with
|
|
ThreadState =
|
|
state.ThreadState
|
|
|> Map.change
|
|
thread
|
|
(fun existing ->
|
|
match existing with
|
|
| None -> failwith "tried to set variable in nonactive thread"
|
|
| Some existing -> existing |> ThreadState.setLocalVariable stackFrame varIndex value |> Some
|
|
)
|
|
}
|
|
|
|
let setSyncBlock
|
|
(addr : ManagedHeapAddress)
|
|
(syncBlockValue : SyncBlock)
|
|
(state : IlMachineState)
|
|
: IlMachineState
|
|
=
|
|
{ state with
|
|
ManagedHeap = state.ManagedHeap |> ManagedHeap.setSyncBlock addr syncBlockValue
|
|
}
|
|
|
|
let getSyncBlock (addr : ManagedHeapAddress) (state : IlMachineState) : SyncBlock =
|
|
state.ManagedHeap |> ManagedHeap.getSyncBlock addr
|
|
|
|
let getFieldValue (obj : ManagedPointerSource) (fieldName : string) (state : IlMachineState) : CliType =
|
|
match obj with
|
|
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
|
getLocalVariable sourceThread methodFrame whichVar state
|
|
|> CliType.getField fieldName
|
|
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> failwith "todo"
|
|
| ManagedPointerSource.Heap addr ->
|
|
ManagedHeap.get addr state.ManagedHeap
|
|
|> AllocatedNonArrayObject.DereferenceField fieldName
|
|
| ManagedPointerSource.ArrayIndex (arr, index) -> getArrayValue arr index state |> CliType.getField fieldName
|
|
| ManagedPointerSource.Field (src, fieldName) -> failwith "todo"
|
|
| ManagedPointerSource.Null -> failwith "TODO: throw NRE"
|
|
| ManagedPointerSource.InterpretedAsType (src, ty) -> failwith "TODO"
|
|
|
|
let setFieldValue
|
|
(obj : ManagedPointerSource)
|
|
(v : CliType)
|
|
(fieldName : string)
|
|
(state : IlMachineState)
|
|
: IlMachineState
|
|
=
|
|
match obj with
|
|
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
|
let v =
|
|
getLocalVariable sourceThread methodFrame whichVar state
|
|
|> CliType.withFieldSet fieldName v
|
|
|
|
state |> setLocalVariable sourceThread methodFrame whichVar v
|
|
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> failwith "todo"
|
|
| ManagedPointerSource.Heap addr ->
|
|
let newValue =
|
|
ManagedHeap.get addr state.ManagedHeap
|
|
|> AllocatedNonArrayObject.SetField fieldName v
|
|
|
|
{ state with
|
|
ManagedHeap = ManagedHeap.set addr newValue state.ManagedHeap
|
|
}
|
|
| ManagedPointerSource.ArrayIndex (arr, index) ->
|
|
let v = getArrayValue arr index state |> CliType.withFieldSet fieldName v
|
|
state |> setArrayValue arr v index
|
|
| ManagedPointerSource.Field (managedPointerSource, fieldName) -> failwith "todo"
|
|
| ManagedPointerSource.Null -> failwith "TODO: throw NRE"
|
|
| ManagedPointerSource.InterpretedAsType (src, ty) -> failwith "TODO"
|
|
|
|
let executeDelegateConstructor (instruction : MethodState) (state : IlMachineState) : IlMachineState =
|
|
// We've been called with arguments already popped from the stack into local arguments.
|
|
let constructing = instruction.Arguments.[0]
|
|
let targetObj = instruction.Arguments.[1]
|
|
let methodPtr = instruction.Arguments.[2]
|
|
|
|
let targetObj =
|
|
match targetObj with
|
|
| CliType.RuntimePointer (CliRuntimePointer.Managed (ManagedPointerSource.Heap target))
|
|
| CliType.ObjectRef (Some target) -> Some target
|
|
| CliType.ObjectRef None
|
|
| CliType.RuntimePointer (CliRuntimePointer.Managed ManagedPointerSource.Null) -> None
|
|
| _ -> failwith $"Unexpected target type for delegate: {targetObj}"
|
|
|
|
let constructing =
|
|
match constructing with
|
|
| CliType.RuntimePointer (CliRuntimePointer.Managed ManagedPointerSource.Null)
|
|
| CliType.ObjectRef None -> failwith "unexpectedly constructing the null delegate"
|
|
| CliType.RuntimePointer (CliRuntimePointer.Managed (ManagedPointerSource.Heap target))
|
|
| CliType.ObjectRef (Some target) -> target
|
|
| _ -> failwith $"Unexpectedly not constructing a managed object: {constructing}"
|
|
|
|
let heapObj =
|
|
match state.ManagedHeap.NonArrayObjects.TryGetValue constructing with
|
|
| true, obj -> obj
|
|
| false, _ -> failwith $"Delegate object {constructing} not found on heap"
|
|
|
|
// Standard delegate fields in .NET are _target and _methodPtr
|
|
// Update the fields with the target object and method pointer
|
|
let updatedObj =
|
|
let newContents =
|
|
heapObj.Contents
|
|
|> CliValueType.AddField
|
|
{
|
|
Name = "_target"
|
|
Contents = CliType.ObjectRef targetObj
|
|
Offset = None
|
|
}
|
|
|> CliValueType.AddField
|
|
{
|
|
Name = "_methodPtr"
|
|
Contents = methodPtr
|
|
Offset = None
|
|
}
|
|
|
|
{ heapObj with
|
|
Contents = newContents
|
|
}
|
|
|
|
let updatedHeap =
|
|
{ state.ManagedHeap with
|
|
NonArrayObjects = state.ManagedHeap.NonArrayObjects |> Map.add constructing updatedObj
|
|
}
|
|
|
|
{ state with
|
|
ManagedHeap = updatedHeap
|
|
}
|
|
|
|
/// Returns the type handle and an allocated System.RuntimeType.
|
|
let getOrAllocateType
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(defn : ConcreteTypeHandle)
|
|
(state : IlMachineState)
|
|
: ManagedHeapAddress * IlMachineState
|
|
=
|
|
let state, runtimeType =
|
|
TypeDefn.FromDefinition (
|
|
ComparableTypeDefinitionHandle.Make baseClassTypes.RuntimeType.TypeDefHandle,
|
|
baseClassTypes.Corelib.Name.FullName,
|
|
SignatureTypeKind.Class
|
|
)
|
|
|> concretizeType
|
|
loggerFactory
|
|
baseClassTypes
|
|
state
|
|
baseClassTypes.Corelib.Name
|
|
ImmutableArray.Empty
|
|
ImmutableArray.Empty
|
|
|
|
let result, reg, state =
|
|
TypeHandleRegistry.getOrAllocate
|
|
state
|
|
(fun fields state -> allocateManagedObject runtimeType fields state)
|
|
defn
|
|
state.TypeHandles
|
|
|
|
let state =
|
|
{ state with
|
|
TypeHandles = reg
|
|
}
|
|
|
|
result, state
|
|
|
|
/// Returns a System.RuntimeFieldHandle.
|
|
let getOrAllocateField
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(declaringAssy : AssemblyName)
|
|
(fieldHandle : FieldDefinitionHandle)
|
|
(state : IlMachineState)
|
|
: CliType * IlMachineState
|
|
=
|
|
let field = state.LoadedAssembly(declaringAssy).Value.Fields.[fieldHandle]
|
|
|
|
// For LdToken, we need to convert GenericParamFromMetadata to TypeDefn
|
|
// When we don't have generic context, we use the generic type parameters directly
|
|
let declaringTypeWithGenerics =
|
|
field.DeclaringType
|
|
|> ConcreteType.mapGeneric (fun _index (param, _metadata) ->
|
|
TypeDefn.GenericTypeParameter param.SequenceNumber
|
|
)
|
|
|
|
let declaringType, state =
|
|
concretizeFieldDeclaringType loggerFactory baseClassTypes declaringTypeWithGenerics state
|
|
|
|
let state, runtimeType =
|
|
TypeDefn.FromDefinition (
|
|
ComparableTypeDefinitionHandle.Make baseClassTypes.RuntimeType.TypeDefHandle,
|
|
baseClassTypes.Corelib.Name.FullName,
|
|
SignatureTypeKind.Class
|
|
)
|
|
|> concretizeType
|
|
loggerFactory
|
|
baseClassTypes
|
|
state
|
|
baseClassTypes.Corelib.Name
|
|
ImmutableArray.Empty
|
|
ImmutableArray.Empty
|
|
|
|
let result, reg, state =
|
|
FieldHandleRegistry.getOrAllocate
|
|
baseClassTypes
|
|
state
|
|
(fun fields state -> allocateManagedObject runtimeType fields state)
|
|
declaringAssy
|
|
declaringType
|
|
fieldHandle
|
|
state.FieldHandles
|
|
|
|
let state =
|
|
{ state with
|
|
FieldHandles = reg
|
|
}
|
|
|
|
result, state
|
|
|
|
let setStatic
|
|
(ty : ConcreteTypeHandle)
|
|
(field : string)
|
|
(value : CliType)
|
|
(this : IlMachineState)
|
|
: IlMachineState
|
|
=
|
|
let statics =
|
|
match this._Statics.TryGetValue ty with
|
|
| false, _ -> this._Statics.Add (ty, ImmutableDictionary.Create().Add (field, value))
|
|
| true, v -> this._Statics.SetItem (ty, v.SetItem (field, value))
|
|
|
|
{ this with
|
|
_Statics = statics
|
|
}
|
|
|
|
let getStatic (ty : ConcreteTypeHandle) (field : string) (this : IlMachineState) : CliType option =
|
|
match this._Statics.TryGetValue ty with
|
|
| false, _ -> None
|
|
| true, v ->
|
|
match v.TryGetValue field with
|
|
| false, _ -> None
|
|
| true, v -> Some v
|
|
|
|
let rec dereferencePointer (state : IlMachineState) (src : ManagedPointerSource) : CliType =
|
|
match src with
|
|
| ManagedPointerSource.Null -> failwith "TODO: throw NRE"
|
|
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
|
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables.[int<uint16> whichVar]
|
|
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
|
|
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
|
|
| ManagedPointerSource.Heap addr -> failwith "todo"
|
|
| ManagedPointerSource.ArrayIndex (arr, index) -> getArrayValue arr index state
|
|
| ManagedPointerSource.Field (addr, name) ->
|
|
let obj = dereferencePointer state addr
|
|
|
|
match obj with
|
|
| CliType.ValueType vt -> vt |> CliValueType.DereferenceField name
|
|
| v -> failwith $"could not find field {name} on object {v}"
|
|
| ManagedPointerSource.InterpretedAsType (src, ty) -> failwith "TODO"
|
|
|
|
let lookupTypeDefn
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(state : IlMachineState)
|
|
(activeAssy : DumpedAssembly)
|
|
(typeDef : TypeDefinitionHandle)
|
|
: IlMachineState * TypeDefn
|
|
=
|
|
let defn = activeAssy.TypeDefs.[typeDef]
|
|
state, DumpedAssembly.typeInfoToTypeDefn' baseClassTypes state._LoadedAssemblies defn
|
|
|
|
let lookupTypeRef
|
|
(loggerFactory : ILoggerFactory)
|
|
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
|
(state : IlMachineState)
|
|
(activeAssy : DumpedAssembly)
|
|
typeGenerics
|
|
(ref : TypeReferenceHandle)
|
|
: IlMachineState * TypeDefn * DumpedAssembly
|
|
=
|
|
let ref = activeAssy.TypeRefs.[ref]
|
|
|
|
// Convert ConcreteTypeHandles back to TypeDefn for metadata operations
|
|
let typeGenerics =
|
|
typeGenerics
|
|
|> Seq.map (fun handle ->
|
|
Concretization.concreteHandleToTypeDefn
|
|
baseClassTypes
|
|
handle
|
|
state.ConcreteTypes
|
|
state._LoadedAssemblies
|
|
)
|
|
|> ImmutableArray.CreateRange
|
|
|
|
let state, assy, resolved =
|
|
resolveTypeFromRef loggerFactory activeAssy ref typeGenerics state
|
|
|
|
state, DumpedAssembly.typeInfoToTypeDefn baseClassTypes state._LoadedAssemblies resolved, assy
|