Skip to content

Commit

Permalink
Merge pull request #138 from Washi1337/feature/type-initializers
Browse files Browse the repository at this point in the history
Emulated Type Initializers
  • Loading branch information
Washi1337 authored Mar 22, 2024
2 parents 90da97a + 4ff94aa commit 6acf1ba
Show file tree
Hide file tree
Showing 21 changed files with 530 additions and 47 deletions.
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

0 comments on commit 6acf1ba

Please sign in to comment.