mirror of
https://github.com/Smaug123/WoofWare.PawPrint
synced 2025-10-08 07:28:40 +00:00
Split a bunch of code into new files (#17)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
836
WoofWare.PawPrint/IlMachineState.fs
Normal file
836
WoofWare.PawPrint/IlMachineState.fs
Normal file
@@ -0,0 +1,836 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System.Collections.Immutable
|
||||
open System.IO
|
||||
open System.Reflection
|
||||
open System.Reflection.Metadata
|
||||
open Microsoft.Extensions.Logging
|
||||
open Microsoft.FSharp.Core
|
||||
|
||||
type IlMachineState =
|
||||
{
|
||||
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
|
||||
Statics : ImmutableDictionary<TypeDefinitionHandle * AssemblyName, CliType>
|
||||
DotnetRuntimeDirs : string ImmutableArray
|
||||
}
|
||||
|
||||
member this.WithTypeBeginInit (thread : ThreadId) (handle : TypeDefinitionHandle, assy : AssemblyName) =
|
||||
this.Logger.LogDebug (
|
||||
"Beginning initialisation of type {TypeName}, handle {TypeDefinitionHandle} from assy {AssemblyHash}",
|
||||
this.LoadedAssembly(assy).Value.TypeDefs.[handle].Name,
|
||||
handle.GetHashCode (),
|
||||
assy.GetHashCode ()
|
||||
)
|
||||
|
||||
let typeInitTable =
|
||||
this.TypeInitTable |> TypeInitTable.beginInitialising thread (handle, assy)
|
||||
|
||||
{ this with
|
||||
TypeInitTable = typeInitTable
|
||||
}
|
||||
|
||||
member this.WithTypeEndInit (thread : ThreadId) (handle : TypeDefinitionHandle, assy : AssemblyName) =
|
||||
this.Logger.LogDebug (
|
||||
"Marking complete initialisation of type {TypeName}, handle {TypeDefinitionHandle} from assy {AssemblyHash}",
|
||||
this.LoadedAssembly(assy).Value.TypeDefs.[handle].Name,
|
||||
handle.GetHashCode (),
|
||||
assy.GetHashCode ()
|
||||
)
|
||||
|
||||
let typeInitTable =
|
||||
this.TypeInitTable |> TypeInitTable.markInitialised thread (handle, assy)
|
||||
|
||||
{ this with
|
||||
TypeInitTable = typeInitTable
|
||||
}
|
||||
|
||||
member this.WithLoadedAssembly (name : AssemblyName) (value : DumpedAssembly) =
|
||||
{ this with
|
||||
_LoadedAssemblies = this._LoadedAssemblies.Add (name.FullName, value)
|
||||
}
|
||||
|
||||
member this.LoadedAssembly (name : AssemblyName) : DumpedAssembly option =
|
||||
match this._LoadedAssemblies.TryGetValue name.FullName with
|
||||
| false, _ -> None
|
||||
| true, v -> Some v
|
||||
|
||||
/// 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 loadAssembly
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(referencedInAssembly : DumpedAssembly)
|
||||
(r : AssemblyReferenceHandle)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState * DumpedAssembly * AssemblyName
|
||||
=
|
||||
let assemblyRef = referencedInAssembly.AssemblyReferences.[r]
|
||||
let assemblyName = assemblyRef.Name
|
||||
|
||||
match state.LoadedAssembly assemblyName with
|
||||
| Some v -> state, v, assemblyName
|
||||
| None ->
|
||||
let logger = loggerFactory.CreateLogger typeof<Dummy>.DeclaringType
|
||||
|
||||
let assy =
|
||||
state.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 ->
|
||||
|
||||
state.WithLoadedAssembly assemblyName assy, assy, assemblyName
|
||||
|
||||
let rec internal resolveTypeFromName
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(ns : string option)
|
||||
(name : string)
|
||||
(assy : DumpedAssembly)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn>
|
||||
=
|
||||
match ns with
|
||||
| None -> failwith "what are the semantics here"
|
||||
| Some ns ->
|
||||
|
||||
match assy.TypeDef ns name with
|
||||
| Some typeDef ->
|
||||
// If resolved from TypeDef, it won't have generic parameters, I hope?
|
||||
let typeDef =
|
||||
typeDef
|
||||
|> TypeInfo.mapGeneric (fun _ -> failwith<TypeDefn> "no generic parameters")
|
||||
|
||||
state, assy, typeDef
|
||||
| None ->
|
||||
|
||||
match assy.TypeRef ns name with
|
||||
| Some typeRef -> resolveTypeFromRef loggerFactory assy typeRef state
|
||||
| None ->
|
||||
|
||||
match assy.ExportedType (Some ns) name with
|
||||
| Some export -> resolveTypeFromExport loggerFactory assy export state
|
||||
| None -> failwith $"TODO: type resolution unimplemented for {ns} {name}"
|
||||
|
||||
and resolveTypeFromExport
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(fromAssembly : DumpedAssembly)
|
||||
(ty : WoofWare.PawPrint.ExportedType)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn>
|
||||
=
|
||||
match ty.Data with
|
||||
| NonForwarded _ -> failwith "Somehow didn't find type definition but it is exported"
|
||||
| ForwardsTo assy ->
|
||||
let state, targetAssy, _ = loadAssembly loggerFactory fromAssembly assy state
|
||||
resolveTypeFromName loggerFactory ty.Namespace ty.Name targetAssy state
|
||||
|
||||
and resolveTypeFromRef
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(referencedInAssembly : DumpedAssembly)
|
||||
(target : TypeRef)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn>
|
||||
=
|
||||
match target.ResolutionScope with
|
||||
| AssemblyReference r ->
|
||||
let state, assy, newAssyName =
|
||||
loadAssembly loggerFactory referencedInAssembly r state
|
||||
|
||||
let nsPath = target.Namespace.Split '.' |> Array.toList
|
||||
|
||||
let targetNs = assy.NonRootNamespaces.[nsPath]
|
||||
|
||||
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 ] ->
|
||||
// If resolved from TypeDef (above), it won't have generic parameters, I hope?
|
||||
let t =
|
||||
t |> TypeInfo.mapGeneric (fun _ -> failwith<TypeDefn> "no generic parameters")
|
||||
|
||||
state, 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 loggerFactory assy ty state
|
||||
| k -> failwith $"Unexpected: {k}"
|
||||
|
||||
and resolveType
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(ty : TypeReferenceHandle)
|
||||
(assy : DumpedAssembly)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn>
|
||||
=
|
||||
let target = assy.TypeRefs.[ty]
|
||||
|
||||
resolveTypeFromRef loggerFactory assy target state
|
||||
|
||||
let rec resolveTypeFromDefn
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(ty : TypeDefn)
|
||||
(assy : DumpedAssembly)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState *
|
||||
DumpedAssembly *
|
||||
WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter> *
|
||||
TypeDefn ImmutableArray option
|
||||
=
|
||||
match ty with
|
||||
| TypeDefn.GenericInstantiation (generic, args) ->
|
||||
let state, _, generic, subArgs =
|
||||
resolveTypeFromDefn loggerFactory generic assy state
|
||||
|
||||
match subArgs with
|
||||
| Some _ -> failwith "unexpectedly had multiple generic instantiations for the same type"
|
||||
| None ->
|
||||
|
||||
state, assy, generic, Some args
|
||||
| TypeDefn.FromDefinition (defn, _typeKind) -> state, assy, assy.TypeDefs.[defn], None
|
||||
| s -> failwith $"TODO: resolveTypeFromDefn unimplemented for {s}"
|
||||
|
||||
let rec resolveTypeFromSpec
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(ty : TypeSpecificationHandle)
|
||||
(assy : DumpedAssembly)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn>
|
||||
=
|
||||
let state, assy, generic, args =
|
||||
resolveTypeFromDefn loggerFactory assy.TypeSpecs.[ty].Signature assy state
|
||||
|
||||
match args with
|
||||
| None ->
|
||||
let generic =
|
||||
generic
|
||||
|> TypeInfo.mapGeneric (fun _ -> failwith<TypeDefn> "no generic parameters")
|
||||
|
||||
state, assy, generic
|
||||
| Some args ->
|
||||
let generic = TypeInfo.withGenerics args generic
|
||||
state, assy, generic
|
||||
|
||||
let callMethod
|
||||
(wasInitialising : (TypeDefinitionHandle * AssemblyName) option)
|
||||
(wasConstructing : ManagedHeapAddress option)
|
||||
(wasClassConstructor : bool)
|
||||
(methodToCall : WoofWare.PawPrint.MethodInfo)
|
||||
(thread : ThreadId)
|
||||
(threadState : ThreadState)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState
|
||||
=
|
||||
let activeMethodState = threadState.MethodStates.[threadState.ActiveMethodState]
|
||||
|
||||
let newFrame, oldFrame =
|
||||
if methodToCall.IsStatic then
|
||||
let args = ImmutableArray.CreateBuilder methodToCall.Parameters.Length
|
||||
let mutable afterPop = activeMethodState
|
||||
|
||||
for i = 0 to methodToCall.Parameters.Length - 1 do
|
||||
let poppedArg, afterPop' = afterPop |> MethodState.popFromStack
|
||||
// TODO: generics
|
||||
let zeroArg =
|
||||
CliType.zeroOf ImmutableArray.Empty methodToCall.Signature.ParameterTypes.[i]
|
||||
|
||||
let poppedArg = EvalStackValue.toCliTypeCoerced zeroArg poppedArg
|
||||
afterPop <- afterPop'
|
||||
args.Add poppedArg
|
||||
|
||||
args.Reverse ()
|
||||
|
||||
let newFrame =
|
||||
MethodState.Empty
|
||||
methodToCall
|
||||
(args.ToImmutable ())
|
||||
(Some
|
||||
{
|
||||
JumpTo = threadState.ActiveMethodState
|
||||
WasInitialisingType = wasInitialising
|
||||
WasConstructingObj = wasConstructing
|
||||
})
|
||||
|
||||
let oldFrame =
|
||||
if wasClassConstructor then
|
||||
afterPop
|
||||
else
|
||||
afterPop |> MethodState.advanceProgramCounter
|
||||
|
||||
newFrame, oldFrame
|
||||
else
|
||||
let args = ImmutableArray.CreateBuilder (methodToCall.Parameters.Length + 1)
|
||||
let poppedArg, afterPop = activeMethodState |> MethodState.popFromStack
|
||||
let mutable afterPop = afterPop
|
||||
|
||||
for i = 1 to methodToCall.Parameters.Length do
|
||||
let poppedArg, afterPop' = afterPop |> MethodState.popFromStack
|
||||
// TODO: generics
|
||||
let zeroArg =
|
||||
CliType.zeroOf ImmutableArray.Empty methodToCall.Signature.ParameterTypes.[i - 1]
|
||||
|
||||
let poppedArg = EvalStackValue.toCliTypeCoerced zeroArg poppedArg
|
||||
afterPop <- afterPop'
|
||||
args.Add poppedArg
|
||||
|
||||
// it only matters that the RuntimePointer is a RuntimePointer, so that the coercion has a target of the
|
||||
// right shape
|
||||
args.Add (
|
||||
EvalStackValue.toCliTypeCoerced (CliType.RuntimePointer (CliRuntimePointer.Unmanaged ())) poppedArg
|
||||
)
|
||||
|
||||
args.Reverse ()
|
||||
|
||||
let newFrame =
|
||||
MethodState.Empty
|
||||
methodToCall
|
||||
(args.ToImmutable ())
|
||||
(Some
|
||||
{
|
||||
JumpTo = threadState.ActiveMethodState
|
||||
WasInitialisingType = wasInitialising
|
||||
WasConstructingObj = wasConstructing
|
||||
})
|
||||
|
||||
let oldFrame = afterPop |> MethodState.advanceProgramCounter
|
||||
newFrame, oldFrame
|
||||
|
||||
let newThreadState =
|
||||
{ threadState with
|
||||
MethodStates = threadState.MethodStates.Add(newFrame).SetItem (threadState.ActiveMethodState, oldFrame)
|
||||
ActiveMethodState = threadState.MethodStates.Length
|
||||
}
|
||||
|
||||
{ state with
|
||||
ThreadState = state.ThreadState |> Map.add thread newThreadState
|
||||
}
|
||||
|
||||
let rec loadClass
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(typeDefHandle : TypeDefinitionHandle)
|
||||
(assemblyName : AssemblyName)
|
||||
(currentThread : ThreadId)
|
||||
(state : IlMachineState)
|
||||
: StateLoadResult
|
||||
=
|
||||
if typeDefHandle.IsNil then
|
||||
failwith "Called `loadClass` with a nil typedef"
|
||||
|
||||
let logger = loggerFactory.CreateLogger "LoadClass"
|
||||
|
||||
match TypeInitTable.tryGet (typeDefHandle, assemblyName) state.TypeInitTable with
|
||||
| Some TypeInitState.Initialized ->
|
||||
// Type already initialized; nothing to do
|
||||
StateLoadResult.NothingToDo state
|
||||
| Some (TypeInitState.InProgress tid) when tid = currentThread ->
|
||||
// We're already initializing this type on this thread; just proceed with the initialisation, no extra
|
||||
// class loading required.
|
||||
StateLoadResult.NothingToDo state
|
||||
| Some (TypeInitState.InProgress _) ->
|
||||
// This is usually signalled by WhatWeDid.Blocked
|
||||
failwith
|
||||
"TODO: cross-thread class init synchronization unimplemented - this thread has to wait for the other thread to finish initialisation"
|
||||
| None ->
|
||||
// We have work to do!
|
||||
|
||||
let state, origAssyName =
|
||||
state.WithThreadSwitchedToAssembly assemblyName currentThread
|
||||
|
||||
let sourceAssembly = state.LoadedAssembly assemblyName |> Option.get
|
||||
|
||||
let typeDef =
|
||||
match sourceAssembly.TypeDefs.TryGetValue typeDefHandle with
|
||||
| false, _ -> failwith $"Failed to find type definition {typeDefHandle} in {assemblyName.Name}"
|
||||
| true, v -> v
|
||||
|
||||
logger.LogDebug ("Resolving type {TypeDefNamespace}.{TypeDefName}", typeDef.Namespace, typeDef.Name)
|
||||
|
||||
// First mark as in-progress to detect cycles
|
||||
let state = state.WithTypeBeginInit currentThread (typeDefHandle, assemblyName)
|
||||
|
||||
// Check if the type has a base type that needs initialization
|
||||
let firstDoBaseClass =
|
||||
match typeDef.BaseType with
|
||||
| Some baseTypeInfo ->
|
||||
// Determine if base type is in the same or different assembly
|
||||
match baseTypeInfo with
|
||||
| ForeignAssemblyType (baseAssemblyName, baseTypeHandle) ->
|
||||
logger.LogDebug (
|
||||
"Resolved base type of {TypeDefNamespace}.{TypeDefName} to foreign assembly {ForeignAssemblyName}",
|
||||
typeDef.Namespace,
|
||||
typeDef.Name,
|
||||
baseAssemblyName.Name
|
||||
)
|
||||
|
||||
match loadClass loggerFactory baseTypeHandle baseAssemblyName currentThread state with
|
||||
| FirstLoadThis state -> Error state
|
||||
| NothingToDo state -> Ok state
|
||||
| TypeDef typeDefinitionHandle ->
|
||||
logger.LogDebug (
|
||||
"Resolved base type of {TypeDefNamespace}.{TypeDefName} to this assembly, typedef",
|
||||
typeDef.Namespace,
|
||||
typeDef.Name
|
||||
)
|
||||
|
||||
match loadClass loggerFactory typeDefinitionHandle assemblyName currentThread state with
|
||||
| FirstLoadThis state -> Error state
|
||||
| NothingToDo state -> Ok state
|
||||
| TypeRef typeReferenceHandle ->
|
||||
let state, assy, targetType =
|
||||
resolveType loggerFactory typeReferenceHandle (state.ActiveAssembly currentThread) state
|
||||
|
||||
logger.LogDebug (
|
||||
"Resolved base type of {TypeDefNamespace}.{TypeDefName} to a typeref in assembly {ResolvedAssemblyName}, {BaseTypeNamespace}.{BaseTypeName}",
|
||||
typeDef.Namespace,
|
||||
typeDef.Name,
|
||||
assy.Name.Name,
|
||||
targetType.Namespace,
|
||||
targetType.Name
|
||||
)
|
||||
|
||||
match loadClass loggerFactory targetType.TypeDefHandle assy.Name currentThread state with
|
||||
| FirstLoadThis state -> Error state
|
||||
| NothingToDo state -> Ok state
|
||||
| TypeSpec typeSpecificationHandle -> failwith "TODO: TypeSpec base type loading unimplemented"
|
||||
| None -> Ok state // No base type (or it's System.Object)
|
||||
|
||||
match firstDoBaseClass with
|
||||
| Error state -> FirstLoadThis state
|
||||
| Ok state ->
|
||||
|
||||
// TODO: also need to initialise all interfaces implemented by the type
|
||||
|
||||
// Find the class constructor (.cctor) if it exists
|
||||
let cctor =
|
||||
typeDef.Methods
|
||||
|> List.tryFind (fun method -> method.Name = ".cctor" && method.IsStatic && method.Parameters.IsEmpty)
|
||||
|
||||
match cctor with
|
||||
| Some ctorMethod ->
|
||||
// Call the class constructor! Note that we *don't* use `callMethodInActiveAssembly`, because that
|
||||
// performs class loading, but we're already in the middle of loading this class.
|
||||
// TODO: factor out the common bit.
|
||||
let currentThreadState = state.ThreadState.[currentThread]
|
||||
|
||||
callMethod
|
||||
(Some (typeDefHandle, assemblyName))
|
||||
None
|
||||
true
|
||||
ctorMethod
|
||||
currentThread
|
||||
currentThreadState
|
||||
state
|
||||
|> FirstLoadThis
|
||||
| None ->
|
||||
// No constructor, just continue.
|
||||
// Mark the type as initialized.
|
||||
let state = state.WithTypeEndInit currentThread (typeDefHandle, assemblyName)
|
||||
|
||||
// Restore original assembly context if needed
|
||||
state.WithThreadSwitchedToAssembly origAssyName currentThread
|
||||
|> fst
|
||||
|> NothingToDo
|
||||
|
||||
let callMethodInActiveAssembly
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(thread : ThreadId)
|
||||
(methodToCall : WoofWare.PawPrint.MethodInfo)
|
||||
(weAreConstructingObj : ManagedHeapAddress option)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState * WhatWeDid
|
||||
=
|
||||
let threadState = state.ThreadState.[thread]
|
||||
|
||||
match TypeInitTable.tryGet methodToCall.DeclaringType state.TypeInitTable with
|
||||
| None ->
|
||||
match
|
||||
loadClass loggerFactory (fst methodToCall.DeclaringType) (snd methodToCall.DeclaringType) thread state
|
||||
with
|
||||
| NothingToDo state ->
|
||||
callMethod None weAreConstructingObj false methodToCall thread threadState state, WhatWeDid.Executed
|
||||
| FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit
|
||||
| Some TypeInitState.Initialized ->
|
||||
callMethod None weAreConstructingObj false methodToCall thread threadState state, WhatWeDid.Executed
|
||||
| Some (InProgress threadId) ->
|
||||
if threadId = thread then
|
||||
// II.10.5.3.2: avoid the deadlock by simply proceeding.
|
||||
callMethod None weAreConstructingObj false methodToCall thread threadState state, WhatWeDid.Executed
|
||||
else
|
||||
state, WhatWeDid.BlockedOnClassInit threadId
|
||||
|
||||
let initial
|
||||
(lf : ILoggerFactory)
|
||||
(dotnetRuntimeDirs : ImmutableArray<string>)
|
||||
(entryAssembly : DumpedAssembly)
|
||||
: IlMachineState
|
||||
=
|
||||
let assyName = entryAssembly.ThisAssemblyDefinition.Name
|
||||
let logger = lf.CreateLogger "IlMachineState"
|
||||
|
||||
let state =
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
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<'generic>
|
||||
(typeInfo : WoofWare.PawPrint.TypeInfo<'generic>)
|
||||
(fields : (string * CliType) list)
|
||||
(state : IlMachineState)
|
||||
: ManagedHeapAddress * IlMachineState
|
||||
=
|
||||
let o =
|
||||
{
|
||||
Fields = Map.ofList fields
|
||||
Type = TypeInfoCrate.make typeInfo
|
||||
}
|
||||
|
||||
let alloc, heap = state.ManagedHeap |> ManagedHeap.AllocateNonArray o
|
||||
|
||||
let state =
|
||||
{ state with
|
||||
ManagedHeap = heap
|
||||
}
|
||||
|
||||
alloc, state
|
||||
|
||||
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 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 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 setArrayValue
|
||||
(arrayAllocation : ManagedHeapAddress)
|
||||
(v : CliType)
|
||||
(index : int)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState
|
||||
=
|
||||
let heap = ManagedHeap.SetArrayValue arrayAllocation index v state.ManagedHeap
|
||||
|
||||
{ state with
|
||||
ManagedHeap = heap
|
||||
}
|
||||
|
||||
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 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)
|
||||
(assy : DumpedAssembly)
|
||||
(m : MemberReferenceHandle)
|
||||
(state : IlMachineState)
|
||||
: IlMachineState * AssemblyName * Choice<WoofWare.PawPrint.MethodInfo, WoofWare.PawPrint.FieldInfo>
|
||||
=
|
||||
// TODO: do we need to initialise the parent class here?
|
||||
let mem = assy.Members.[m]
|
||||
|
||||
let memberName : string = assy.Strings mem.Name
|
||||
|
||||
let state, assy, targetType =
|
||||
match mem.Parent with
|
||||
| MetadataToken.TypeReference parent -> resolveType loggerFactory parent assy state
|
||||
| MetadataToken.TypeSpecification parent -> resolveTypeFromSpec loggerFactory parent assy state
|
||||
| parent -> failwith $"Unexpected: {parent}"
|
||||
|
||||
match mem.Signature with
|
||||
| MemberSignature.Field fieldSig ->
|
||||
let availableFields =
|
||||
targetType.Fields
|
||||
|> List.filter (fun fi -> fi.Name = memberName)
|
||||
|> List.filter (fun fi -> fi.Signature = fieldSig)
|
||||
|
||||
let field =
|
||||
match availableFields with
|
||||
| [] ->
|
||||
failwith
|
||||
$"Could not find field member {memberName} with the right signature on {targetType.Namespace}.{targetType.Name}"
|
||||
| [ x ] -> x
|
||||
| _ ->
|
||||
failwith
|
||||
$"Multiple overloads matching signature for {targetType.Namespace}.{targetType.Name}'s field {memberName}!"
|
||||
|
||||
state, assy.Name, Choice2Of2 field
|
||||
|
||||
| MemberSignature.Method memberSig ->
|
||||
let availableMethods =
|
||||
targetType.Methods
|
||||
|> List.filter (fun mi -> mi.Name = memberName)
|
||||
|> List.filter (fun mi -> mi.Signature = memberSig)
|
||||
|
||||
let method =
|
||||
match availableMethods with
|
||||
| [] ->
|
||||
failwith
|
||||
$"Could not find member {memberName} with the right signature on {targetType.Namespace}.{targetType.Name}"
|
||||
| [ x ] -> x
|
||||
| _ ->
|
||||
failwith
|
||||
$"Multiple overloads matching signature for call to {targetType.Namespace}.{targetType.Name}'s {memberName}!"
|
||||
|
||||
state, assy.Name, Choice1Of2 method
|
@@ -98,7 +98,6 @@ module AssemblyReference =
|
||||
Version = ref.Version
|
||||
}
|
||||
|
||||
|
||||
type NullaryIlOp =
|
||||
| Nop
|
||||
| LdArg0
|
||||
@@ -308,6 +307,10 @@ type UnaryConstIlOp =
|
||||
| Ldarg_s of uint8
|
||||
| Ldarga_s of uint8
|
||||
| Leave of int32
|
||||
/// Unconditionally transfer control to this offset from the next instruction;
|
||||
/// like Br but can leave a try/filter/catch block too, and ensures surrounding `finally` blocks execute.
|
||||
/// Unconditionally empties the evaluation stack; so a Leave outside an exception-handling block is just a Br which
|
||||
/// also clears the eval stack.
|
||||
| Leave_s of int8
|
||||
| Starg_s of uint8
|
||||
| Starg of uint16
|
||||
|
@@ -2,6 +2,7 @@ namespace WoofWare.PawPrint
|
||||
|
||||
#nowarn "9"
|
||||
|
||||
open System
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection
|
||||
open System.Reflection.Metadata
|
||||
@@ -84,6 +85,37 @@ module GenericParameter =
|
||||
)
|
||||
|> ImmutableArray.CreateRange
|
||||
|
||||
type ExceptionOffset =
|
||||
{
|
||||
TryLength : int
|
||||
TryOffset : int
|
||||
HandlerLength : int
|
||||
HandlerOffset : int
|
||||
}
|
||||
|
||||
type ExceptionRegion =
|
||||
| Filter of filterOffset : int * ExceptionOffset
|
||||
/// Token is a TypeRef, TypeDef, or TypeSpec
|
||||
| Catch of MetadataToken * ExceptionOffset
|
||||
| Finally of ExceptionOffset
|
||||
| Fault of ExceptionOffset
|
||||
|
||||
static member OfExceptionRegion (r : System.Reflection.Metadata.ExceptionRegion) : ExceptionRegion =
|
||||
let offset =
|
||||
{
|
||||
HandlerLength = r.HandlerLength
|
||||
HandlerOffset = r.HandlerOffset
|
||||
TryLength = r.TryLength
|
||||
TryOffset = r.TryOffset
|
||||
}
|
||||
|
||||
match r.Kind with
|
||||
| ExceptionRegionKind.Catch -> ExceptionRegion.Catch (MetadataToken.ofEntityHandle r.CatchType, offset)
|
||||
| ExceptionRegionKind.Filter -> ExceptionRegion.Filter (r.FilterOffset, offset)
|
||||
| ExceptionRegionKind.Finally -> ExceptionRegion.Finally offset
|
||||
| ExceptionRegionKind.Fault -> ExceptionRegion.Fault offset
|
||||
| _ -> raise (ArgumentOutOfRangeException ())
|
||||
|
||||
type MethodInstructions =
|
||||
{
|
||||
/// <summary>
|
||||
@@ -105,6 +137,8 @@ type MethodInstructions =
|
||||
LocalsInit : bool
|
||||
|
||||
LocalVars : ImmutableArray<TypeDefn> option
|
||||
|
||||
ExceptionRegions : ImmutableArray<ExceptionRegion>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -505,12 +539,17 @@ module MethodInfo =
|
||||
|
||||
let instructions = readInstructions []
|
||||
|
||||
let er =
|
||||
methodBody.ExceptionRegions
|
||||
|> Seq.map ExceptionRegion.OfExceptionRegion
|
||||
|> ImmutableArray.CreateRange
|
||||
|
||||
{
|
||||
Instructions = instructions
|
||||
LocalInit = methodBody.LocalVariablesInitialized
|
||||
LocalSig = localSig
|
||||
MaxStackSize = methodBody.MaxStack
|
||||
ExceptionRegions = methodBody.ExceptionRegions
|
||||
ExceptionRegions = er
|
||||
}
|
||||
|> Some
|
||||
|
||||
@@ -547,6 +586,7 @@ module MethodInfo =
|
||||
Locations = body.Instructions |> List.map (fun (a, b) -> b, a) |> Map.ofList
|
||||
LocalsInit = body.LocalInit
|
||||
LocalVars = body.LocalSig
|
||||
ExceptionRegions = body.ExceptionRegions
|
||||
}
|
||||
|> Some
|
||||
|
||||
|
142
WoofWare.PawPrint/MethodState.fs
Normal file
142
WoofWare.PawPrint/MethodState.fs
Normal file
@@ -0,0 +1,142 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection
|
||||
open System.Reflection.Metadata
|
||||
|
||||
type MethodReturnState =
|
||||
{
|
||||
/// Index in the MethodStates array of a ThreadState
|
||||
JumpTo : int
|
||||
WasInitialisingType : (TypeDefinitionHandle * AssemblyName) option
|
||||
/// The Newobj instruction means we need to push a reference immediately after Ret.
|
||||
WasConstructingObj : ManagedHeapAddress option
|
||||
}
|
||||
|
||||
and MethodState =
|
||||
{
|
||||
// TODO: local variables are initialised to 0 if the localsinit flag is set for the method
|
||||
LocalVariables : CliType ImmutableArray
|
||||
/// Index into the stream of IL bytes.
|
||||
IlOpIndex : int
|
||||
EvaluationStack : EvalStack
|
||||
Arguments : CliType ImmutableArray
|
||||
ExecutingMethod : WoofWare.PawPrint.MethodInfo
|
||||
/// 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
|
||||
}
|
||||
|
||||
static member jumpProgramCounter (bytes : int) (state : MethodState) =
|
||||
{ state with
|
||||
IlOpIndex = state.IlOpIndex + bytes
|
||||
}
|
||||
|
||||
static member advanceProgramCounter (state : MethodState) =
|
||||
MethodState.jumpProgramCounter
|
||||
(IlOp.NumberOfBytes state.ExecutingMethod.Instructions.Value.Locations.[state.IlOpIndex])
|
||||
state
|
||||
|
||||
static member peekEvalStack (state : MethodState) : EvalStackValue option = EvalStack.Peek state.EvaluationStack
|
||||
|
||||
static member pushToEvalStack' (e : EvalStackValue) (state : MethodState) : MethodState =
|
||||
{ state with
|
||||
EvaluationStack = EvalStack.Push' e state.EvaluationStack
|
||||
}
|
||||
|
||||
static member pushToEvalStack (o : CliType) (state : MethodState) : MethodState =
|
||||
{ state with
|
||||
EvaluationStack = EvalStack.Push o state.EvaluationStack
|
||||
}
|
||||
|
||||
/// Pop the eval stack into the given argument slot.
|
||||
static member popFromStackToArg (index : int) (state : MethodState) : MethodState =
|
||||
let popped, state = MethodState.popFromStack state
|
||||
|
||||
let arg =
|
||||
if index < state.Arguments.Length then
|
||||
state.Arguments.[index]
|
||||
else
|
||||
failwith
|
||||
$"Tried to get element {index} of the args list for method {state.ExecutingMethod.Name}, which has only {state.Arguments.Length} elements"
|
||||
|
||||
let popped = EvalStackValue.toCliTypeCoerced arg popped
|
||||
|
||||
{ state with
|
||||
Arguments = state.Arguments.SetItem (index, popped)
|
||||
}
|
||||
|
||||
static member loadArgument (index : int) (state : MethodState) : MethodState =
|
||||
// Correct CIL guarantees that we are loading an argument from an index that exists.
|
||||
MethodState.pushToEvalStack state.Arguments.[index] state
|
||||
|
||||
static member popFromStack (state : MethodState) : EvalStackValue * MethodState =
|
||||
let popped, newStack = EvalStack.Pop state.EvaluationStack
|
||||
|
||||
let state =
|
||||
{ state with
|
||||
EvaluationStack = newStack
|
||||
}
|
||||
|
||||
popped, state
|
||||
|
||||
static member popFromStackToVariable (localVariableIndex : int) (state : MethodState) : MethodState =
|
||||
if localVariableIndex >= state.LocalVariables.Length then
|
||||
failwith
|
||||
$"Tried to access zero-indexed local variable %i{localVariableIndex} but only %i{state.LocalVariables.Length} exist"
|
||||
|
||||
if localVariableIndex < 0 || localVariableIndex >= 65535 then
|
||||
failwith $"Incorrect CIL encountered: local variable index has value %i{localVariableIndex}"
|
||||
|
||||
let popped, state = MethodState.popFromStack state
|
||||
|
||||
let desiredValue =
|
||||
EvalStackValue.toCliTypeCoerced state.LocalVariables.[localVariableIndex] popped
|
||||
|
||||
{ state with
|
||||
LocalVariables = state.LocalVariables.SetItem (localVariableIndex, desiredValue)
|
||||
}
|
||||
|
||||
/// `args` must be populated with entries of the right type.
|
||||
/// 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
|
||||
(method : WoofWare.PawPrint.MethodInfo)
|
||||
(args : ImmutableArray<CliType>)
|
||||
(returnState : MethodReturnState option)
|
||||
: MethodState
|
||||
=
|
||||
do
|
||||
if method.IsStatic then
|
||||
if args.Length <> method.Parameters.Length then
|
||||
failwith
|
||||
$"Static method {method.Name} should have had %i{method.Parameters.Length} parameters, but was given %i{args.Length}"
|
||||
else if args.Length <> method.Parameters.Length + 1 then
|
||||
failwith
|
||||
$"Non-static method {method.Name} should have had %i{method.Parameters.Length + 1} parameters, but was given %i{args.Length}"
|
||||
|
||||
let localVariableSig =
|
||||
match method.Instructions with
|
||||
| None -> ImmutableArray.Empty
|
||||
| Some method ->
|
||||
match method.LocalVars with
|
||||
| None -> ImmutableArray.Empty
|
||||
| Some vars -> vars
|
||||
// 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 localVars =
|
||||
// TODO: generics?
|
||||
localVariableSig
|
||||
|> Seq.map (CliType.zeroOf ImmutableArray.Empty)
|
||||
|> ImmutableArray.CreateRange
|
||||
|
||||
{
|
||||
EvaluationStack = EvalStack.Empty
|
||||
LocalVariables = localVars
|
||||
IlOpIndex = 0
|
||||
Arguments = args
|
||||
ExecutingMethod = method
|
||||
LocalMemoryPool = ()
|
||||
ReturnState = returnState
|
||||
}
|
508
WoofWare.PawPrint/NullaryIlOp.fs
Normal file
508
WoofWare.PawPrint/NullaryIlOp.fs
Normal file
@@ -0,0 +1,508 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
#nowarn "42"
|
||||
|
||||
open System.Collections.Immutable
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module NullaryIlOp =
|
||||
let internal execute (state : IlMachineState) (currentThread : ThreadId) (op : NullaryIlOp) : ExecutionResult =
|
||||
match op with
|
||||
| Nop ->
|
||||
(IlMachineState.advanceProgramCounter currentThread state, WhatWeDid.Executed)
|
||||
|> ExecutionResult.Stepped
|
||||
| LdArg0 ->
|
||||
state
|
||||
|> IlMachineState.loadArgument currentThread 0
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| LdArg1 ->
|
||||
state
|
||||
|> IlMachineState.loadArgument currentThread 1
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| LdArg2 ->
|
||||
state
|
||||
|> IlMachineState.loadArgument currentThread 2
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| LdArg3 ->
|
||||
state
|
||||
|> IlMachineState.loadArgument currentThread 3
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Ldloc_0 ->
|
||||
let localVar = state.ThreadState.[currentThread].MethodState.LocalVariables.[0]
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack localVar currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Ldloc_1 ->
|
||||
let localVar = state.ThreadState.[currentThread].MethodState.LocalVariables.[1]
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack localVar currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Ldloc_2 ->
|
||||
let localVar = state.ThreadState.[currentThread].MethodState.LocalVariables.[2]
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack localVar currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Ldloc_3 ->
|
||||
let localVar = state.ThreadState.[currentThread].MethodState.LocalVariables.[3]
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack localVar currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Pop -> failwith "TODO: Pop unimplemented"
|
||||
| Dup ->
|
||||
let topValue =
|
||||
match IlMachineState.peekEvalStack currentThread state with
|
||||
| None -> failwith "tried to Dup when nothing on top of stack"
|
||||
| Some v -> v
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' topValue currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Ret ->
|
||||
let threadStateAtEndOfMethod = state.ThreadState.[currentThread]
|
||||
|
||||
match threadStateAtEndOfMethod.MethodState.ReturnState with
|
||||
| None -> ExecutionResult.Terminated (state, currentThread)
|
||||
| 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 =
|
||||
snd
|
||||
threadStateAtEndOfMethod.MethodStates.[returnState.JumpTo].ExecutingMethod
|
||||
.DeclaringType
|
||||
}
|
||||
}
|
||||
|
||||
let state =
|
||||
match returnState.WasConstructingObj with
|
||||
| Some constructing ->
|
||||
// Assumption: a constructor can't also return a value.
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.OfManagedObject constructing) currentThread
|
||||
| None ->
|
||||
match threadStateAtEndOfMethod.MethodState.EvaluationStack.Values with
|
||||
| [] ->
|
||||
// no return value
|
||||
state
|
||||
| [ retVal ] ->
|
||||
let retType =
|
||||
threadStateAtEndOfMethod.MethodState.ExecutingMethod.Signature.ReturnType
|
||||
|
||||
match retType with
|
||||
| TypeDefn.Void -> state
|
||||
| retType ->
|
||||
// TODO: generics
|
||||
let toPush =
|
||||
EvalStackValue.toCliTypeCoerced (CliType.zeroOf ImmutableArray.Empty retType) retVal
|
||||
|
||||
state |> IlMachineState.pushToEvalStack toPush currentThread
|
||||
| _ ->
|
||||
failwith
|
||||
"Unexpected interpretation result has a local evaluation stack with more than one element on RET"
|
||||
|
||||
state |> Tuple.withRight WhatWeDid.Executed |> ExecutionResult.Stepped
|
||||
|
||||
| LdcI4_0 ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 0)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| LdcI4_1 ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 1)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| LdcI4_2 ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 2)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| LdcI4_3 ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 3)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| LdcI4_4 ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 4)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| LdcI4_5 ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 5)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| LdcI4_6 ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 6)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| LdcI4_7 ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 7)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| LdcI4_8 ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 8)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| LdcI4_m1 -> failwith "TODO: LdcI4_m1 unimplemented"
|
||||
| LdNull ->
|
||||
let state =
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack'
|
||||
(EvalStackValue.ManagedPointer ManagedPointerSource.Null)
|
||||
currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||
| Ceq ->
|
||||
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 ->
|
||||
failwith $"TODO (CEQ): nativeint vs nativeint"
|
||||
| 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 ->
|
||||
failwith $"TODO (CEQ): managed pointers"
|
||||
| EvalStackValue.ManagedPointer var1, EvalStackValue.NativeInt var2 ->
|
||||
failwith $"TODO (CEQ): managed pointer vs nativeint"
|
||||
| EvalStackValue.ManagedPointer _, _ -> failwith $"bad ceq: ManagedPointer vs {var2}"
|
||||
| EvalStackValue.UserDefinedValueType, _ -> failwith $"bad ceq: UserDefinedValueType vs {var2}"
|
||||
|
||||
state
|
||||
|> 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 var1 < var2 then 1 else 0
|
||||
| EvalStackValue.NativeInt var1, other -> failwith $"invalid comparison, nativeint {var1} vs %O{other}"
|
||||
| EvalStackValue.ManagedPointer managedPointerSource, NativeInt int64 ->
|
||||
failwith "TODO: Clt ManagedPointer vs NativeInt comparison unimplemented"
|
||||
| EvalStackValue.ManagedPointer managedPointerSource, ManagedPointer pointerSource ->
|
||||
failwith "TODO: Clt ManagedPointer vs ManagedPointer comparison unimplemented"
|
||||
| EvalStackValue.ManagedPointer managedPointerSource, UserDefinedValueType ->
|
||||
failwith "TODO: Clt ManagedPointer vs UserDefinedValueType comparison unimplemented"
|
||||
| EvalStackValue.UserDefinedValueType, NativeInt int64 ->
|
||||
failwith "TODO: Clt UserDefinedValueType vs NativeInt comparison unimplemented"
|
||||
| EvalStackValue.UserDefinedValueType, ManagedPointer managedPointerSource ->
|
||||
failwith "TODO: Clt UserDefinedValueType vs ManagedPointer comparison unimplemented"
|
||||
| EvalStackValue.UserDefinedValueType, UserDefinedValueType ->
|
||||
failwith "TODO: Clt UserDefinedValueType vs UserDefinedValueType comparison unimplemented"
|
||||
|
||||
state
|
||||
|> 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
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Stloc_1 ->
|
||||
state
|
||||
|> IlMachineState.popFromStackToLocalVariable currentThread 1
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Stloc_2 ->
|
||||
state
|
||||
|> IlMachineState.popFromStackToLocalVariable currentThread 2
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Stloc_3 ->
|
||||
state
|
||||
|> IlMachineState.popFromStackToLocalVariable currentThread 3
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Sub -> failwith "TODO: Sub unimplemented"
|
||||
| Sub_ovf -> failwith "TODO: Sub_ovf unimplemented"
|
||||
| Sub_ovf_un -> failwith "TODO: Sub_ovf_un unimplemented"
|
||||
| Add ->
|
||||
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 add operation: {val1} and {val2}"
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' result currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Add_ovf -> failwith "TODO: Add_ovf unimplemented"
|
||||
| Add_ovf_un -> failwith "TODO: Add_ovf_un unimplemented"
|
||||
| Mul -> failwith "TODO: Mul unimplemented"
|
||||
| Mul_ovf -> failwith "TODO: Mul_ovf unimplemented"
|
||||
| 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_un -> failwith "TODO: Shr_un unimplemented"
|
||||
| Shl -> failwith "TODO: Shl unimplemented"
|
||||
| And -> failwith "TODO: And unimplemented"
|
||||
| Or -> failwith "TODO: Or unimplemented"
|
||||
| Xor -> failwith "TODO: Xor unimplemented"
|
||||
| Conv_I -> failwith "TODO: Conv_I unimplemented"
|
||||
| Conv_I1 -> failwith "TODO: Conv_I1 unimplemented"
|
||||
| Conv_I2 -> failwith "TODO: Conv_I2 unimplemented"
|
||||
| Conv_I4 -> failwith "TODO: Conv_I4 unimplemented"
|
||||
| Conv_I8 -> failwith "TODO: Conv_I8 unimplemented"
|
||||
| Conv_R4 -> failwith "TODO: Conv_R4 unimplemented"
|
||||
| Conv_R8 -> failwith "TODO: Conv_R8 unimplemented"
|
||||
| Conv_U ->
|
||||
let popped, state = IlMachineState.popEvalStack currentThread state
|
||||
let converted = EvalStackValue.toUnsignedNativeInt popped
|
||||
|
||||
let state =
|
||||
match converted with
|
||||
| None -> failwith "TODO: Conv_U conversion failure unimplemented"
|
||||
| Some conv ->
|
||||
// > If overflow occurs when converting one integer type to another, the high-order bits are silently truncated.
|
||||
let conv =
|
||||
match conv with
|
||||
| UnsignedNativeIntSource.Verbatim conv ->
|
||||
if conv > uint64 System.Int64.MaxValue then
|
||||
(conv % uint64 System.Int64.MaxValue) |> int64 |> NativeIntSource.Verbatim
|
||||
else
|
||||
int64 conv |> NativeIntSource.Verbatim
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack' (EvalStackValue.NativeInt conv) currentThread
|
||||
|
||||
let state = state |> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||
| Conv_U1 -> failwith "TODO: Conv_U1 unimplemented"
|
||||
| Conv_U2 -> failwith "TODO: Conv_U2 unimplemented"
|
||||
| Conv_U4 -> failwith "TODO: Conv_U4 unimplemented"
|
||||
| Conv_U8 -> failwith "TODO: Conv_U8 unimplemented"
|
||||
| LdLen -> failwith "TODO: LdLen unimplemented"
|
||||
| Endfilter -> failwith "TODO: Endfilter unimplemented"
|
||||
| Endfinally -> failwith "TODO: Endfinally unimplemented"
|
||||
| Rethrow -> failwith "TODO: Rethrow unimplemented"
|
||||
| Throw -> failwith "TODO: Throw unimplemented"
|
||||
| Localloc -> failwith "TODO: Localloc unimplemented"
|
||||
| Stind_I -> failwith "TODO: Stind_I unimplemented"
|
||||
| Stind_I1 -> failwith "TODO: Stind_I1 unimplemented"
|
||||
| Stind_I2 -> failwith "TODO: Stind_I2 unimplemented"
|
||||
| Stind_I4 -> failwith "TODO: Stind_I4 unimplemented"
|
||||
| Stind_I8 -> failwith "TODO: Stind_I8 unimplemented"
|
||||
| Stind_R4 -> failwith "TODO: Stind_R4 unimplemented"
|
||||
| Stind_R8 -> failwith "TODO: Stind_R8 unimplemented"
|
||||
| Ldind_i -> failwith "TODO: Ldind_i unimplemented"
|
||||
| Ldind_i1 -> failwith "TODO: Ldind_i1 unimplemented"
|
||||
| Ldind_i2 -> failwith "TODO: Ldind_i2 unimplemented"
|
||||
| Ldind_i4 -> failwith "TODO: Ldind_i4 unimplemented"
|
||||
| Ldind_i8 -> failwith "TODO: Ldind_i8 unimplemented"
|
||||
| Ldind_u1 ->
|
||||
let popped, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let value =
|
||||
match popped with
|
||||
| EvalStackValue.NativeInt nativeIntSource -> failwith $"TODO: in Ldind_u1, {nativeIntSource}"
|
||||
| EvalStackValue.ManagedPointer src ->
|
||||
match src with
|
||||
| ManagedPointerSource.Null -> failwith "unexpected null pointer in Ldind_u1"
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
||||
let methodState =
|
||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables
|
||||
.[int<uint16> whichVar]
|
||||
|
||||
match methodState with
|
||||
| CliType.Bool b -> b
|
||||
| CliType.Numeric numeric -> failwith $"tried to load a Numeric as a u8: {numeric}"
|
||||
| CliType.Char _ -> failwith "tried to load a Char as a u8"
|
||||
| CliType.ObjectRef _ -> failwith "tried to load an ObjectRef as a u8"
|
||||
| CliType.RuntimePointer _ -> failwith "tried to load a RuntimePointer as a u8"
|
||||
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
|
||||
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
|
||||
| popped -> failwith $"unexpected Ldind_u1 input: {popped}"
|
||||
|
||||
let state =
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.UInt8 value)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
|
||||
| Ldind_u2 -> failwith "TODO: Ldind_u2 unimplemented"
|
||||
| Ldind_u4 -> failwith "TODO: Ldind_u4 unimplemented"
|
||||
| Ldind_u8 -> failwith "TODO: Ldind_u8 unimplemented"
|
||||
| Ldind_r4 -> failwith "TODO: Ldind_r4 unimplemented"
|
||||
| Ldind_r8 -> failwith "TODO: Ldind_r8 unimplemented"
|
||||
| Rem -> failwith "TODO: Rem unimplemented"
|
||||
| Rem_un -> failwith "TODO: Rem_un unimplemented"
|
||||
| Volatile -> failwith "TODO: Volatile unimplemented"
|
||||
| Tail -> failwith "TODO: Tail unimplemented"
|
||||
| Conv_ovf_i_un -> failwith "TODO: Conv_ovf_i_un unimplemented"
|
||||
| Conv_ovf_u_un -> failwith "TODO: Conv_ovf_u_un unimplemented"
|
||||
| Conv_ovf_i1_un -> failwith "TODO: Conv_ovf_i1_un unimplemented"
|
||||
| Conv_ovf_u1_un -> failwith "TODO: Conv_ovf_u1_un unimplemented"
|
||||
| Conv_ovf_i2_un -> failwith "TODO: Conv_ovf_i2_un unimplemented"
|
||||
| Conv_ovf_u2_un -> failwith "TODO: Conv_ovf_u2_un unimplemented"
|
||||
| Conv_ovf_i4_un -> failwith "TODO: Conv_ovf_i4_un unimplemented"
|
||||
| Conv_ovf_u4_un -> failwith "TODO: Conv_ovf_u4_un unimplemented"
|
||||
| Conv_ovf_i8_un -> failwith "TODO: Conv_ovf_i8_un unimplemented"
|
||||
| Conv_ovf_u8_un -> failwith "TODO: Conv_ovf_u8_un unimplemented"
|
||||
| Conv_ovf_i -> failwith "TODO: Conv_ovf_i unimplemented"
|
||||
| Conv_ovf_u -> failwith "TODO: Conv_ovf_u unimplemented"
|
||||
| Neg -> failwith "TODO: Neg unimplemented"
|
||||
| Not -> failwith "TODO: Not unimplemented"
|
||||
| Ldind_ref -> failwith "TODO: Ldind_ref unimplemented"
|
||||
| Stind_ref -> failwith "TODO: Stind_ref unimplemented"
|
||||
| Ldelem_i -> failwith "TODO: Ldelem_i unimplemented"
|
||||
| Ldelem_i1 -> failwith "TODO: Ldelem_i1 unimplemented"
|
||||
| Ldelem_u1 -> failwith "TODO: Ldelem_u1 unimplemented"
|
||||
| Ldelem_i2 -> failwith "TODO: Ldelem_i2 unimplemented"
|
||||
| Ldelem_u2 -> failwith "TODO: Ldelem_u2 unimplemented"
|
||||
| Ldelem_i4 -> failwith "TODO: Ldelem_i4 unimplemented"
|
||||
| Ldelem_u4 -> failwith "TODO: Ldelem_u4 unimplemented"
|
||||
| Ldelem_i8 -> failwith "TODO: Ldelem_i8 unimplemented"
|
||||
| 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"
|
||||
| Stelem_u1 -> failwith "TODO: Stelem_u1 unimplemented"
|
||||
| Stelem_i2 -> failwith "TODO: Stelem_i2 unimplemented"
|
||||
| Stelem_u2 -> failwith "TODO: Stelem_u2 unimplemented"
|
||||
| Stelem_i4 -> failwith "TODO: Stelem_i4 unimplemented"
|
||||
| Stelem_u4 -> failwith "TODO: Stelem_u4 unimplemented"
|
||||
| Stelem_i8 -> failwith "TODO: Stelem_i8 unimplemented"
|
||||
| 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"
|
||||
| Cpblk -> failwith "TODO: Cpblk unimplemented"
|
||||
| Initblk -> failwith "TODO: Initblk unimplemented"
|
||||
| Conv_ovf_u1 -> failwith "TODO: Conv_ovf_u1 unimplemented"
|
||||
| Conv_ovf_u2 -> failwith "TODO: Conv_ovf_u2 unimplemented"
|
||||
| Conv_ovf_u4 -> failwith "TODO: Conv_ovf_u4 unimplemented"
|
||||
| Conv_ovf_u8 -> failwith "TODO: Conv_ovf_u8 unimplemented"
|
||||
| Conv_ovf_i1 -> failwith "TODO: Conv_ovf_i1 unimplemented"
|
||||
| Conv_ovf_i2 -> failwith "TODO: Conv_ovf_i2 unimplemented"
|
||||
| Conv_ovf_i4 -> failwith "TODO: Conv_ovf_i4 unimplemented"
|
||||
| Conv_ovf_i8 -> failwith "TODO: Conv_ovf_i8 unimplemented"
|
||||
| Break -> failwith "TODO: Break unimplemented"
|
||||
| Conv_r_un -> failwith "TODO: Conv_r_un unimplemented"
|
||||
| Arglist -> failwith "TODO: Arglist unimplemented"
|
||||
| Ckfinite -> failwith "TODO: Ckfinite unimplemented"
|
||||
| Readonly -> failwith "TODO: Readonly unimplemented"
|
||||
| Refanytype -> failwith "TODO: Refanytype unimplemented"
|
92
WoofWare.PawPrint/ThreadState.fs
Normal file
92
WoofWare.PawPrint/ThreadState.fs
Normal file
@@ -0,0 +1,92 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection
|
||||
|
||||
type ThreadState =
|
||||
{
|
||||
// TODO: thread-local storage, synchronisation state, exception handling context
|
||||
MethodStates : MethodState ImmutableArray
|
||||
ActiveMethodState : int
|
||||
ActiveAssembly : AssemblyName
|
||||
}
|
||||
|
||||
member this.MethodState = this.MethodStates.[this.ActiveMethodState]
|
||||
|
||||
static member New (activeAssy : AssemblyName) (methodState : MethodState) =
|
||||
{
|
||||
ActiveMethodState = 0
|
||||
MethodStates = ImmutableArray.Create methodState
|
||||
ActiveAssembly = activeAssy
|
||||
}
|
||||
|
||||
static member peekEvalStack (state : ThreadState) : EvalStackValue option =
|
||||
MethodState.peekEvalStack state.MethodStates.[state.ActiveMethodState]
|
||||
|
||||
static member popFromEvalStack (state : ThreadState) : EvalStackValue * ThreadState =
|
||||
let activeMethodState = state.MethodStates.[state.ActiveMethodState]
|
||||
let ret, popped = activeMethodState |> MethodState.popFromStack
|
||||
|
||||
let state =
|
||||
{ state with
|
||||
MethodStates = state.MethodStates.SetItem (state.ActiveMethodState, popped)
|
||||
}
|
||||
|
||||
ret, state
|
||||
|
||||
static member pushToEvalStack (o : CliType) (methodStateIndex : int) (state : ThreadState) =
|
||||
let newMethodStates =
|
||||
state.MethodStates.SetItem (
|
||||
methodStateIndex,
|
||||
MethodState.pushToEvalStack o state.MethodStates.[methodStateIndex]
|
||||
)
|
||||
|
||||
{ state with
|
||||
MethodStates = newMethodStates
|
||||
}
|
||||
|
||||
static member pushToEvalStack' (e : EvalStackValue) (methodStateIndex : int) (state : ThreadState) =
|
||||
let newMethodStates =
|
||||
state.MethodStates.SetItem (
|
||||
methodStateIndex,
|
||||
MethodState.pushToEvalStack' e state.MethodStates.[methodStateIndex]
|
||||
)
|
||||
|
||||
{ state with
|
||||
MethodStates = newMethodStates
|
||||
}
|
||||
|
||||
static member jumpProgramCounter (bytes : int) (state : ThreadState) =
|
||||
let methodState =
|
||||
state.MethodStates.SetItem (
|
||||
state.ActiveMethodState,
|
||||
state.MethodStates.[state.ActiveMethodState]
|
||||
|> MethodState.jumpProgramCounter bytes
|
||||
)
|
||||
|
||||
{ state with
|
||||
MethodStates = methodState
|
||||
}
|
||||
|
||||
static member advanceProgramCounter (state : ThreadState) =
|
||||
let methodState =
|
||||
state.MethodStates.SetItem (
|
||||
state.ActiveMethodState,
|
||||
state.MethodStates.[state.ActiveMethodState]
|
||||
|> MethodState.advanceProgramCounter
|
||||
)
|
||||
|
||||
{ state with
|
||||
MethodStates = methodState
|
||||
}
|
||||
|
||||
static member loadArgument (i : int) (state : ThreadState) =
|
||||
let methodState =
|
||||
state.MethodStates.SetItem (
|
||||
state.ActiveMethodState,
|
||||
state.MethodStates.[state.ActiveMethodState] |> MethodState.loadArgument i
|
||||
)
|
||||
|
||||
{ state with
|
||||
MethodStates = methodState
|
||||
}
|
185
WoofWare.PawPrint/UnaryConstIlOp.fs
Normal file
185
WoofWare.PawPrint/UnaryConstIlOp.fs
Normal file
@@ -0,0 +1,185 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module internal UnaryConstIlOp =
|
||||
let execute (state : IlMachineState) (currentThread : ThreadId) (op : UnaryConstIlOp) : IlMachineState * WhatWeDid =
|
||||
match op with
|
||||
| Stloc s ->
|
||||
state
|
||||
|> IlMachineState.popFromStackToLocalVariable currentThread (int s)
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Stloc_s b ->
|
||||
state
|
||||
|> IlMachineState.popFromStackToLocalVariable currentThread (int b)
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Ldc_I8 i ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int64 i)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Ldc_I4 i ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 i)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Ldc_R4 f ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Float32 f)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Ldc_R8 f ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Float64 f)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Ldc_I4_s b ->
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int8 b)) currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Br i -> failwith "TODO: Br unimplemented"
|
||||
| Br_s b ->
|
||||
state
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> IlMachineState.jumpProgramCounter currentThread (int b)
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Brfalse_s b ->
|
||||
let popped, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let isTrue =
|
||||
match popped with
|
||||
| EvalStackValue.Int32 i -> i <> 0
|
||||
| EvalStackValue.Int64 i -> i <> 0L
|
||||
| EvalStackValue.NativeInt i -> not (NativeIntSource.isZero i)
|
||||
| EvalStackValue.Float f -> failwith "TODO: Brfalse_s float semantics undocumented"
|
||||
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> false
|
||||
| EvalStackValue.ManagedPointer _ -> true
|
||||
| EvalStackValue.ObjectRef _ -> failwith "TODO: Brfalse_s ObjectRef comparison unimplemented"
|
||||
| EvalStackValue.UserDefinedValueType ->
|
||||
failwith "TODO: Brfalse_s UserDefinedValueType comparison unimplemented"
|
||||
|
||||
state
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> if isTrue then
|
||||
id
|
||||
else
|
||||
IlMachineState.jumpProgramCounter currentThread (int b)
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Brtrue_s b ->
|
||||
let popped, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let isTrue =
|
||||
match popped with
|
||||
| EvalStackValue.Int32 i -> i <> 0
|
||||
| EvalStackValue.Int64 i -> i <> 0L
|
||||
| EvalStackValue.NativeInt i -> not (NativeIntSource.isZero i)
|
||||
| EvalStackValue.Float f -> failwith "TODO: Brtrue_s float semantics undocumented"
|
||||
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> false
|
||||
| EvalStackValue.ManagedPointer _ -> true
|
||||
| EvalStackValue.ObjectRef _ -> failwith "TODO: Brtrue_s ObjectRef comparison unimplemented"
|
||||
| EvalStackValue.UserDefinedValueType ->
|
||||
failwith "TODO: Brtrue_s UserDefinedValueType comparison unimplemented"
|
||||
|
||||
state
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> if isTrue then
|
||||
IlMachineState.jumpProgramCounter currentThread (int b)
|
||||
else
|
||||
id
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Brfalse i ->
|
||||
let popped, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let isFalse =
|
||||
match popped with
|
||||
| EvalStackValue.Int32 i -> i = 0
|
||||
| EvalStackValue.Int64 i -> i = 0L
|
||||
| EvalStackValue.NativeInt i -> NativeIntSource.isZero i
|
||||
| EvalStackValue.Float f -> failwith "TODO: Brfalse float semantics undocumented"
|
||||
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> true
|
||||
| EvalStackValue.ManagedPointer _ -> false
|
||||
| EvalStackValue.ObjectRef _ -> failwith "TODO: Brfalse ObjectRef comparison unimplemented"
|
||||
| EvalStackValue.UserDefinedValueType ->
|
||||
failwith "TODO: Brfalse UserDefinedValueType comparison unimplemented"
|
||||
|
||||
state
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> if isFalse then
|
||||
IlMachineState.jumpProgramCounter currentThread i
|
||||
else
|
||||
id
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Brtrue i ->
|
||||
let popped, state = IlMachineState.popEvalStack currentThread state
|
||||
|
||||
let isTrue =
|
||||
match popped with
|
||||
| EvalStackValue.Int32 i -> i <> 0
|
||||
| EvalStackValue.Int64 i -> i <> 0L
|
||||
| EvalStackValue.NativeInt i -> not (NativeIntSource.isZero i)
|
||||
| EvalStackValue.Float f -> failwith "TODO: Brtrue float semantics undocumented"
|
||||
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> false
|
||||
| EvalStackValue.ManagedPointer _ -> true
|
||||
| EvalStackValue.ObjectRef _ -> failwith "TODO: Brtrue ObjectRef comparison unimplemented"
|
||||
| EvalStackValue.UserDefinedValueType ->
|
||||
failwith "TODO: Brtrue UserDefinedValueType comparison unimplemented"
|
||||
|
||||
state
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|> if isTrue then
|
||||
IlMachineState.jumpProgramCounter currentThread i
|
||||
else
|
||||
id
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| Beq_s b -> failwith "TODO: Beq_s unimplemented"
|
||||
| Blt_s b -> failwith "TODO: Blt_s unimplemented"
|
||||
| Ble_s b -> failwith "TODO: Ble_s unimplemented"
|
||||
| Bgt_s b -> failwith "TODO: Bgt_s unimplemented"
|
||||
| Bge_s b -> failwith "TODO: Bge_s unimplemented"
|
||||
| Beq i -> failwith "TODO: Beq unimplemented"
|
||||
| Blt i -> failwith "TODO: Blt unimplemented"
|
||||
| Ble i -> failwith "TODO: Ble unimplemented"
|
||||
| Bgt i -> failwith "TODO: Bgt unimplemented"
|
||||
| Bge i -> failwith "TODO: Bge unimplemented"
|
||||
| Bne_un_s b -> failwith "TODO: Bne_un_s unimplemented"
|
||||
| Bge_un_s b -> failwith "TODO: Bge_un_s unimplemented"
|
||||
| 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"
|
||||
| Bne_un i -> failwith "TODO: Bne_un unimplemented"
|
||||
| Bge_un i -> failwith "TODO: Bge_un unimplemented"
|
||||
| Bgt_un i -> failwith "TODO: Bgt_un unimplemented"
|
||||
| Ble_un i -> failwith "TODO: Ble_un unimplemented"
|
||||
| Blt_un i -> failwith "TODO: Blt_un unimplemented"
|
||||
| Ldloc_s b -> failwith "TODO: Ldloc_s unimplemented"
|
||||
| Ldloca_s b ->
|
||||
let threadState = state.ThreadState.[currentThread]
|
||||
|
||||
let state =
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack'
|
||||
(EvalStackValue.ManagedPointer (
|
||||
ManagedPointerSource.LocalVariable (
|
||||
currentThread,
|
||||
threadState.ActiveMethodState,
|
||||
uint16<uint8> b
|
||||
)
|
||||
))
|
||||
currentThread
|
||||
|> IlMachineState.advanceProgramCounter currentThread
|
||||
|
||||
state, WhatWeDid.Executed
|
||||
| Ldarga s -> failwith "TODO: Ldarga unimplemented"
|
||||
| Ldarg_s b -> failwith "TODO: Ldarg_s unimplemented"
|
||||
| Ldarga_s b -> failwith "TODO: Ldarga_s unimplemented"
|
||||
| Leave i -> failwith "TODO: Leave unimplemented"
|
||||
| Leave_s b -> failwith "TODO: Leave_s unimplemented"
|
||||
| Starg_s b -> failwith "TODO: Starg_s unimplemented"
|
||||
| Starg s -> failwith "TODO: Starg unimplemented"
|
||||
| Unaligned b -> failwith "TODO: Unaligned unimplemented"
|
||||
| Ldloc s -> failwith "TODO: Ldloc unimplemented"
|
||||
| Ldloca s -> failwith "TODO: Ldloca unimplemented"
|
||||
| Ldarg s -> failwith "TODO: Ldarg unimplemented"
|
411
WoofWare.PawPrint/UnaryMetadataIlOp.fs
Normal file
411
WoofWare.PawPrint/UnaryMetadataIlOp.fs
Normal file
@@ -0,0 +1,411 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System.Collections.Immutable
|
||||
open System.Reflection
|
||||
open Microsoft.Extensions.Logging
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal UnaryMetadataIlOp =
|
||||
let execute
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
|
||||
(op : UnaryMetadataTokenIlOp)
|
||||
(metadataToken : MetadataToken)
|
||||
(state : IlMachineState)
|
||||
(thread : ThreadId)
|
||||
: IlMachineState * WhatWeDid
|
||||
=
|
||||
match op with
|
||||
| Call ->
|
||||
let state, methodToCall =
|
||||
match metadataToken with
|
||||
| MetadataToken.MethodSpecification h ->
|
||||
let spec = (state.ActiveAssembly thread).MethodSpecs.[h]
|
||||
|
||||
match spec.Method with
|
||||
| MetadataToken.MethodDef token -> state, (state.ActiveAssembly thread).Methods.[token]
|
||||
| MetadataToken.MemberReference ref ->
|
||||
let state, _, method =
|
||||
IlMachineState.resolveMember loggerFactory (state.ActiveAssembly thread) ref state
|
||||
|
||||
match method with
|
||||
| Choice2Of2 _field -> failwith "tried to Call a field"
|
||||
| Choice1Of2 method -> state, method
|
||||
| k -> failwith $"Unrecognised kind: %O{k}"
|
||||
| MetadataToken.MemberReference h ->
|
||||
let state, _, method =
|
||||
IlMachineState.resolveMember loggerFactory (state.ActiveAssembly thread) h state
|
||||
|
||||
match method with
|
||||
| Choice2Of2 _field -> failwith "tried to Call a field"
|
||||
| Choice1Of2 method -> state, method
|
||||
|
||||
| MetadataToken.MethodDef defn ->
|
||||
let activeAssy = state.ActiveAssembly thread
|
||||
|
||||
match activeAssy.Methods.TryGetValue defn with
|
||||
| true, method -> state, method
|
||||
| false, _ -> failwith $"could not find method in {activeAssy.Name}"
|
||||
| k -> failwith $"Unrecognised kind: %O{k}"
|
||||
|
||||
match
|
||||
IlMachineState.loadClass
|
||||
loggerFactory
|
||||
(fst methodToCall.DeclaringType)
|
||||
(snd methodToCall.DeclaringType)
|
||||
thread
|
||||
state
|
||||
with
|
||||
| NothingToDo state ->
|
||||
state.WithThreadSwitchedToAssembly (snd methodToCall.DeclaringType) thread
|
||||
|> fst
|
||||
|> IlMachineState.callMethodInActiveAssembly loggerFactory thread methodToCall None
|
||||
| FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit
|
||||
|
||||
| Callvirt ->
|
||||
let method =
|
||||
match metadataToken with
|
||||
| MetadataToken.MethodDef defn ->
|
||||
let activeAssy = state.ActiveAssembly thread
|
||||
|
||||
match activeAssy.Methods.TryGetValue defn with
|
||||
| true, method -> method
|
||||
| false, _ -> failwith $"could not find method in {activeAssy.Name}"
|
||||
| _ -> failwith $"TODO (Callvirt): %O{metadataToken}"
|
||||
|
||||
let currentObj =
|
||||
match IlMachineState.peekEvalStack thread state with
|
||||
| None -> failwith "nothing on stack when Callvirt called"
|
||||
| Some obj -> obj
|
||||
|
||||
let methodToCall =
|
||||
match currentObj with
|
||||
| EvalStackValue.ManagedPointer src ->
|
||||
match src with
|
||||
| ManagedPointerSource.Null -> failwith "TODO: raise NullReferenceException"
|
||||
| ManagedPointerSource.LocalVariable _ -> failwith "TODO (Callvirt): LocalVariable"
|
||||
| ManagedPointerSource.Heap addr ->
|
||||
match state.ManagedHeap.NonArrayObjects.TryGetValue addr with
|
||||
| false, _ -> failwith "TODO (Callvirt): address"
|
||||
| true, v ->
|
||||
{ new TypeInfoEval<_> with
|
||||
member _.Eval ty =
|
||||
let matchingMethods =
|
||||
ty.Methods
|
||||
|> List.filter (fun mi ->
|
||||
mi.Name = method.Name && mi.Signature = method.Signature && not mi.IsStatic
|
||||
)
|
||||
|
||||
match matchingMethods with
|
||||
| [] ->
|
||||
failwith
|
||||
"TODO: walk up the class hierarchy; eventually throw MissingMethodException"
|
||||
| [ m ] -> m
|
||||
| _ -> failwith $"multiple matching methods for {method.Name}"
|
||||
}
|
||||
|> v.Type.Apply
|
||||
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
|
||||
| _ -> failwith $"TODO (Callvirt): can't identify type of {currentObj}"
|
||||
|
||||
state.WithThreadSwitchedToAssembly (snd methodToCall.DeclaringType) thread
|
||||
|> fst
|
||||
|> IlMachineState.callMethodInActiveAssembly loggerFactory thread methodToCall None
|
||||
| Castclass -> failwith "TODO: Castclass unimplemented"
|
||||
| Newobj ->
|
||||
let state, assy, ctor =
|
||||
match metadataToken with
|
||||
| MethodDef md ->
|
||||
let activeAssy = state.ActiveAssembly thread
|
||||
let method = activeAssy.Methods.[md]
|
||||
state, activeAssy.Name, method
|
||||
| MemberReference mr ->
|
||||
let state, name, method =
|
||||
IlMachineState.resolveMember loggerFactory (state.ActiveAssembly thread) mr state
|
||||
|
||||
match method with
|
||||
| Choice1Of2 mr -> state, name, mr
|
||||
| Choice2Of2 _field -> failwith "unexpectedly NewObj found a constructor which is a field"
|
||||
| x -> failwith $"Unexpected metadata token for constructor: %O{x}"
|
||||
|
||||
let ctorType, ctorAssembly = ctor.DeclaringType
|
||||
let ctorAssembly = state.LoadedAssembly ctorAssembly |> Option.get
|
||||
let ctorType = ctorAssembly.TypeDefs.[ctorType]
|
||||
|
||||
let fields =
|
||||
ctorType.Fields
|
||||
|> List.map (fun field ->
|
||||
// TODO: I guess the type itself can have generics, which should be passed in as this array?
|
||||
let zeroedAllocation = CliType.zeroOf ImmutableArray.Empty field.Signature
|
||||
field.Name, zeroedAllocation
|
||||
)
|
||||
|
||||
let allocatedAddr, state =
|
||||
IlMachineState.allocateManagedObject ctorType fields state
|
||||
|
||||
let state =
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack'
|
||||
(EvalStackValue.ManagedPointer (ManagedPointerSource.Heap allocatedAddr))
|
||||
thread
|
||||
|
||||
let state, whatWeDid =
|
||||
state.WithThreadSwitchedToAssembly assy thread
|
||||
|> fst
|
||||
|> IlMachineState.callMethodInActiveAssembly loggerFactory thread ctor (Some allocatedAddr)
|
||||
|
||||
match whatWeDid with
|
||||
| SuspendedForClassInit -> failwith "unexpectedly suspended while initialising constructor"
|
||||
| BlockedOnClassInit threadBlockingUs ->
|
||||
failwith "TODO: Newobj blocked on class init synchronization unimplemented"
|
||||
| Executed -> ()
|
||||
|
||||
state, WhatWeDid.Executed
|
||||
| Newarr ->
|
||||
let currentState = state.ThreadState.[thread]
|
||||
let popped, newMethodState = MethodState.popFromStack currentState.MethodState
|
||||
|
||||
let currentState =
|
||||
{ currentState with
|
||||
MethodStates = currentState.MethodStates.SetItem (currentState.ActiveMethodState, newMethodState)
|
||||
}
|
||||
|
||||
let len =
|
||||
match popped with
|
||||
| EvalStackValue.Int32 v -> v
|
||||
| popped -> failwith $"unexpectedly popped value %O{popped} to serve as array len"
|
||||
|
||||
let elementType =
|
||||
match metadataToken with
|
||||
| MetadataToken.TypeDefinition defn ->
|
||||
state.LoadedAssembly currentState.ActiveAssembly
|
||||
|> Option.get
|
||||
|> fun assy -> assy.TypeDefs.[defn]
|
||||
| x -> failwith $"TODO: Newarr element type resolution unimplemented for {x}"
|
||||
|
||||
let baseType =
|
||||
elementType.BaseType
|
||||
|> TypeInfo.resolveBaseType
|
||||
(fun (x : DumpedAssembly) -> x.Name)
|
||||
(fun x y -> x.TypeDefs.[y])
|
||||
baseClassTypes
|
||||
elementType.Assembly
|
||||
|
||||
let zeroOfType =
|
||||
match baseType with
|
||||
| ResolvedBaseType.Object ->
|
||||
// initialise with null references
|
||||
fun () -> CliType.ObjectRef None
|
||||
| ResolvedBaseType.Enum -> failwith "TODO: Newarr Enum array initialization unimplemented"
|
||||
| ResolvedBaseType.ValueType -> failwith "TODO: Newarr ValueType array initialization unimplemented"
|
||||
| ResolvedBaseType.Delegate -> failwith "TODO: Newarr Delegate array initialization unimplemented"
|
||||
|
||||
let alloc, state = IlMachineState.allocateArray zeroOfType len state
|
||||
|
||||
let state =
|
||||
{ state with
|
||||
ThreadState = state.ThreadState |> Map.add thread currentState
|
||||
}
|
||||
|> IlMachineState.pushToEvalStack (CliType.ObjectRef (Some alloc)) thread
|
||||
|> IlMachineState.advanceProgramCounter thread
|
||||
|
||||
state, WhatWeDid.Executed
|
||||
| Box -> failwith "TODO: Box unimplemented"
|
||||
| Ldelema -> failwith "TODO: Ldelema unimplemented"
|
||||
| Isinst -> failwith "TODO: Isinst unimplemented"
|
||||
| Stfld -> failwith "TODO: Stfld unimplemented"
|
||||
| Stsfld ->
|
||||
let fieldHandle =
|
||||
match metadataToken with
|
||||
| MetadataToken.FieldDefinition f -> f
|
||||
| t -> failwith $"Unexpectedly asked to store to a non-field: {t}"
|
||||
|
||||
let activeAssy = state.ActiveAssembly thread
|
||||
|
||||
match activeAssy.Fields.TryGetValue fieldHandle with
|
||||
| false, _ -> failwith "TODO: Stsfld - throw MissingFieldException"
|
||||
| true, field ->
|
||||
|
||||
do
|
||||
let logger = loggerFactory.CreateLogger "Stsfld"
|
||||
let declaring = state.ActiveAssembly(thread).TypeDefs.[field.DeclaringType]
|
||||
|
||||
logger.LogInformation (
|
||||
"Storing in static field {FieldAssembly}.{FieldDeclaringType}.{FieldName} (type {FieldType})",
|
||||
declaring.Assembly.Name,
|
||||
declaring.Name,
|
||||
field.Name,
|
||||
field.Signature
|
||||
)
|
||||
|
||||
match IlMachineState.loadClass loggerFactory field.DeclaringType activeAssy.Name thread state with
|
||||
| FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit
|
||||
| NothingToDo state ->
|
||||
|
||||
let popped, state = IlMachineState.popEvalStack thread state
|
||||
|
||||
let toStore =
|
||||
EvalStackValue.toCliTypeCoerced (CliType.zeroOf ImmutableArray.Empty field.Signature) popped
|
||||
|
||||
let state =
|
||||
{ state with
|
||||
Statics = state.Statics.SetItem ((field.DeclaringType, activeAssy.Name), toStore)
|
||||
}
|
||||
|> IlMachineState.advanceProgramCounter thread
|
||||
|
||||
state, WhatWeDid.Executed
|
||||
|
||||
| Ldfld ->
|
||||
let state, assyName, field =
|
||||
match metadataToken with
|
||||
| MetadataToken.FieldDefinition f ->
|
||||
state, (state.ActiveAssembly thread).Name, state.ActiveAssembly(thread).Fields.[f]
|
||||
| MetadataToken.MemberReference mr ->
|
||||
let state, assyName, field =
|
||||
IlMachineState.resolveMember loggerFactory (state.ActiveAssembly thread) mr state
|
||||
|
||||
match field with
|
||||
| Choice1Of2 _method -> failwith "member reference was unexpectedly a method"
|
||||
| Choice2Of2 field -> state, assyName, field
|
||||
| t -> failwith $"Unexpectedly asked to load from a non-field: {t}"
|
||||
|
||||
do
|
||||
let logger = loggerFactory.CreateLogger "Ldfld"
|
||||
let declaring = state.ActiveAssembly(thread).TypeDefs.[field.DeclaringType]
|
||||
|
||||
logger.LogInformation (
|
||||
"Storing in object field {FieldAssembly}.{FieldDeclaringType}.{FieldName} (type {FieldType})",
|
||||
declaring.Assembly.Name,
|
||||
declaring.Name,
|
||||
field.Name,
|
||||
field.Signature
|
||||
)
|
||||
|
||||
let currentObj, state = IlMachineState.popEvalStack thread state
|
||||
|
||||
if field.Attributes.HasFlag FieldAttributes.Static then
|
||||
let staticField = state.Statics.[field.DeclaringType, assyName]
|
||||
let state = state |> IlMachineState.pushToEvalStack staticField thread
|
||||
state, WhatWeDid.Executed
|
||||
else
|
||||
|
||||
let state =
|
||||
match currentObj with
|
||||
| EvalStackValue.Int32 i -> failwith "todo: int32"
|
||||
| EvalStackValue.Int64 int64 -> failwith "todo: int64"
|
||||
| EvalStackValue.NativeInt nativeIntSource -> failwith $"todo: nativeint {nativeIntSource}"
|
||||
| EvalStackValue.Float f -> failwith "todo: float"
|
||||
| EvalStackValue.ManagedPointer managedPointerSource ->
|
||||
match managedPointerSource with
|
||||
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
|
||||
let currentValue =
|
||||
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables
|
||||
.[int<uint16> whichVar]
|
||||
|
||||
failwith $"todo: local variable {currentValue} {field}"
|
||||
| ManagedPointerSource.Heap managedHeapAddress ->
|
||||
match state.ManagedHeap.NonArrayObjects.TryGetValue managedHeapAddress with
|
||||
| false, _ -> failwith $"todo: array {managedHeapAddress}"
|
||||
| true, v -> IlMachineState.pushToEvalStack v.Fields.[field.Name] thread state
|
||||
| ManagedPointerSource.Null -> failwith "TODO: raise NullReferenceException"
|
||||
| EvalStackValue.ObjectRef managedHeapAddress -> failwith $"todo: {managedHeapAddress}"
|
||||
| EvalStackValue.UserDefinedValueType -> failwith "todo"
|
||||
|
||||
state
|
||||
|> IlMachineState.advanceProgramCounter thread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|
||||
| Ldflda -> failwith "TODO: Ldflda unimplemented"
|
||||
| Ldsfld ->
|
||||
let fieldHandle =
|
||||
match metadataToken with
|
||||
| MetadataToken.FieldDefinition f -> f
|
||||
| t -> failwith $"Unexpectedly asked to load from a non-field: {t}"
|
||||
|
||||
let activeAssy = state.ActiveAssembly thread
|
||||
|
||||
match activeAssy.Fields.TryGetValue fieldHandle with
|
||||
| false, _ -> failwith "TODO: Ldsfld - throw MissingFieldException"
|
||||
| true, field ->
|
||||
|
||||
do
|
||||
let logger = loggerFactory.CreateLogger "Ldsfld"
|
||||
let declaring = state.ActiveAssembly(thread).TypeDefs.[field.DeclaringType]
|
||||
|
||||
logger.LogInformation (
|
||||
"Loading from static field {FieldAssembly}.{FieldDeclaringType}.{FieldName} (type {FieldType})",
|
||||
declaring.Assembly.Name,
|
||||
declaring.Name,
|
||||
field.Name,
|
||||
field.Signature
|
||||
)
|
||||
|
||||
match IlMachineState.loadClass loggerFactory field.DeclaringType activeAssy.Name thread state with
|
||||
| FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit
|
||||
| NothingToDo state ->
|
||||
|
||||
let fieldValue, state =
|
||||
match state.Statics.TryGetValue ((field.DeclaringType, activeAssy.Name)) with
|
||||
| false, _ ->
|
||||
// TODO: generics
|
||||
let newVal = CliType.zeroOf ImmutableArray.Empty field.Signature
|
||||
|
||||
newVal,
|
||||
{ state with
|
||||
Statics = state.Statics.SetItem ((field.DeclaringType, activeAssy.Name), newVal)
|
||||
}
|
||||
| true, v -> v, state
|
||||
|
||||
let state =
|
||||
IlMachineState.pushToEvalStack fieldValue thread state
|
||||
|> IlMachineState.advanceProgramCounter thread
|
||||
|
||||
state, WhatWeDid.Executed
|
||||
|
||||
| Unbox_Any -> failwith "TODO: Unbox_Any unimplemented"
|
||||
| Stelem -> failwith "TODO: Stelem unimplemented"
|
||||
| Ldelem -> failwith "TODO: Ldelem unimplemented"
|
||||
| Initobj -> failwith "TODO: Initobj unimplemented"
|
||||
| Ldsflda ->
|
||||
// TODO: check whether we should throw FieldAccessException
|
||||
let fieldHandle =
|
||||
match metadataToken with
|
||||
| MetadataToken.FieldDefinition f -> f
|
||||
| t -> failwith $"Unexpectedly asked to load a non-field: {t}"
|
||||
|
||||
let activeAssy = state.ActiveAssembly thread
|
||||
|
||||
match activeAssy.Fields.TryGetValue fieldHandle with
|
||||
| false, _ -> failwith "TODO: Ldsflda - throw MissingFieldException"
|
||||
| true, field ->
|
||||
match IlMachineState.loadClass loggerFactory field.DeclaringType activeAssy.Name thread state with
|
||||
| FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit
|
||||
| NothingToDo state ->
|
||||
|
||||
if TypeDefn.isManaged field.Signature then
|
||||
match state.Statics.TryGetValue ((field.DeclaringType, activeAssy.Name)) with
|
||||
| true, v ->
|
||||
IlMachineState.pushToEvalStack v thread state
|
||||
|> IlMachineState.advanceProgramCounter thread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
| false, _ ->
|
||||
let allocation, state =
|
||||
state |> (failwith "TODO: Ldsflda static field allocation unimplemented")
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack (CliType.ObjectRef (Some allocation)) thread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
else
|
||||
failwith "TODO: Ldsflda - push unmanaged pointer"
|
||||
| Ldftn -> failwith "TODO: Ldftn unimplemented"
|
||||
| Stobj -> failwith "TODO: Stobj unimplemented"
|
||||
| Constrained -> failwith "TODO: Constrained unimplemented"
|
||||
| Ldtoken -> failwith "TODO: Ldtoken unimplemented"
|
||||
| Cpobj -> failwith "TODO: Cpobj unimplemented"
|
||||
| Ldobj -> failwith "TODO: Ldobj unimplemented"
|
||||
| Sizeof -> failwith "TODO: Sizeof unimplemented"
|
||||
| Calli -> failwith "TODO: Calli unimplemented"
|
||||
| Unbox -> failwith "TODO: Unbox unimplemented"
|
||||
| Ldvirtftn -> failwith "TODO: Ldvirtftn unimplemented"
|
||||
| Mkrefany -> failwith "TODO: Mkrefany unimplemented"
|
||||
| Refanyval -> failwith "TODO: Refanyval unimplemented"
|
||||
| Jmp -> failwith "TODO: Jmp unimplemented"
|
72
WoofWare.PawPrint/UnaryStringTokenIlOp.fs
Normal file
72
WoofWare.PawPrint/UnaryStringTokenIlOp.fs
Normal file
@@ -0,0 +1,72 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System.Reflection
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module internal UnaryStringTokenIlOp =
|
||||
let execute
|
||||
(baseClassTypes : BaseClassTypes<'a>)
|
||||
(op : UnaryStringTokenIlOp)
|
||||
(sh : StringToken)
|
||||
(state : IlMachineState)
|
||||
(thread : ThreadId)
|
||||
: IlMachineState * WhatWeDid
|
||||
=
|
||||
match op with
|
||||
| UnaryStringTokenIlOp.Ldstr ->
|
||||
let addressToLoad, state =
|
||||
match state.InternedStrings.TryGetValue sh with
|
||||
| false, _ ->
|
||||
let stringToAllocate = state.ActiveAssembly(thread).Strings sh
|
||||
|
||||
let dataAddr, state =
|
||||
IlMachineState.allocateStringData stringToAllocate.Length state
|
||||
|
||||
let state = state |> IlMachineState.setStringData dataAddr stringToAllocate
|
||||
|
||||
let stringInstanceFields =
|
||||
baseClassTypes.String.Fields
|
||||
|> List.choose (fun field ->
|
||||
if int (field.Attributes &&& FieldAttributes.Static) = 0 then
|
||||
Some (field.Name, field.Signature)
|
||||
else
|
||||
None
|
||||
)
|
||||
|> List.sortBy fst
|
||||
// Assert that the string type has the fields we expect
|
||||
if
|
||||
stringInstanceFields
|
||||
<> [
|
||||
("_firstChar", TypeDefn.PrimitiveType PrimitiveType.Char)
|
||||
("_stringLength", TypeDefn.PrimitiveType PrimitiveType.Int32)
|
||||
]
|
||||
then
|
||||
failwith
|
||||
$"unexpectedly don't know how to initialise a string: got fields %O{stringInstanceFields}"
|
||||
|
||||
let fields =
|
||||
[
|
||||
"_firstChar", CliType.OfChar state.ManagedHeap.StringArrayData.[dataAddr]
|
||||
"_stringLength", CliType.Numeric (CliNumericType.Int32 stringToAllocate.Length)
|
||||
]
|
||||
|
||||
let addr, state =
|
||||
IlMachineState.allocateManagedObject
|
||||
(baseClassTypes.String
|
||||
|> TypeInfo.mapGeneric (fun _ -> failwith<unit> "string is not generic"))
|
||||
fields
|
||||
state
|
||||
|
||||
addr,
|
||||
{ state with
|
||||
InternedStrings = state.InternedStrings.Add (sh, addr)
|
||||
}
|
||||
| true, v -> v, state
|
||||
|
||||
let state =
|
||||
IlMachineState.pushToEvalStack (CliType.ObjectRef (Some addressToLoad)) thread state
|
||||
|
||||
state
|
||||
|> IlMachineState.advanceProgramCounter thread
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
@@ -26,6 +26,13 @@
|
||||
<Compile Include="ManagedHeap.fs" />
|
||||
<Compile Include="TypeInitialisation.fs" />
|
||||
<Compile Include="EvalStack.fs" />
|
||||
<Compile Include="MethodState.fs" />
|
||||
<Compile Include="ThreadState.fs" />
|
||||
<Compile Include="IlMachineState.fs" />
|
||||
<Compile Include="NullaryIlOp.fs" />
|
||||
<Compile Include="UnaryMetadataIlOp.fs" />
|
||||
<Compile Include="UnaryStringTokenIlOp.fs" />
|
||||
<Compile Include="UnaryConstIlOp.fs" />
|
||||
<Compile Include="AbstractMachine.fs" />
|
||||
<Compile Include="Program.fs" />
|
||||
</ItemGroup>
|
||||
|
Reference in New Issue
Block a user