Skip to content

Commit

Permalink
Add constrained opcode handling.
Browse files Browse the repository at this point in the history
  • Loading branch information
Washi1337 committed Mar 23, 2024
1 parent 7d2705f commit 81bf8c3
Show file tree
Hide file tree
Showing 12 changed files with 367 additions and 187 deletions.
53 changes: 35 additions & 18 deletions src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilThread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,27 +242,44 @@ private void Step(CilExecutionContext context)

// Determine the next instruction to dispatch.
int pc = currentFrame.ProgramCounter;
var instruction = body.Instructions.GetByOffset(pc);
if (instruction is null)
throw new CilEmulatorException($"Invalid program counter in {currentFrame}.");
int index = body.Instructions.GetIndexByOffset(pc);

// Are we entering any protected regions?
UpdateExceptionHandlerStack();

// Dispatch the instruction.
var result = Machine.Dispatcher.Dispatch(context, instruction);

if (!result.IsSuccess)
// Note: This is a loop because instructions can have prefixes. A single step must execute an entire
// instruction including its prefixes.
bool continuing = true;
while (continuing)
{
var exceptionObject = result.ExceptionObject;
if (exceptionObject.IsNull)
throw new CilEmulatorException("A null exception object was thrown.");

// If there were any errors thrown after dispatching, it may trigger the execution of one of the
// exception handlers in the entire call stack.
if (!UnwindCallStack(ref exceptionObject))
throw new EmulatedException(exceptionObject);
var instruction = body.Instructions[index];
if (instruction is null)
throw new CilEmulatorException($"Invalid program counter in {currentFrame}.");

// Continue until we are no longer a prefix instruction.
continuing = instruction.OpCode.OpCodeType == CilOpCodeType.Prefix;
index++;

// Are we entering any protected regions?
UpdateExceptionHandlerStack();

// Dispatch the instruction.
var result = Machine.Dispatcher.Dispatch(context, instruction);

// Did we throw an exception?
if (!result.IsSuccess)
{
var exceptionObject = result.ExceptionObject;
if (exceptionObject.IsNull)
throw new CilEmulatorException("A null exception object was thrown.");

// If there were any errors thrown after dispatching, it may trigger the execution of one of the
// exception handlers in the entire call stack. If no handler is catching it, we communicate it to
// the caller of the Echo API itself.
if (!UnwindCallStack(ref exceptionObject))
throw new EmulatedException(exceptionObject);
}
}

// Reset any prefix-related flags.
currentFrame.ConstrainedType = null;
}

private void UpdateExceptionHandlerStack()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,23 @@ namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel
public class CallHandler : CallHandlerBase
{
/// <inheritdoc />
protected override MethodDevirtualizationResult DevirtualizeMethodInternal(CilExecutionContext context,
CilInstruction instruction,
protected override MethodDevirtualizationResult DevirtualizeMethodInternal(
CilExecutionContext context,
IMethodDescriptor method,
IList<BitVector> arguments)
{
// If we are constraining on a specific declaring type, we need to simulate a "virtual dispatch" on that type.
if (context.CurrentFrame.ConstrainedType?.Resolve() is { } constrainedType)
{
var resolvedBaseMethod = method.Resolve();
if (resolvedBaseMethod is { IsVirtual: true })
{
var implementationMethod = FindMethodImplementationInType(constrainedType, resolvedBaseMethod);
if (implementationMethod is not null)
return MethodDevirtualizationResult.Success(implementationMethod);
}
}

return MethodDevirtualizationResult.Success(method);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ protected virtual bool ShouldPopInstanceObject(IMethodDescriptor method)
/// </summary>
/// <param name="context">The context to evaluate the instruction in.</param>
/// <param name="instruction">The instruction to dispatch and evaluate.</param>
/// <param name="method">The method that is called.</param>
/// <param name="arguments">The arguments to call the method with.</param>
/// <returns>The dispatching result.</returns>
protected CilDispatchResult HandleCall(
Expand Down Expand Up @@ -115,12 +116,13 @@ private static BitVector GetInstancePointer(CilExecutionContext context, IMethod
return stack.Pop(declaringType);
}

private MethodDevirtualizationResult DevirtualizeMethod(CilExecutionContext context,
private MethodDevirtualizationResult DevirtualizeMethod(
CilExecutionContext context,
CilInstruction instruction,
IMethodDescriptor method,
IList<BitVector> arguments)
{
var result = DevirtualizeMethodInternal(context, instruction, method, arguments);
var result = DevirtualizeMethodInternal(context, method, arguments);
if (!result.IsUnknown)
return result;

Expand All @@ -137,14 +139,14 @@ private MethodDevirtualizationResult DevirtualizeMethod(CilExecutionContext cont
/// Devirtualizes and resolves the method that is referenced by the provided instruction.
/// </summary>
/// <param name="context">The execution context the instruction is evaluated in.</param>
/// <param name="instruction">The instruction that is being evaluated.</param>
/// <param name="method"></param>
/// <param name="method">The method that is being devirtualized.</param>
/// <param name="arguments">The arguments pushed onto the stack.</param>
/// <returns>The result of the devirtualization.</returns>
protected abstract MethodDevirtualizationResult DevirtualizeMethodInternal(CilExecutionContext context,
CilInstruction instruction,
protected abstract MethodDevirtualizationResult DevirtualizeMethodInternal(
CilExecutionContext context,
IMethodDescriptor method,
IList<BitVector> arguments);
IList<BitVector> arguments
);

private static IMethodDescriptor InstantiateDeclaringType(IMethodDescriptor caller, IMethodDescriptor callee)
{
Expand Down Expand Up @@ -208,5 +210,120 @@ private static CilDispatchResult Invoke(CilExecutionContext context, IMethodDesc
throw new ArgumentOutOfRangeException();
}
}

protected static MethodDefinition? FindMethodImplementationInType(TypeDefinition? type, MethodDefinition? baseMethod)

Check warning on line 214 in src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandlerBase.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'CallHandlerBase.FindMethodImplementationInType(TypeDefinition?, MethodDefinition?)'

Check warning on line 214 in src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandlerBase.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'CallHandlerBase.FindMethodImplementationInType(TypeDefinition?, MethodDefinition?)'

Check warning on line 214 in src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandlerBase.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'CallHandlerBase.FindMethodImplementationInType(TypeDefinition?, MethodDefinition?)'
{
if (type is null || baseMethod is null || !baseMethod.IsVirtual)
return baseMethod;

var implementation = default(MethodDefinition);
var declaringType = baseMethod.DeclaringType!;

// If this is a static method, it means we must be implementing a 'static abstract' interface method.
if (baseMethod.IsStatic && !declaringType.IsInterface)
return baseMethod;

while (type is not null && !SignatureComparer.Default.Equals(type, declaringType))
{
if (baseMethod.IsStatic)
{
// Static base methods can only be implemented through explicit interface implementation.
implementation = TryFindExplicitInterfaceImplementationInType(type, baseMethod);
}
else
{
// Prioritize interface implementations.
if (declaringType.IsInterface)
{
implementation = TryFindExplicitInterfaceImplementationInType(type, baseMethod)
?? TryFindImplicitInterfaceImplementationInType(type, baseMethod);
}

// Try to find other implicit implementations.
implementation ??= TryFindImplicitImplementationInType(type, baseMethod);
}

if (implementation is not null)
break;

// Move up type hierarchy tree.
type = type.BaseType?.Resolve();
}

// If there's no override, just use the base implementation (if available).
if (implementation is null && !baseMethod.IsAbstract)
implementation = baseMethod;

return implementation;
}

private static MethodDefinition? TryFindImplicitImplementationInType(TypeDefinition type, MethodDefinition baseMethod)
{
foreach (var method in type.Methods)
{
if (method is { IsVirtual: true, IsReuseSlot: true }
&& method.Name == baseMethod.Name
&& SignatureComparer.Default.Equals(method.Signature, baseMethod.Signature))
{
return method;
}
}

return null;
}

private static MethodDefinition? TryFindImplicitInterfaceImplementationInType(TypeDefinition type, MethodDefinition baseMethod)
{
// Find the correct interface implementation and instantiate any generics.
MethodSignature? baseMethodSig = null;
foreach (var interfaceImpl in type.Interfaces)
{
if (SignatureComparer.Default.Equals(interfaceImpl.Interface?.ToTypeSignature().GetUnderlyingTypeDefOrRef(), baseMethod.DeclaringType))
{
baseMethodSig = baseMethod.Signature?.InstantiateGenericTypes(GenericContext.FromType(interfaceImpl.Interface!));
break;
}
}
if (baseMethodSig is null)
return null;

// Find implemented method in type.
for (int i = 0; i < type.Methods.Count; i++)
{
var method = type.Methods[i];
// Only public virtual instance methods can implicity implement interface methods. (ECMA-335, 6th edition, II.12.2)
if (method is { IsPublic: true, IsVirtual: true, IsStatic: false }
&& method.Name == baseMethod.Name
&& SignatureComparer.Default.Equals(method.Signature, baseMethodSig))
{
return method;
}
}

return null;
}

private static MethodDefinition? TryFindExplicitInterfaceImplementationInType(TypeDefinition type, MethodDefinition baseMethod)
{
for (int i = 0; i < type.MethodImplementations.Count; i++)
{
var impl = type.MethodImplementations[i];
if (impl.Declaration is null || impl.Declaration.Name != baseMethod.Name)
continue;

// Compare underlying TypeDefOrRef and instantiate any generics to ensure correct comparison.
var declaringType = impl.Declaration?.DeclaringType?.ToTypeSignature().GetUnderlyingTypeDefOrRef();
if (!SignatureComparer.Default.Equals(declaringType, baseMethod.DeclaringType))
continue;

var context = GenericContext.FromMethod(impl.Declaration!);
var implMethodSig = impl.Declaration!.Signature?.InstantiateGenericTypes(context);
var baseMethodSig = baseMethod.Signature?.InstantiateGenericTypes(context);
if (SignatureComparer.Default.Equals(baseMethodSig, implMethodSig))
return impl.Body?.Resolve();
}

return null;
}
}
}
Loading

0 comments on commit 81bf8c3

Please sign in to comment.