Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emulated Type Initializers #138

Merged
merged 8 commits into from
Mar 22, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ private void Step(CilExecutionContext context)

// 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(exceptionObject))
if (!UnwindCallStack(ref exceptionObject))
throw new EmulatedException(exceptionObject);
}
}
Expand All @@ -281,12 +281,16 @@ private void UpdateExceptionHandlerStack()
}
}

private bool UnwindCallStack(ObjectHandle exceptionObject)
private bool UnwindCallStack(ref ObjectHandle exceptionObject)
{
while (!CallStack.Peek().IsRoot)
{
var currentFrame = CallStack.Peek();

// If the exception happened in a .cctor, register it and wrap it in a type initialization error.
if (currentFrame.Body?.Owner is { IsConstructor: true, IsStatic: true, DeclaringType: {} type })
exceptionObject = Machine.TypeManager.RegisterInitializationException(type, exceptionObject);

var result = currentFrame.ExceptionHandlerStack.RegisterException(exceptionObject);
if (result.IsSuccess)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public CilVirtualMachine(ModuleDefinition contextModule, bool is32Bit)
}

Dispatcher = new CilDispatcher();
TypeManager = new RuntimeTypeManager(this);
Threads = new ReadOnlyCollection<CilThread>(_threads);
}

Expand Down Expand Up @@ -152,6 +153,15 @@ public IMethodInvoker Invoker
set;
} = DefaultInvokers.ReturnUnknown;

/// <summary>
/// Gets the service that is responsible for the initialization and management of runtime types residing in
/// the virtual machine.
/// </summary>
public RuntimeTypeManager TypeManager
{
get;
}

/// <summary>
/// Gets or sets the service that is responsible for resolving unknown values on the stack in critical moments.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,16 @@ private static CilDispatchResult Invoke(CilExecutionContext context, IMethodDesc
frame.WriteArgument(i, arguments[i]);

context.Thread.CallStack.Push(frame);

// Ensure type initializer is called for declaring type when necessary.
// TODO: Handle `beforefieldinit` flag.
if (method.DeclaringType is { } declaringType)
{
return context.Machine.TypeManager
.HandleInitialization(context.Thread, declaringType)
.ToDispatchResult();
}

return CilDispatchResult.Success();

case InvocationResultType.StepOver:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using AsmResolver.DotNet;
using AsmResolver.PE.DotNet.Cil;

namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel;

/// <summary>
/// Represents a handler that handles opcodes related to field access.
/// </summary>
public abstract class FieldOpCodeHandler : ICilOpCodeHandler
{
/// <inheritdoc />
public CilDispatchResult Dispatch(CilExecutionContext context, CilInstruction instruction)
{
var field = (IFieldDescriptor) instruction.Operand!;

// Ensure the enclosing type is initialized in the runtime.
if (field.DeclaringType is { } declaringType)
{
var initResult = context.Machine.TypeManager.HandleInitialization(context.Thread, declaringType);
if (!initResult.IsNoAction)
return initResult.ToDispatchResult();
}

// Handle the actual field operation.
var dispatchResult = DispatchInternal(context, instruction, field);

// We are not inheriting from FallThroughOpCodeHandler because of the type initialization.
// This means we need to manually increase the PC on success.
if (dispatchResult.IsSuccess)
context.CurrentFrame.ProgramCounter += instruction.Size;

return dispatchResult;
}

/// <summary>
/// Handles the actual operation on the field.
/// </summary>
/// <param name="context">The context to evaluate the instruction in.</param>
/// <param name="instruction">The instruction to dispatch and evaluate.</param>
/// <param name="field">The field to perform the operation on.</param>
/// <returns>The dispatching result.</returns>
protected abstract CilDispatchResult DispatchInternal(
CilExecutionContext context,
CilInstruction instruction,
IFieldDescriptor field
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using AsmResolver.DotNet;
using AsmResolver.PE.DotNet.Cil;
using Echo.Memory;
using Echo.Platforms.AsmResolver.Emulation.Stack;

namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel
Expand All @@ -10,15 +9,17 @@ namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel
/// Implements a CIL instruction handler for <c>ldfld</c> operations.
/// </summary>
[DispatcherTableEntry(CilCode.Ldfld)]
public class LdFldHandler : FallThroughOpCodeHandler
public class LdFldHandler : FieldOpCodeHandler
{
/// <inheritdoc />
protected override CilDispatchResult DispatchInternal(CilExecutionContext context, CilInstruction instruction)
protected override CilDispatchResult DispatchInternal(
CilExecutionContext context,
CilInstruction instruction,
IFieldDescriptor field)
{
var stack = context.CurrentFrame.EvaluationStack;
var factory = context.Machine.ValueFactory;

var field = (IFieldDescriptor) instruction.Operand!;
var instance = stack.Pop();

try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel
/// Implements a CIL instruction handler for <c>ldflda</c> operations.
/// </summary>
[DispatcherTableEntry(CilCode.Ldflda)]
public class LdFldaHandler : FallThroughOpCodeHandler
public class LdFldaHandler : FieldOpCodeHandler
{
/// <inheritdoc />
protected override CilDispatchResult DispatchInternal(CilExecutionContext context, CilInstruction instruction)
protected override CilDispatchResult DispatchInternal(
CilExecutionContext context,
CilInstruction instruction,
IFieldDescriptor field)
{
var stack = context.CurrentFrame.EvaluationStack;
var factory = context.Machine.ValueFactory;

var field = (IFieldDescriptor) instruction.Operand!;
var instance = stack.Pop();
var result = context.Machine.ValueFactory.RentNativeInteger(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel
/// Implements a CIL instruction handler for <c>ldsfld</c> operations.
/// </summary>
[DispatcherTableEntry(CilCode.Ldsfld)]
public class LdsFldHandler : FallThroughOpCodeHandler
public class LdsFldHandler : FieldOpCodeHandler
{
/// <inheritdoc />
protected override CilDispatchResult DispatchInternal(CilExecutionContext context, CilInstruction instruction)
protected override CilDispatchResult DispatchInternal(
CilExecutionContext context,
CilInstruction instruction,
IFieldDescriptor field)
{
var field = (IFieldDescriptor) instruction.Operand!;
var fieldSpan = context.Machine.StaticFields.GetFieldSpan(field);
context.CurrentFrame.EvaluationStack.Push(fieldSpan, field.Signature!.FieldType);
return CilDispatchResult.Success();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel
/// Implements a CIL instruction handler for <c>ldsflda</c> operations.
/// </summary>
[DispatcherTableEntry(CilCode.Ldsflda)]
public class LdsFldaHandler : FallThroughOpCodeHandler
public class LdsFldaHandler : FieldOpCodeHandler
{
/// <inheritdoc />
protected override CilDispatchResult DispatchInternal(CilExecutionContext context, CilInstruction instruction)
protected override CilDispatchResult DispatchInternal(
CilExecutionContext context,
CilInstruction instruction,
IFieldDescriptor field)
{
var field = (IFieldDescriptor) instruction.Operand!;
var address = context.Machine.ValueFactory.RentNativeInteger(
context.Machine.StaticFields.GetFieldAddress(field));
context.CurrentFrame.EvaluationStack.Push(new StackSlot(address, StackSlotTypeHint.Integer));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using AsmResolver.DotNet;
using AsmResolver.PE.DotNet.Cil;
using Echo.Memory;
using Echo.Platforms.AsmResolver.Emulation.Stack;

namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel
Expand All @@ -9,15 +8,17 @@ namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel
/// Implements a CIL instruction handler for <c>stfld</c> operations.
/// </summary>
[DispatcherTableEntry(CilCode.Stfld)]
public class StFldHandler : FallThroughOpCodeHandler
public class StFldHandler : FieldOpCodeHandler
{
/// <inheritdoc />
protected override CilDispatchResult DispatchInternal(CilExecutionContext context, CilInstruction instruction)
protected override CilDispatchResult DispatchInternal(
CilExecutionContext context,
CilInstruction instruction,
IFieldDescriptor field)
{
var stack = context.CurrentFrame.EvaluationStack;
var factory = context.Machine.ValueFactory;

var field = (IFieldDescriptor) instruction.Operand!;
var value = stack.Pop(field.Signature!.FieldType);
var instance = stack.Pop();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel
/// Implements a CIL instruction handler for <c>stsfld</c> operations.
/// </summary>
[DispatcherTableEntry(CilCode.Stsfld)]
public class StsFldHandler : FallThroughOpCodeHandler
public class StsFldHandler : FieldOpCodeHandler
{
/// <inheritdoc />
protected override CilDispatchResult DispatchInternal(CilExecutionContext context, CilInstruction instruction)
protected override CilDispatchResult DispatchInternal(
CilExecutionContext context,
CilInstruction instruction,
IFieldDescriptor field)
{
var field = (IFieldDescriptor) instruction.Operand!;
var value = context.CurrentFrame.EvaluationStack.Pop(field.Signature!.FieldType);

try
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

using System;
using AsmResolver.DotNet;
using AsmResolver.DotNet.Signatures;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using AsmResolver;
using AsmResolver.DotNet;
using AsmResolver.DotNet.Signatures;

namespace Echo.Platforms.AsmResolver.Emulation.Runtime;

/// <summary>
/// Provides a mechanism for initialization and management of types residing in a virtual machine.
/// </summary>
public sealed class RuntimeTypeManager
{
private readonly CilVirtualMachine _machine;

private readonly ConcurrentDictionary<ITypeDescriptor, TypeInitialization> _initializations
= new(SignatureComparer.Default);

/// <summary>
/// Creates a new runtime type manager.
/// </summary>
/// <param name="machine">The machine the type is made for.</param>
public RuntimeTypeManager(CilVirtualMachine machine)
{
_machine = machine;
}

private TypeInitialization GetInitialization(ITypeDescriptor type)
{
if (_initializations.TryGetValue(type, out var initialization))
return initialization;

var newInitialization = new TypeInitialization(type);
while (!_initializations.TryGetValue(type, out initialization))
{
if (_initializations.TryAdd(type, newInitialization))
{
initialization = newInitialization;
break;
}
}

return initialization;
}

/// <summary>
/// Registers the event that a type has failed to initialize.
/// </summary>
/// <param name="type">The type that failed to initialize.</param>
/// <param name="innerException">The exception object that describes the failure.</param>
/// <returns>The resulting TypeInitializationException instance.</returns>
public ObjectHandle RegisterInitializationException(ITypeDescriptor type, ObjectHandle innerException)
{
var initialization = GetInitialization(type);
if (!initialization.Exception.IsNull)
return initialization.Exception;

lock (initialization)
{
if (initialization.Exception.IsNull)
{
initialization.Exception = _machine.Heap
.AllocateObject(_machine.ValueFactory.TypeInitializationExceptionType, true)
.AsObjectHandle(_machine);
}

// TODO: incorporate `innerException`.
}

return initialization.Exception;
}

/// <summary>
/// Handles the type initialization on the provided thread.
/// </summary>
/// <param name="thread">The thread the initialization is to be called on.</param>
/// <param name="type">The type to initialize.</param>
/// <returns>The initialization result.</returns>
public TypeInitializerResult HandleInitialization(CilThread thread, ITypeDescriptor type)
{
var initialization = GetInitialization(type);

// If we already have an exception cached as a result of a previous type-load failure, rethrow it.
if (!initialization.Exception.IsNull)
return TypeInitializerResult.Exception(initialization.Exception);

// We only need to call the constructor once.
if (initialization.ConstructorCalled)
return TypeInitializerResult.NoAction();

lock (initialization)
{
// Try check if any thread beat us in the initialization handling.
if (!initialization.Exception.IsNull)
return TypeInitializerResult.Exception(initialization.Exception);

if (initialization.ConstructorCalled)
return TypeInitializerResult.NoAction();

// Try resolve the type that is being initialized.
var definition = type.Resolve();
if (definition is null)
{
initialization.Exception = _machine.Heap
.AllocateObject(_machine.ValueFactory.TypeInitializationExceptionType, true)
.AsObjectHandle(_machine);

return TypeInitializerResult.Exception(initialization.Exception);
}

// "Call" the constructor.
initialization.ConstructorCalled = true;

// Actually find the constructor and call it if it is there.
var cctor = definition.GetStaticConstructor();
if (cctor is not null)
{
thread.CallStack.Push(cctor);
return TypeInitializerResult.Redirected();
}

return TypeInitializerResult.NoAction();
}
}

private sealed class TypeInitialization
{
public TypeInitialization(ITypeDescriptor type)
{
Type = type;
}

public ITypeDescriptor Type { get; }

public bool ConstructorCalled { get; set; }

public ObjectHandle Exception { get; set; }
}
}
Loading
Loading