mirror of
https://github.com/Smaug123/WoofWare.PawPrint
synced 2025-10-08 15:38:41 +00:00
Implement custom delegate calling (#60)
This commit is contained in:
@@ -73,6 +73,22 @@ module TestCases =
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 1) ]
|
||||
}
|
||||
{
|
||||
FileName = "CustomDelegate.cs"
|
||||
ExpectedReturnCode = 8
|
||||
NativeImpls = MockEnv.make ()
|
||||
LocalVariablesOfMain =
|
||||
[
|
||||
// filter
|
||||
CliType.ObjectRef (Some (ManagedHeapAddress 2))
|
||||
// result
|
||||
CliType.OfBool true
|
||||
// result, cloned for "if(result)" check
|
||||
CliType.OfBool true
|
||||
// ret
|
||||
CliType.Numeric (CliNumericType.Int32 8)
|
||||
]
|
||||
}
|
||||
{
|
||||
FileName = "ArgumentOrdering.cs"
|
||||
ExpectedReturnCode = 42
|
||||
|
@@ -28,6 +28,7 @@
|
||||
<EmbeddedResource Include="sources\Threads.cs" />
|
||||
<EmbeddedResource Include="sources\ResizeArray.cs" />
|
||||
<EmbeddedResource Include="sources\ArgumentOrdering.cs" />
|
||||
<EmbeddedResource Include="sources\CustomDelegate.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
27
WoofWare.PawPrint.Test/sources/CustomDelegate.cs
Normal file
27
WoofWare.PawPrint.Test/sources/CustomDelegate.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
public delegate bool MyFilter(object item, object criteria);
|
||||
|
||||
public class DelegateDemo
|
||||
{
|
||||
// This static field initialization will generate the exact IL pattern:
|
||||
public static readonly MyFilter FilterField = FilterImpl;
|
||||
|
||||
// The static method that the delegate points to
|
||||
private static bool FilterImpl(object item, object criteria)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int Main(string[] argv)
|
||||
{
|
||||
// Force static constructor to run
|
||||
var filter = FilterField;
|
||||
|
||||
// Test the delegate
|
||||
bool result = filter("test item", "criterion");
|
||||
if (result)
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
namespace WoofWare.PawPrint
|
||||
|
||||
open System.Collections.Immutable
|
||||
open Microsoft.Extensions.Logging
|
||||
open Microsoft.FSharp.Core
|
||||
open WoofWare.PawPrint.ExternImplementations
|
||||
@@ -21,7 +22,6 @@ module AbstractMachine =
|
||||
|
||||
match instruction.ExecutingMethod.Instructions with
|
||||
| None ->
|
||||
// TODO: this could be a delegate, like System.Func.
|
||||
let targetAssy =
|
||||
state.LoadedAssembly instruction.ExecutingMethod.DeclaringType.Assembly
|
||||
|> Option.get
|
||||
@@ -36,17 +36,70 @@ module AbstractMachine =
|
||||
targetAssy.Name
|
||||
targetType.BaseType
|
||||
|
||||
match baseType, instruction.ReturnState with
|
||||
| ResolvedBaseType.Delegate,
|
||||
Some {
|
||||
WasConstructingObj = Some _
|
||||
} ->
|
||||
IlMachineState.executeDelegateConstructor instruction state
|
||||
// can't advance the program counter here - there's no IL instructions executing!
|
||||
|> IlMachineState.returnStackFrame loggerFactory baseClassTypes thread
|
||||
|> Option.get
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
match baseType with
|
||||
| ResolvedBaseType.Delegate ->
|
||||
match instruction.ReturnState with
|
||||
| None -> failwith "How come we don't have a return point from a delegate?!"
|
||||
| Some {
|
||||
WasConstructingObj = Some _
|
||||
} ->
|
||||
IlMachineState.executeDelegateConstructor instruction state
|
||||
// can't advance the program counter here - there's no IL instructions executing!
|
||||
|> IlMachineState.returnStackFrame loggerFactory baseClassTypes thread
|
||||
|> Option.get
|
||||
|> Tuple.withRight WhatWeDid.Executed
|
||||
|> ExecutionResult.Stepped
|
||||
| Some {
|
||||
WasConstructingObj = None
|
||||
} ->
|
||||
// We've been instructed to run a delegate.
|
||||
let delegateToRunAddr =
|
||||
match instruction.Arguments.[0] with
|
||||
| CliType.ObjectRef (Some addr) -> addr
|
||||
| _ -> failwith "expected a managed object ref to delegate"
|
||||
|
||||
let delegateToRun = state.ManagedHeap.NonArrayObjects.[delegateToRunAddr]
|
||||
|
||||
if delegateToRun.Fields.["_target"] <> CliType.ObjectRef None then
|
||||
failwith "TODO: delegate target wasn't None"
|
||||
|
||||
let methodPtr =
|
||||
match delegateToRun.Fields.["_methodPtr"] with
|
||||
| CliType.Numeric (CliNumericType.ProvenanceTrackedNativeInt64 mi) -> mi
|
||||
| _ -> failwith "unexpectedly not a method pointer in delegate invocation"
|
||||
|
||||
let typeGenerics =
|
||||
instruction.ExecutingMethod.DeclaringType.Generics |> ImmutableArray.CreateRange
|
||||
|
||||
let methodGenerics = instruction.ExecutingMethod.Generics
|
||||
|
||||
let methodPtr =
|
||||
methodPtr |> MethodInfo.mapTypeGenerics (fun i _ -> typeGenerics.[i])
|
||||
|
||||
// When we return, we need to go back up the stack
|
||||
match state |> IlMachineState.returnStackFrame loggerFactory baseClassTypes thread with
|
||||
| None -> failwith "unexpectedly nowhere to return from delegate"
|
||||
| Some state ->
|
||||
|
||||
// Push args
|
||||
let state =
|
||||
(state, instruction.Arguments)
|
||||
||> Seq.fold (fun state arg -> IlMachineState.pushToEvalStack arg thread state)
|
||||
|
||||
// Don't advance the program counter again on return; that was already done by the Callvirt that
|
||||
// caused this delegate to be invoked.
|
||||
let state, result =
|
||||
state
|
||||
|> IlMachineState.callMethodInActiveAssembly
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
thread
|
||||
false
|
||||
(Some methodGenerics)
|
||||
methodPtr
|
||||
None
|
||||
|
||||
ExecutionResult.Stepped (state, result)
|
||||
| _ ->
|
||||
|
||||
let outcome =
|
||||
|
@@ -455,6 +455,7 @@ module IlMachineState =
|
||||
(wasInitialising : RuntimeConcreteType option)
|
||||
(wasConstructing : ManagedHeapAddress option)
|
||||
(wasClassConstructor : bool)
|
||||
(advanceProgramCounterOfCaller : bool)
|
||||
(methodGenerics : ImmutableArray<TypeDefn> option)
|
||||
(methodToCall : WoofWare.PawPrint.MethodInfo<TypeDefn, WoofWare.PawPrint.GenericParameter>)
|
||||
(thread : ThreadId)
|
||||
@@ -464,6 +465,7 @@ module IlMachineState =
|
||||
=
|
||||
let activeAssy = state.ActiveAssembly thread
|
||||
|
||||
// Check for intrinsics first
|
||||
let isIntrinsic =
|
||||
methodToCall.IsJITIntrinsic
|
||||
(fun handle ->
|
||||
@@ -473,13 +475,12 @@ module IlMachineState =
|
||||
)
|
||||
activeAssy.Methods
|
||||
|
||||
let handleIntrinsic =
|
||||
match
|
||||
if isIntrinsic then
|
||||
callIntrinsic corelib methodToCall state
|
||||
else
|
||||
None
|
||||
|
||||
match handleIntrinsic with
|
||||
with
|
||||
| Some result -> result
|
||||
| None ->
|
||||
|
||||
@@ -488,6 +489,7 @@ module IlMachineState =
|
||||
| [] -> None
|
||||
| x -> Some (ImmutableArray.CreateRange x)
|
||||
|
||||
// Get zero values for all parameters
|
||||
let state, argZeroObjects =
|
||||
((state, []), methodToCall.Signature.ParameterTypes)
|
||||
||> List.fold (fun (state, zeros) ty ->
|
||||
@@ -512,122 +514,108 @@ module IlMachineState =
|
||||
methodToCall
|
||||
|> MethodInfo.mapMethodGenerics (fun _ param -> methodGenerics.Value.[param.SequenceNumber])
|
||||
|
||||
let state, newFrame, oldFrame =
|
||||
// Helper to pop and coerce a single argument
|
||||
let popAndCoerceArg zeroType methodState =
|
||||
let value, newState = MethodState.popFromStack methodState
|
||||
EvalStackValue.toCliTypeCoerced zeroType value, newState
|
||||
|
||||
// Collect arguments based on calling convention
|
||||
let args, afterPop =
|
||||
if methodToCall.IsStatic then
|
||||
let args = ImmutableArray.CreateBuilder methodToCall.Parameters.Length
|
||||
let mutable afterPop = activeMethodState
|
||||
// Static method: pop args in reverse order
|
||||
let args = ImmutableArray.CreateBuilder (methodToCall.Parameters.Length)
|
||||
let mutable currentState = activeMethodState
|
||||
|
||||
for i = 0 to methodToCall.Parameters.Length - 1 do
|
||||
let poppedArg, afterPop' = afterPop |> MethodState.popFromStack
|
||||
|
||||
let zeroArg = argZeroObjects.[i]
|
||||
|
||||
let poppedArg = EvalStackValue.toCliTypeCoerced zeroArg poppedArg
|
||||
afterPop <- afterPop'
|
||||
args.Add poppedArg
|
||||
for i = methodToCall.Parameters.Length - 1 downto 0 do
|
||||
let arg, newState = popAndCoerceArg argZeroObjects.[i] currentState
|
||||
args.Add arg
|
||||
currentState <- newState
|
||||
|
||||
args.Reverse ()
|
||||
|
||||
let rec newFrame (state : IlMachineState) =
|
||||
let meth =
|
||||
MethodState.Empty
|
||||
corelib
|
||||
state._LoadedAssemblies
|
||||
(state.ActiveAssembly thread)
|
||||
methodToCall
|
||||
methodGenerics
|
||||
(args.ToImmutable ())
|
||||
(Some
|
||||
{
|
||||
JumpTo = threadState.ActiveMethodState
|
||||
WasInitialisingType = wasInitialising
|
||||
WasConstructingObj = wasConstructing
|
||||
})
|
||||
|
||||
match meth with
|
||||
| Ok r -> state, r
|
||||
| Error toLoad ->
|
||||
(state, toLoad)
|
||||
||> List.fold (fun state (toLoad : WoofWare.PawPrint.AssemblyReference) ->
|
||||
let state, _, _ =
|
||||
loadAssembly
|
||||
loggerFactory
|
||||
(state.LoadedAssembly methodToCall.DeclaringType.Assembly |> Option.get)
|
||||
(fst toLoad.Handle)
|
||||
state
|
||||
|
||||
state
|
||||
)
|
||||
|> newFrame
|
||||
|
||||
let state, newFrame = newFrame state
|
||||
|
||||
let oldFrame =
|
||||
if wasClassConstructor then
|
||||
afterPop
|
||||
else
|
||||
afterPop |> MethodState.advanceProgramCounter
|
||||
|
||||
state, newFrame, oldFrame
|
||||
args.ToImmutable (), currentState
|
||||
else
|
||||
let args = ImmutableArray.CreateBuilder (methodToCall.Parameters.Length + 1)
|
||||
// Instance method: handle `this` pointer
|
||||
let argCount = methodToCall.Parameters.Length
|
||||
let args = ImmutableArray.CreateBuilder (argCount + 1)
|
||||
let mutable currentState = activeMethodState
|
||||
|
||||
let thisPointer, afterPop = activeMethodState |> MethodState.popFromStack
|
||||
let mutable afterPop = afterPop
|
||||
match wasConstructing with
|
||||
| Some _ ->
|
||||
// Constructor: `this` is on top of stack, by our own odd little calling convention
|
||||
// where Newobj puts the object pointer on top
|
||||
let thisArg, newState =
|
||||
popAndCoerceArg (CliType.RuntimePointer (CliRuntimePointer.Unmanaged ())) currentState
|
||||
|
||||
for i = 1 to methodToCall.Parameters.Length do
|
||||
let poppedArg, afterPop' = afterPop |> MethodState.popFromStack
|
||||
let zeroArg = argZeroObjects.[i - 1]
|
||||
args.Add thisArg
|
||||
currentState <- newState
|
||||
|
||||
let poppedArg = EvalStackValue.toCliTypeCoerced zeroArg poppedArg
|
||||
afterPop <- afterPop'
|
||||
args.Add poppedArg
|
||||
// Pop remaining args in reverse
|
||||
for i = argCount - 1 downto 0 do
|
||||
let arg, newState = popAndCoerceArg argZeroObjects.[i] currentState
|
||||
args.Add (arg)
|
||||
currentState <- newState
|
||||
|
||||
// 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 ()))
|
||||
thisPointer
|
||||
)
|
||||
args.ToImmutable (), currentState
|
||||
| None ->
|
||||
// Regular instance method: args then `this`
|
||||
for i = argCount - 1 downto 0 do
|
||||
let arg, newState = popAndCoerceArg argZeroObjects.[i] currentState
|
||||
args.Add (arg)
|
||||
currentState <- newState
|
||||
|
||||
args.Reverse ()
|
||||
let thisArg, newState =
|
||||
popAndCoerceArg (CliType.RuntimePointer (CliRuntimePointer.Unmanaged ())) currentState
|
||||
|
||||
let rec newFrame (state : IlMachineState) =
|
||||
let meth =
|
||||
MethodState.Empty
|
||||
corelib
|
||||
state._LoadedAssemblies
|
||||
(state.ActiveAssembly thread)
|
||||
methodToCall
|
||||
methodGenerics
|
||||
(args.ToImmutable ())
|
||||
(Some
|
||||
{
|
||||
JumpTo = threadState.ActiveMethodState
|
||||
WasInitialisingType = wasInitialising
|
||||
WasConstructingObj = wasConstructing
|
||||
})
|
||||
args.Add thisArg
|
||||
currentState <- newState
|
||||
|
||||
match meth with
|
||||
| Ok r -> state, r
|
||||
| Error toLoad ->
|
||||
(state, toLoad)
|
||||
||> List.fold (fun state (toLoad : WoofWare.PawPrint.AssemblyReference) ->
|
||||
let state, _, _ =
|
||||
loadAssembly
|
||||
loggerFactory
|
||||
(state.LoadedAssembly methodToCall.DeclaringType.Assembly |> Option.get)
|
||||
(fst toLoad.Handle)
|
||||
state
|
||||
args.Reverse ()
|
||||
args.ToImmutable (), currentState
|
||||
|
||||
state
|
||||
)
|
||||
|> newFrame
|
||||
// Helper to create new frame with assembly loading
|
||||
let rec createNewFrame state =
|
||||
let returnInfo =
|
||||
Some
|
||||
{
|
||||
JumpTo = threadState.ActiveMethodState
|
||||
WasInitialisingType = wasInitialising
|
||||
WasConstructingObj = wasConstructing
|
||||
}
|
||||
|
||||
let state, newFrame = newFrame state
|
||||
let oldFrame = afterPop |> MethodState.advanceProgramCounter
|
||||
state, newFrame, oldFrame
|
||||
match
|
||||
MethodState.Empty
|
||||
corelib
|
||||
state._LoadedAssemblies
|
||||
(state.ActiveAssembly thread)
|
||||
methodToCall
|
||||
methodGenerics
|
||||
args
|
||||
returnInfo
|
||||
with
|
||||
| Ok frame -> state, frame
|
||||
| Error toLoad ->
|
||||
let state' =
|
||||
(state, toLoad)
|
||||
||> List.fold (fun s (asmRef : WoofWare.PawPrint.AssemblyReference) ->
|
||||
let s, _, _ =
|
||||
loadAssembly
|
||||
loggerFactory
|
||||
(state.LoadedAssembly methodToCall.DeclaringType.Assembly |> Option.get)
|
||||
(fst asmRef.Handle)
|
||||
s
|
||||
|
||||
s
|
||||
)
|
||||
|
||||
createNewFrame state'
|
||||
|
||||
let state, newFrame = createNewFrame state
|
||||
|
||||
let oldFrame =
|
||||
if wasClassConstructor || not advanceProgramCounterOfCaller then
|
||||
afterPop
|
||||
else
|
||||
afterPop |> MethodState.advanceProgramCounter
|
||||
|
||||
let newThreadState =
|
||||
{ threadState with
|
||||
@@ -766,6 +754,7 @@ module IlMachineState =
|
||||
(Some ty)
|
||||
None
|
||||
true
|
||||
true
|
||||
// constructor is surely not generic
|
||||
None
|
||||
cctorMethod
|
||||
@@ -804,10 +793,15 @@ module IlMachineState =
|
||||
else
|
||||
state, WhatWeDid.BlockedOnClassInit threadId
|
||||
|
||||
/// It may be useful to *not* advance the program counter of the caller, e.g. if you're using `callMethodInActiveAssembly`
|
||||
/// as a convenient way to move to a different method body rather than to genuinely perform a call.
|
||||
/// (Delegates do this, for example: we get a call to invoke the delegate, and then we implement the delegate as
|
||||
/// another call to its function pointer.)
|
||||
let callMethodInActiveAssembly
|
||||
(loggerFactory : ILoggerFactory)
|
||||
(corelib : BaseClassTypes<DumpedAssembly>)
|
||||
(thread : ThreadId)
|
||||
(advanceProgramCounterOfCaller : bool)
|
||||
(methodGenerics : TypeDefn ImmutableArray option)
|
||||
(methodToCall : WoofWare.PawPrint.MethodInfo<TypeDefn, WoofWare.PawPrint.GenericParameter>)
|
||||
(weAreConstructingObj : ManagedHeapAddress option)
|
||||
@@ -827,6 +821,7 @@ module IlMachineState =
|
||||
None
|
||||
weAreConstructingObj
|
||||
false
|
||||
advanceProgramCounterOfCaller
|
||||
methodGenerics
|
||||
methodToCall
|
||||
thread
|
||||
@@ -1175,12 +1170,12 @@ module IlMachineState =
|
||||
constructed.Type.BaseType
|
||||
|
||||
match resolvedBaseType with
|
||||
| ResolvedBaseType.Delegate
|
||||
| ResolvedBaseType.Object -> state |> pushToEvalStack (CliType.OfManagedObject constructing) currentThread
|
||||
| ResolvedBaseType.ValueType ->
|
||||
state
|
||||
|> pushToEvalStack (CliType.ValueType (Seq.toList constructed.Fields.Values)) currentThread
|
||||
| ResolvedBaseType.Enum -> failwith "TODO"
|
||||
| ResolvedBaseType.Delegate -> failwith "TODO"
|
||||
| None ->
|
||||
match threadStateAtEndOfMethod.MethodState.EvaluationStack.Values with
|
||||
| [] ->
|
||||
@@ -1241,9 +1236,9 @@ module IlMachineState =
|
||||
|
||||
let executeDelegateConstructor (instruction : MethodState) (state : IlMachineState) : IlMachineState =
|
||||
// We've been called with arguments already popped from the stack into local arguments.
|
||||
let constructing = instruction.Arguments.[2]
|
||||
let constructing = instruction.Arguments.[0]
|
||||
let methodPtr = instruction.Arguments.[1]
|
||||
let targetObj = instruction.Arguments.[0]
|
||||
let targetObj = instruction.Arguments.[2]
|
||||
|
||||
let targetObj =
|
||||
match targetObj with
|
||||
|
@@ -182,6 +182,8 @@ module Program =
|
||||
let state, _ =
|
||||
pumpToReturn loggerFactory logger baseClassTypes impls mainThread state
|
||||
|
||||
logger.LogInformation "Main method class now initialised"
|
||||
|
||||
// Now that BCL initialisation has taken place and the user-code classes are constructed,
|
||||
// overwrite the main thread completely.
|
||||
let methodState =
|
||||
|
@@ -76,6 +76,7 @@ module internal UnaryMetadataIlOp =
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
thread
|
||||
true
|
||||
methodGenerics
|
||||
methodToCall
|
||||
None
|
||||
@@ -138,7 +139,7 @@ module internal UnaryMetadataIlOp =
|
||||
|
||||
state.WithThreadSwitchedToAssembly method.DeclaringType.Assembly thread
|
||||
|> fst
|
||||
|> IlMachineState.callMethodInActiveAssembly loggerFactory baseClassTypes thread generics method None
|
||||
|> IlMachineState.callMethodInActiveAssembly loggerFactory baseClassTypes thread true generics method None
|
||||
|
||||
| Castclass -> failwith "TODO: Castclass unimplemented"
|
||||
| Newobj ->
|
||||
@@ -227,6 +228,7 @@ module internal UnaryMetadataIlOp =
|
||||
loggerFactory
|
||||
baseClassTypes
|
||||
thread
|
||||
true
|
||||
None
|
||||
ctor
|
||||
(Some allocatedAddr)
|
||||
@@ -880,11 +882,20 @@ module internal UnaryMetadataIlOp =
|
||||
failwith "TODO: Ldsflda - push unmanaged pointer"
|
||||
|
||||
| Ldftn ->
|
||||
let logger = loggerFactory.CreateLogger "Ldftn"
|
||||
|
||||
let method =
|
||||
match metadataToken with
|
||||
| MetadataToken.MethodDef handle -> activeAssy.Methods.[handle]
|
||||
| t -> failwith $"Unexpectedly asked to Ldftn a non-method: {t}"
|
||||
|
||||
logger.LogDebug (
|
||||
"Pushed pointer to function {LdFtnAssembly}.{LdFtnType}.{LdFtnMethodName}",
|
||||
method.DeclaringType.Assembly.Name,
|
||||
method.DeclaringType.Name,
|
||||
method.Name
|
||||
)
|
||||
|
||||
state
|
||||
|> IlMachineState.pushToEvalStack'
|
||||
(EvalStackValue.NativeInt (NativeIntSource.FunctionPointer method))
|
||||
|
Reference in New Issue
Block a user