Skip to content

Instantly share code, notes, and snippets.

@lfkdsk
Last active July 5, 2022 10:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lfkdsk/42ee5cca8770dfe1b705077401955378 to your computer and use it in GitHub Desktop.
Save lfkdsk/42ee5cca8770dfe1b705077401955378 to your computer and use it in GitHub Desktop.
MethodBodyWriter
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Unity.IL2CPP.Common;
using Unity.IL2CPP.Common.CFG;
using Unity.IL2CPP.Common.Profiles;
using Unity.IL2CPP.Debugger;
using Unity.IL2CPP.Tiny;
using Unity.IL2CPP.GenericSharing;
using Unity.IL2CPP.ILPreProcessor;
using Unity.IL2CPP.IoCServices;
using Unity.IL2CPP.Marshaling.BodyWriters.NativeToManaged;
using Unity.IL2CPP.Metadata;
using Unity.IL2CPP.Naming;
using Unity.IL2CPP.StackAnalysis;
using Unity.IL2CPP.BD.Cecil.Decompiler;
namespace Unity.IL2CPP
{
class MethodBodyWriterDebugOptions
{
public bool EmitInputAndOutputs { get; set; }
public bool EmitLineNumbers { get; set; }
public bool EmitIlCode { get; set; }
public bool EmitBlockInfo { get; set; }
}
public enum MethodCallType
{
Normal,
Virtual,
VirtualEx,
DirectVirtual
}
public enum SharingType
{
NonShared,
Shared
}
public class MethodBodyWriter
{
private const string VariableNameForVirtualInvokeDataInDirectVirtualCall = "il2cpp_virtual_invoke_data_";
private const string VariableNameForTypeInfoInConstrainedCall = "il2cpp_this_typeinfo";
private int _tempIndex;
private readonly SharingType _sharingType;
private readonly Labeler _labeler;
private readonly IGeneratedMethodCodeWriter _writer;
private readonly ControlFlowGraph _cfg;
private readonly TypeResolver _typeResolver;
private readonly MethodReference _methodReference;
private readonly MethodDefinition _methodDefinition;
private readonly MethodBodyWriterDebugOptions _options;
private readonly StackAnalysis.StackAnalysis _stackAnalysis;
private readonly IRuntimeMetadataAccess _runtimeMetadataAccess;
private readonly ArrayBoundsCheckSupport _arrayBoundsCheckSupport;
private readonly DivideByZeroCheckSupport _divideByZeroCheckSupport;
private readonly VTableBuilder _vTableBuilder;
private readonly Stack<StackInfo> _valueStack = new Stack<StackInfo>();
private readonly HashSet<Instruction> _emittedLabels = new HashSet<Instruction>();
private readonly HashSet<Instruction> _referencedLabels = new HashSet<Instruction>();
private readonly HashSet<TypeReference> _classesAlreadyInitializedInBlock = new HashSet<TypeReference>(new TypeReferenceEqualityComparer());
private bool _thisInstructionIsVolatile = false;
private ExceptionSupport _exceptionSupport;
private NullChecksSupport _nullCheckSupport;
private TypeReference _constrainedCallThisType;
private readonly ISourceAnnotationWriter _sourceAnnotationWriter;
private ISequencePointProvider _sequencePointProvider;
private ICatchPointCollector _catchPointCollector;
private Dictionary<VariableDefinition, TypeReference> _typeReferences =
new Dictionary<VariableDefinition, TypeReference>();
enum OverflowCheck
{
None,
Signed,
Unsigned
}
public MethodBodyWriter(IGeneratedMethodCodeWriter writer, MethodReference methodReference, TypeResolver typeResolver, IRuntimeMetadataAccess metadataAccess, VTableBuilder vTableBuilder, ISourceAnnotationWriter sourceAnnotationWriter, ISequencePointProvider sequencePointProvider, ICatchPointCollector catchPointCollector)
{
_methodReference = methodReference;
_methodDefinition = methodReference.Resolve();
_nullCheckSupport = new NullChecksSupport(writer, _methodDefinition, CodeGenOptions.EmitNullChecks);
_arrayBoundsCheckSupport = new ArrayBoundsCheckSupport(_methodDefinition, CodeGenOptions.EnableArrayBoundsCheck);
_divideByZeroCheckSupport = new DivideByZeroCheckSupport(writer, _methodDefinition, CodeGenOptions.EnableDivideByZeroCheck);
_sequencePointProvider = sequencePointProvider;
_catchPointCollector = catchPointCollector;
_cfg = ControlFlowGraph.Create(_methodDefinition);
_writer = writer;
_typeResolver = typeResolver;
_vTableBuilder = vTableBuilder;
_options = new MethodBodyWriterDebugOptions();
_sourceAnnotationWriter = sourceAnnotationWriter;
_stackAnalysis = StackAnalysis.StackAnalysis.Analyze(_methodDefinition, _cfg, _typeResolver);
DeadBlockAnalysis.MarkBlocksDeadIfNeeded(_cfg.Blocks);
_labeler = new Labeler(_methodDefinition);
_runtimeMetadataAccess = metadataAccess;
_sharingType = GenericSharingAnalysis.IsSharedMethod(methodReference) ? SharingType.Shared : SharingType.NonShared;
}
private TypeReference Int16TypeReference
{
get { return Globals.TypeProvider.Int16TypeReference; }
}
private TypeReference UInt16TypeReference
{
get { return Globals.TypeProvider.UInt16TypeReference; }
}
private TypeReference Int32TypeReference
{
get { return Globals.TypeProvider.Int32TypeReference; }
}
private TypeReference SByteTypeReference
{
get { return Globals.TypeProvider.SByteTypeReference; }
}
private TypeReference ByteTypeReference
{
get { return Globals.TypeProvider.ByteTypeReference; }
}
private TypeReference IntPtrTypeReference
{
get { return Globals.TypeProvider.IntPtrTypeReference; }
}
private TypeReference UIntPtrTypeReference
{
get { return Globals.TypeProvider.UIntPtrTypeReference; }
}
private TypeReference Int64TypeReference
{
get { return Globals.TypeProvider.Int64TypeReference; }
}
private TypeReference UInt32TypeReference
{
get { return Globals.TypeProvider.UInt32TypeReference; }
}
private TypeReference UInt64TypeReference
{
get { return Globals.TypeProvider.UInt64TypeReference; }
}
private TypeReference SingleTypeReference
{
get { return Globals.TypeProvider.SingleTypeReference; }
}
private TypeReference DoubleTypeReference
{
get { return Globals.TypeProvider.DoubleTypeReference; }
}
private TypeReference ObjectTypeReference
{
get { return Globals.TypeProvider.ObjectTypeReference; }
}
private TypeReference StringTypeReference
{
get { return Globals.TypeProvider.StringTypeReference; }
}
private TypeReference SystemIntPtr
{
get { return Globals.TypeProvider.SystemIntPtr; }
}
private TypeReference SystemUIntPtr
{
get { return Globals.TypeProvider.SystemUIntPtr; }
}
private TypeReference RuntimeTypeHandleTypeReference
{
get { return Globals.TypeProvider.RuntimeTypeHandleTypeReference; }
}
private TypeReference RuntimeMethodHandleTypeReference
{
get { return Globals.TypeProvider.RuntimeMethodHandleTypeReference; }
}
private TypeReference RuntimeFieldHandleTypeReference
{
get { return Globals.TypeProvider.RuntimeFieldHandleTypeReference; }
}
public void Generate()
{
if (!_methodDefinition.HasBody)
return;
ErrorInformation.CurrentlyProcessing.Method = _methodDefinition;
if (GenericsUtilities.CheckForMaximumRecursion(_methodReference.DeclaringType as GenericInstanceType) ||
GenericsUtilities.CheckForMaximumRecursion(_methodReference as GenericInstanceMethod))
{
_writer.WriteStatement(Emit.RaiseManagedException("il2cpp_codegen_get_maximum_nested_generics_exception()"));
return;
}
WriteLocalVariables();
if (CodeGenOptions.EnableDebugger)
{
WriteDebuggerSupport();
SequencePointInfo sequencePoint;
if (_sequencePointProvider.TryGetSequencePointAt(_methodDefinition, SequencePointInfo.MethodEntryIlOffset, SequencePointKind.Normal, out sequencePoint))
WriteCheckSequencePoint(sequencePoint);
if (_sequencePointProvider.TryGetSequencePointAt(_methodDefinition, SequencePointInfo.MethodExitIlOffset, SequencePointKind.Normal, out sequencePoint))
WriteCheckMethodExitSequencePoint(sequencePoint);
WriteCheckPausePoint(SequencePointInfo.MethodEntryIlOffset);
}
_exceptionSupport = new ExceptionSupport(_methodDefinition, _cfg.Blocks, _writer);
_exceptionSupport.Prepare();
CollectUsedLabels();
foreach (var exceptionHandler in _methodDefinition.Body.ExceptionHandlers)
{
if (exceptionHandler.CatchType != null)
_writer.AddIncludeForTypeDefinition(_typeResolver.Resolve(exceptionHandler.CatchType));
}
foreach (var globalVariable in _stackAnalysis.Globals)
_writer.WriteVariable(_typeResolver.Resolve(globalVariable.Type, true), globalVariable.VariableName);
foreach (var node in _exceptionSupport.FlowTree.Children)
GenerateCodeRecursive(node);
}
private void WriteDebuggerSupport()
{
bool hasSequencePoints = _sequencePointProvider.MethodHasSequencePoints(_methodDefinition);
var thisVariable = WellKnown.Constants.Null;
if (hasSequencePoints && _methodDefinition.HasThis)
{
_runtimeMetadataAccess.Il2CppTypeFor(_methodDefinition.DeclaringType); // Force metadata initialization
thisVariable = Globals.Naming.ForMethodExecutionContextThisVariable();
_writer.WriteStatement($"DECLARE_METHOD_THIS({Globals.Naming.ForMethodExecutionContextThisVariable()}, {Emit.AddressOf(WellKnown.ParameterNames.This)})");
}
var parametersVariable = WellKnown.Constants.Null;
if (hasSequencePoints && _methodDefinition.HasParameters)
{
var parameters = new List<string>();
foreach (var parameter in _methodDefinition.Parameters)
{
//_runtimeMetadataAccess.Il2CppTypeFor(parameter.ParameterType); // Force metadata initialization
parameters.Add(Emit.AddressOf(Globals.Naming.ForParameterName(parameter)));
}
parametersVariable = Globals.Naming.ForMethodExecutionContextParametersVariable();
_writer.WriteStatement($"DECLARE_METHOD_PARAMS({Globals.Naming.ForMethodExecutionContextParametersVariable()}, {parameters.AggregateWithComma()})");
}
var localsVariable = WellKnown.Constants.Null;
if (hasSequencePoints && _methodDefinition.Body.HasVariables)
{
var variables = new List<string>();
foreach (var localVariable in _methodDefinition.Body.Variables)
{
string localVariableName;
var variableType = _typeResolver.Resolve(localVariable.VariableType);
_runtimeMetadataAccess.Il2CppTypeFor(variableType.GetNonPinnedAndNonByReferenceType()); // Force metadata initialization
if (_methodDefinition.DebugInformation != null &&
_methodDefinition.DebugInformation.TryGetName(localVariable, out localVariableName))
variables.Add(Emit.AddressOf(Globals.Naming.ForVariableName(localVariable)));
}
if (variables.Count > 0)
{
localsVariable = Globals.Naming.ForMethodExecutionContextLocalsVariable();
_writer.WriteStatement(
$"DECLARE_METHOD_LOCALS({Globals.Naming.ForMethodExecutionContextLocalsVariable()}, {variables.AggregateWithComma()})");
}
}
var method = _methodDefinition.HasGenericParameters ? "method" : _runtimeMetadataAccess.MethodInfo(_methodDefinition);
_writer.WriteStatement($"DECLARE_METHOD_EXEC_CTX({Globals.Naming.ForMethodExecutionContextVariable()}, {method}, {thisVariable}, {parametersVariable}, {localsVariable})");
}
private void WriteLocalVariables()
{
foreach (var variable in ResolveLocalVariableTypes())
{
_writer.AddIncludeForTypeDefinition(variable.Value);
_writer.WriteVariable(variable.Value, variable.Key);
}
}
private IEnumerable<KeyValuePair<string, TypeReference>> ResolveLocalVariableTypes()
{
return _methodDefinition.Body.Variables.Select(
v => new KeyValuePair<string, TypeReference>(
Globals.Naming.ForVariableName(v),
_typeResolver.Resolve(v.VariableType)));
}
private void CollectUsedLabels()
{
foreach (var block in _cfg.Blocks.Where(block => block.IsBranchTarget))
_referencedLabels.Add(block.First);
foreach (var node in _exceptionSupport.FlowTree.Children)
RecursivelyAddLabelsForExceptionNodes(node);
}
private void RecursivelyAddLabelsForExceptionNodes(ExceptionSupport.Node node)
{
if (node.Type != ExceptionSupport.NodeType.Try && node.Type != ExceptionSupport.NodeType.Catch && node.Type != ExceptionSupport.NodeType.Finally && node.Type != ExceptionSupport.NodeType.Fault)
return;
if (node.Block != null)
_referencedLabels.Add(node.Block.First);
foreach (var child in node.Children)
{
if (child.Block != null)
_referencedLabels.Add(child.Block.First);
RecursivelyAddLabelsForExceptionNodes(child);
}
}
private void GenerateCodeRecursive(ExceptionSupport.Node node)
{
var block = node.Block;
if (block != null)
{
if (node.Children.Length > 0)
throw new NotSupportedException("Node with explicit Block should have no children!");
if (block.IsDead && !IsLastBlockInNonVoidMethod(block))
{
WriteComment("Dead block : {0}", block.First.ToString());
return;
}
_valueStack.Clear();
// Setup value stack with global input variables
foreach (var globalVariable in _stackAnalysis.InputVariablesFor(block).OrderBy(v => v.Index).Reverse())
_valueStack.Push(new StackInfo(globalVariable.VariableName, _typeResolver.Resolve(globalVariable.Type, true)));
_exceptionSupport.PushExceptionOnStackIfNeeded(node, _valueStack, _typeResolver, Globals.TypeProvider.SystemException);
if (_options.EmitBlockInfo)
WriteComment("BLOCK: {0}", block.Index);
if (_options.EmitInputAndOutputs)
DumpInsFor(block);
var ins = block.First;
WriteLabelForBranchTarget(ins);
EnterNode(node);
_classesAlreadyInitializedInBlock.Clear();
while (true)
{
var sequencePoint = GetSequencePoint(ins);
if (sequencePoint != null)
{
if (sequencePoint.StartLine != 0x00feefee && _options.EmitLineNumbers)
_writer.WriteUnindented("#line {0} \"{1}\"", sequencePoint.StartLine, sequencePoint.Document.Url.Replace("\\", "\\\\"));
// Roslyn emits a lot of NOPs in debug builds, pointing to opening or closing braces
// Not much point in emitting those in comments
if (ins.OpCode != OpCodes.Nop)
_sourceAnnotationWriter.EmitAnnotation(_writer, sequencePoint);
WriteCheckSequencePoint(sequencePoint);
}
WriteCheckPausePoint(ins.Offset);
if (_options.EmitIlCode)
_writer.WriteUnindented("/* {0} */", ins);
ProcessInstruction(node, block, ref ins);
ProcessInstructionOperand(ins.Operand);
if (ins.Next == null || ins == block.Last)
break;
ins = ins.Next;
}
if (ins.OpCode.Code < Code.Br_S || ins.OpCode.Code > Code.Blt_Un)
{
if (block.Successors.Any() && ins.OpCode.Code != Code.Switch)
{
// Fallthrough, setup global output variables.
SetupFallthroughVariables(block);
}
}
if (_options.EmitInputAndOutputs)
DumpOutsFor(block);
if (_options.EmitBlockInfo)
{
if (block.Successors.Any())
WriteComment("END BLOCK {0} (succ: {1})", block.Index, block.Successors.Select(b => b.Index.ToString()).AggregateWithComma());
else
WriteComment("END BLOCK {0} (succ: none)", block.Index);
_writer.WriteLine();
_writer.WriteLine();
}
ExitNode(node);
}
else
{
if (node.Children.Length == 0)
throw new NotSupportedException("Unexpected empty node!");
WriteLabelForBranchTarget(node.Start);
EnterNode(node);
foreach (var child in node.Children)
GenerateCodeRecursive(child);
ExitNode(node);
}
}
bool IsLastBlockInNonVoidMethod(InstructionBlock block)
{
if (_methodDefinition.ReturnType.MetadataType != MetadataType.Void)
return block.Last.Next == null;
return false;
}
private void ProcessInstructionOperand(object operand)
{
var methodReference = operand as MethodReference;
if (methodReference != null)
ProcessMethodReferenceOperand(methodReference);
var fieldReference = operand as FieldReference;
if (fieldReference != null)
ProcessFieldReferenceOperand(fieldReference);
}
private void ProcessMethodReferenceOperand(MethodReference methodReference)
{
var methodDefinition = methodReference.Resolve();
if (methodDefinition != null && methodDefinition.IsVirtual)
_writer.AddIncludeForTypeDefinition(_typeResolver.Resolve(methodReference.DeclaringType));
var typeResolverForMethod = _typeResolver;
var methodToUse = methodReference;
var genericInstanceMethod = methodReference as GenericInstanceMethod;
if (genericInstanceMethod != null)
{
methodToUse = _typeResolver.Resolve(methodReference);
typeResolverForMethod = _typeResolver.Nested(methodToUse as GenericInstanceMethod);
}
_writer.AddIncludeForTypeDefinition(typeResolverForMethod.Resolve(GenericParameterResolver.ResolveReturnTypeIfNeeded(methodToUse)));
if (methodToUse.HasThis)
_writer.AddIncludeForTypeDefinition(typeResolverForMethod.Resolve(GenericParameterResolver.ResolveThisTypeIfNeeded(methodToUse)));
foreach (var p in methodToUse.Parameters)
_writer.AddIncludeForTypeDefinition(
typeResolverForMethod.Resolve(GenericParameterResolver.ResolveParameterTypeIfNeeded(methodToUse, p)));
}
private void ProcessFieldReferenceOperand(FieldReference fieldReference)
{
_writer.AddIncludeForTypeDefinition(_typeResolver.Resolve(fieldReference.DeclaringType));
_writer.AddIncludeForTypeDefinition(_typeResolver.Resolve(GenericParameterResolver.ResolveFieldTypeIfNeeded(fieldReference)));
}
private void SetupFallthroughVariables(InstructionBlock block)
{
var outputVariables = _stackAnalysis.InputVariablesFor(block.Successors.Single());
WriteAssignGlobalVariables(outputVariables);
_valueStack.Clear();
foreach (var globalVariable in outputVariables.OrderBy(v => v.Index).Reverse())
_valueStack.Push(new StackInfo(globalVariable.VariableName, _typeResolver.Resolve(globalVariable.Type)));
}
private void EnterNode(ExceptionSupport.Node node)
{
switch (node.Type)
{
case ExceptionSupport.NodeType.Block:
_writer.BeginBlock();
break;
case ExceptionSupport.NodeType.Try:
EnterTry(node);
break;
case ExceptionSupport.NodeType.Catch:
EnterCatch(node);
break;
case ExceptionSupport.NodeType.Filter:
EnterFilter(node);
break;
case ExceptionSupport.NodeType.Finally:
EnterFinally(node);
break;
case ExceptionSupport.NodeType.Fault:
EnterFault(node);
break;
default:
throw new NotImplementedException("Unexpected node type " + node.Type);
}
}
private void EnterTry(ExceptionSupport.Node node)
{
EmitTry();
_writer.BeginBlock(string.Format("begin try (depth: {0})", node.Depth));
WriteStoreTryId(node);
}
private void EmitTry()
{
if (Globals.Profile != Profile.UnityTiny || CodeGenOptions.EnableDebugger)
_writer.WriteLine("try");
else
_writer.WriteLine("//try - Try blocks are not supported with the DOPS profile");
}
private void EnterFinally(ExceptionSupport.Node node)
{
_writer.BeginBlock(string.Format("begin finally (depth: {0})", node.Depth));
WriteStoreTryId(node.ParentTryNode);
}
private void EnterFault(ExceptionSupport.Node node)
{
_writer.BeginBlock(string.Format("begin fault (depth: {0})", node.Depth));
}
private void EnterCatch(ExceptionSupport.Node node)
{
_writer.BeginBlock(string.Format("begin catch({0})", node.Handler.HandlerType == ExceptionHandlerType.Catch ? node.Handler.CatchType.FullName : "filter"));
WriteStoreTryId(node.ParentTryNode);
if (CodeGenOptions.EnableDebugger)
_catchPointCollector.AddCatchPoint(_methodDefinition, node);
}
private void EnterFilter(ExceptionSupport.Node node)
{
_writer.BeginBlock($"begin filter(depth: {node.Depth})");
_writer.WriteLine($"bool {ExceptionSupport.LocalFilterName} = false;");
EmitTry();
_writer.BeginBlock("begin implicit try block");
}
private void ExitNode(ExceptionSupport.Node node)
{
switch (node.Type)
{
case ExceptionSupport.NodeType.Block:
_writer.EndBlock();
break;
case ExceptionSupport.NodeType.Try:
ExitTry(node);
break;
case ExceptionSupport.NodeType.Catch:
ExitCatch(node);
break;
case ExceptionSupport.NodeType.Filter:
ExitFilter(node);
break;
case ExceptionSupport.NodeType.Finally:
ExitFinally(node);
break;
case ExceptionSupport.NodeType.Fault:
ExitFault(node);
break;
default:
throw new NotImplementedException("Unexpected node type " + node.Type);
}
}
private void ExitTry(ExceptionSupport.Node node)
{
_writer.EndBlock(string.Format("end try (depth: {0})", node.Depth));
var catchNodes = node.CatchNodes;
var finallyNode = node.FinallyNode;
var filterNodes = node.FilterNodes;
var faultNode = node.FaultNode;
// TODO: Globals.TypeProvider.ExceptionTypeReference
if (Globals.Profile == Profile.UnityTiny && !CodeGenOptions.EnableDebugger)
_writer.WriteLine("/* Catch blocks are not supported with the Tiny profile");
_writer.WriteLine("catch(Il2CppExceptionWrapper& e)");
_writer.BeginBlock();
if (catchNodes.Length != 0)
{
_writer.WriteLine("{0} = ({1})e.ex;", ExceptionSupport.LocalExceptionName,
Globals.Naming.ForVariable(Globals.TypeProvider.SystemException));
foreach (var handler in catchNodes.Select(n => n.Handler))
{
if (CodeGenOptions.MonoRuntime)
_writer.WriteLine("if(il2cpp_codegen_class_is_assignable_from ({0}, il2cpp_codegen_object_class((RuntimeObject*)e.ex)))", _runtimeMetadataAccess.TypeInfoFor(handler.CatchType));
else
_writer.WriteLine("if(il2cpp_codegen_class_is_assignable_from ({0}, il2cpp_codegen_object_class(e.ex)))", _runtimeMetadataAccess.TypeInfoFor(handler.CatchType));
_writer.Indent();
_writer.WriteLine(_labeler.ForJump(handler.HandlerStart));
_writer.Dedent();
}
if (finallyNode != null)
{
_writer.WriteLine("{0} = ({1})e.ex;", ExceptionSupport.LastUnhandledExceptionName,
Globals.Naming.ForVariable(Globals.TypeProvider.SystemException));
_writer.WriteLine(_labeler.ForJump(finallyNode.Handler.HandlerStart));
}
else if (faultNode != null)
{
_writer.WriteLine("{0} = ({1})e.ex;", ExceptionSupport.LastUnhandledExceptionName,
Globals.Naming.ForVariable(Globals.TypeProvider.SystemException));
_writer.WriteLine(_labeler.ForJump(faultNode.Handler.HandlerStart));
}
else
_writer.WriteLine("throw e;");
}
else if (filterNodes.Length != 0)
{
_writer.WriteLine("{0} = ({1})e.ex;", ExceptionSupport.LocalExceptionName,
Globals.Naming.ForVariable(Globals.TypeProvider.SystemException));
}
else
{
if (finallyNode == null && faultNode == null)
throw new NotSupportedException("Try block ends without any catch, finally, nor fault handler");
_writer.WriteLine("{0} = ({1})e.ex;", ExceptionSupport.LastUnhandledExceptionName,
Globals.Naming.ForVariable(Globals.TypeProvider.SystemException));
if (finallyNode != null)
_writer.WriteLine(_labeler.ForJump(finallyNode.Handler.HandlerStart));
if (faultNode != null)
_writer.WriteLine(_labeler.ForJump(faultNode.Handler.HandlerStart));
}
_writer.EndBlock();
if (Globals.Profile == Profile.UnityTiny && !CodeGenOptions.EnableDebugger)
_writer.WriteLine("*/");
}
private void ExitCatch(ExceptionSupport.Node node)
{
_writer.EndBlock(string.Format("end catch (depth: {0})", node.Depth));
}
private void ExitFilter(ExceptionSupport.Node node)
{
_writer.EndBlock("end implicit try block");
_writer.WriteLine("catch(Il2CppExceptionWrapper&)");
_writer.BeginBlock("begin implicit catch block");
_writer.WriteLine($"{ExceptionSupport.LocalFilterName} = false;");
_writer.EndBlock("end implicit catch block");
_writer.WriteLine($"if ({ExceptionSupport.LocalFilterName})");
_writer.BeginBlock();
_writer.WriteLine(_labeler.ForJump(node.End.Next));
_writer.EndBlock();
_writer.WriteLine("else");
_writer.BeginBlock();
_writer.WriteStatement(Emit.RaiseManagedException(ExceptionSupport.LocalExceptionName,
_runtimeMetadataAccess.MethodInfo(_methodReference)));
_writer.EndBlock();
_writer.EndBlock($"end filter (depth: {node.Depth})");
}
private void ExitFinally(ExceptionSupport.Node node)
{
_writer.EndBlock(string.Format("end finally (depth: {0})", node.Depth));
_writer.WriteLine("IL2CPP_CLEANUP({0})", node.Start.Offset);
_writer.BeginBlock();
foreach (var targetInstruction in _exceptionSupport.LeaveTargetsFor(node))
{
var fn = node.GetTargetFinallyAndFaultNodesForJump(node.End.Offset, targetInstruction.Offset).ToArray();
if (fn.Length > 0)
_writer.WriteLine(
"IL2CPP_END_CLEANUP(0x{0:X}, {1});",
targetInstruction.Offset, _labeler.FormatOffset(fn.First().Start));
else
_writer.WriteLine(
"IL2CPP_JUMP_TBL(0x{0:X}, {1})",
targetInstruction.Offset, _labeler.FormatOffset(targetInstruction));
}
_writer.WriteLine("IL2CPP_RETHROW_IF_UNHANDLED({0})", Globals.Naming.ForVariable(Globals.TypeProvider.SystemException));
_writer.EndBlock();
}
private void ExitFault(ExceptionSupport.Node node)
{
_writer.EndBlock("end fault");
// NOTE(gab): fault is executed only if an unhandled exception is thrown.
// This means that we should only deal with situation where there is an enclosing finally or fault node,
// and act accordingly
_writer.WriteLine("IL2CPP_CLEANUP({0})", node.Start.Offset);
_writer.BeginBlock();
foreach (var targetInstruction1 in _exceptionSupport.LeaveTargetsFor(node))
{
var fn1 = node.GetTargetFinallyAndFaultNodesForJump(node.End.Offset, targetInstruction1.Offset).ToArray();
if (fn1.Length <= 0)
continue;
_writer.WriteLine(
"IL2CPP_END_CLEANUP(0x{0:X}, {1});",
targetInstruction1.Offset, _labeler.FormatOffset(fn1.First().Start));
}
_writer.WriteLine("IL2CPP_RETHROW_IF_UNHANDLED({0})", Globals.Naming.ForVariable(Globals.TypeProvider.SystemException));
_writer.EndBlock();
}
private void DumpInsFor(InstructionBlock block)
{
var ins = _stackAnalysis.InputStackStateFor(block);
if (ins.IsEmpty)
{
WriteComment("[in: -] empty");
return;
}
var entries = new List<Entry>(ins.Entries);
for (var index = 0; index < entries.Count; ++index)
{
var entry = entries[index];
WriteComment("[in: {0}] {1} (null: {2})", index, entry.Types.Select(t => t.FullName).AggregateWithComma(), entry.NullValue);
}
}
private void DumpOutsFor(InstructionBlock block)
{
var outs = _stackAnalysis.OutputStackStateFor(block);
if (outs.IsEmpty)
{
WriteComment("[out: -] empty");
return;
}
var entries = new List<Entry>(outs.Entries);
for (var index = 0; index < entries.Count; ++index)
{
var entry = entries[index];
WriteComment("[out: {0}] {1} (null: {2})", index, entry.Types.Select(t => t.FullName).AggregateWithComma(), entry.NullValue);
}
}
private void WriteComment(string message, params object[] args)
{
_writer.WriteLine("// {0}", string.Format(message, args));
}
private void WriteAssignment(string leftName, TypeReference leftType, StackInfo right)
{
_writer.WriteStatement(GetAssignment(leftName, leftType, right, _sharingType));
}
public static string GetAssignment(string leftName, TypeReference leftType, StackInfo right, SharingType sharingType = SharingType.NonShared)
{
return Emit.Assign(leftName, WriteExpressionAndCastIfNeeded(leftType, right, sharingType));
}
private static string WriteExpressionAndCastIfNeeded(TypeReference leftType, StackInfo right, SharingType sharingType = SharingType.NonShared)
{
if (leftType.MetadataType == MetadataType.Boolean && right.Type.IsIntegralType())
return EmitCastRightCastToLeftType(leftType, right);
if (leftType.IsPointer)
return EmitCastRightCastToLeftType(leftType, right);
if (leftType.IsIntegralPointerType())
{
if (right.Type.IsByReference || right.Type.IsPointer)
return EmitCastRightCastToLeftType(leftType, right);
}
if (leftType.IsGenericParameter())
return right.Expression;
if (right.Type.MetadataType == MetadataType.Object &&
leftType.MetadataType != MetadataType.Object)
return EmitCastRightCastToLeftType(leftType, right);
if (right.Type.IsArray && leftType.IsArray)
if (right.Type.FullName != leftType.FullName)
return EmitCastRightCastToLeftType(leftType, right);
if (right.Type.MetadataType == MetadataType.IntPtr && leftType.MetadataType == MetadataType.Int32)
{
return string.Format("({0})({1}){2}", Globals.Naming.ForVariable(leftType), WellKnown.VariableNames.IntPtr, right.Expression);
}
if (right.Type.IsIntegralPointerType() && leftType.MetadataType == MetadataType.IntPtr)
{
return EmitCastRightCastToLeftType(leftType, right);
}
if (right.Type.IsPointer && leftType.IsIntegralPointerType())
{
return EmitCastRightCastToLeftType(leftType, right);
}
if (leftType.Resolve().IsSubclassOf(right.Type.Resolve()))
{
return EmitCastRightCastToLeftType(leftType, right);
}
var leftTypeByRef = leftType as ByReferenceType;
if (leftTypeByRef != null)
{
if ((leftTypeByRef.ElementType.IsIntegralPointerType() || leftTypeByRef.ElementType.MetadataType.IsPrimitiveType()) && right.Type == Globals.TypeProvider.SystemIntPtr)
return EmitCastRightCastToLeftType(leftType, right);
}
// The cast is needed to suppress a compiler warning about truncation (when assigning, let's say, 16-bit integer to an 8-bit integer)
if (leftType.IsIntegralType() && right.Type.IsIntegralType() && GetMetadataTypeOrderFor(leftType) < GetMetadataTypeOrderFor(right.Type))
return EmitCastRightCastToLeftType(leftType, right);
// cast because in shared methods with constraints, the target may not be the correct type but it's still safe
if (sharingType == SharingType.Shared)
return EmitCastRightCastToLeftType(leftType, right);
if (CodeGenOptions.MonoRuntime && (leftType.IsSystemObject() || leftType.IsInterface()) && (right.Type.IsArray || right.Type.IsSystemArray()))
return EmitCastRightCastToLeftType(leftType, right);
if (!VarianceSupport.IsNeededForConversion(leftType, right.Type))
return right.Expression;
return string.Format("{0}{1}", VarianceSupport.Apply(leftType, right.Type), right.Expression);
}
private static string EmitCastRightCastToLeftType(TypeReference leftType, StackInfo right)
{
return string.Format("({0}){1}", Globals.Naming.ForVariable(leftType), right.Expression);
}
private SequencePoint GetSequencePoint(Instruction ins)
{
return ins.GetSequencePoint(_methodDefinition);
}
private void ProcessInstruction(ExceptionSupport.Node node, InstructionBlock block, ref Instruction ins)
{
ErrorInformation.CurrentlyProcessing.Instruction = ins;
switch (ins.OpCode.Code)
{
case Code.Nop:
break;
case Code.Break:
break;
case Code.Ldarg_0:
WriteLdarg(0, block, ins);
break;
case Code.Ldarg_1:
WriteLdarg(1, block, ins);
break;
case Code.Ldarg_2:
WriteLdarg(2, block, ins);
break;
case Code.Ldarg_3:
WriteLdarg(3, block, ins);
break;
case Code.Ldloc_0:
WriteLdloc(0, block, ins);
break;
case Code.Ldloc_1:
WriteLdloc(1, block, ins);
break;
case Code.Ldloc_2:
WriteLdloc(2, block, ins);
break;
case Code.Ldloc_3:
WriteLdloc(3, block, ins);
break;
case Code.Stloc_0:
WriteStloc(0);
break;
case Code.Stloc_1:
WriteStloc(1);
break;
case Code.Stloc_2:
WriteStloc(2);
break;
case Code.Stloc_3:
WriteStloc(3);
break;
case Code.Ldarg_S:
{
var parameterReference = (ParameterReference)ins.Operand;
var index = parameterReference.Index;
if (_methodDefinition.HasThis)
index++;
WriteLdarg(index, block, ins);
}
break;
case Code.Ldarga_S:
LoadArgumentAddress((ParameterReference)ins.Operand);
break;
case Code.Starg_S:
StoreArg(ins);
break;
case Code.Ldloc:
case Code.Ldloc_S:
{
var variableReference = (VariableReference)ins.Operand;
WriteLdloc(variableReference.Index, block, ins);
}
break;
case Code.Ldloca_S:
LoadLocalAddress((VariableReference)ins.Operand);
break;
case Code.Stloc:
case Code.Stloc_S:
{
var variableReference = (VariableReference)ins.Operand;
WriteStloc(variableReference.Index);
}
break;
case Code.Ldnull:
LoadNull();
break;
case Code.Ldc_I4_M1:
LoadInt32Constant(-1);
break;
case Code.Ldc_I4_0:
LoadInt32Constant(0);
break;
case Code.Ldc_I4_1:
LoadInt32Constant(1);
break;
case Code.Ldc_I4_2:
LoadInt32Constant(2);
break;
case Code.Ldc_I4_3:
LoadInt32Constant(3);
break;
case Code.Ldc_I4_4:
LoadInt32Constant(4);
break;
case Code.Ldc_I4_5:
LoadInt32Constant(5);
break;
case Code.Ldc_I4_6:
LoadInt32Constant(6);
break;
case Code.Ldc_I4_7:
LoadInt32Constant(7);
break;
case Code.Ldc_I4_8:
LoadInt32Constant(8);
break;
case Code.Ldc_I4_S:
LoadPrimitiveTypeSByte(ins, Int32TypeReference);
break;
case Code.Ldc_I4:
LoadPrimitiveTypeInt32(ins, Int32TypeReference);
break;
case Code.Ldc_I8:
LoadLong(ins, Globals.TypeProvider.Int64TypeReference);
break;
case Code.Ldc_R4:
LoadConstant(SingleTypeReference, Formatter.StringRepresentationFor((float)ins.Operand));
break;
case Code.Ldc_R8:
LoadConstant(DoubleTypeReference, Formatter.StringRepresentationFor((double)ins.Operand));
break;
case Code.Dup:
WriteDup();
break;
case Code.Pop:
_valueStack.Pop();
break;
case Code.Jmp:
throw new NotImplementedException();
case Code.Callvirt:
{
var methodToCall = (MethodReference)ins.Operand;
bool benchmarkMethod = BenchmarkSupport.BeginBenchmark(methodToCall, _writer);
WriteStoreStepOutSequencePoint(ins);
if (methodToCall.IsStatic())
throw new InvalidOperationException($"In method '{_methodReference.FullName}', an attempt to call the static method '{methodToCall.FullName}' with the callvirt opcode is not valid IL. Use the call opcode instead.");
// Add one to account for 'this' parameter
var poppedValues = PopItemsFromStack(methodToCall.Parameters.Count + 1, _valueStack);
string callExpression;
var suffix = "_" + ins.Offset;
string copyBackBoxedExpr = null;
var thisValue = poppedValues[0];
var ctorTypeRef = thisValue.ConstructorType;
TypeDefinition thisType = thisValue.Type.Resolve();
// TypeDefinition ctorType = null;
bool tryVirtualEx = false;
// if (ctorTypeRef != null)
// {
// ctorType = ctorTypeRef.Resolve();
// if (ctorType != null && !methodToCall.IsGenericInstance
// && !methodToCall.ContainsGenericParameter
// && ctorType.GenericParameters.Count() == 0
// && thisType.GenericParameters.Count() == 0
// && (ctorType == thisType || ctorType.IsSubclassOf(thisType)))
// {
// var overridesRef = VTableBuilder.CollectOverrides(thisType);
// MethodDefinition ovr;
// if (overridesRef.TryGetValue(methodToCall, out ovr))
// {
// ConsoleOutput.Info.WriteLine("Optimize " + methodToCall.FullName);
// tryVirtualEx = true;
// methodToCall = ovr ?? methodToCall;
// }
// else
// {
// var findMethod = ctorType.GetVirtualMethods().FirstOrDefault((reference) =>
// reference.Name == methodToCall.Name &&
// reference.ReturnType == methodToCall.ReturnType &&
// (reference.Parameters == methodToCall.Parameters || (reference.Parameters.Count() == 0 && methodToCall.Parameters.Count() == 0)) &&
// (reference.GenericParameters == methodToCall.GenericParameters || (reference.Parameters.Count() == 0 && methodToCall.Parameters.Count() == 0)));
// if (findMethod != null)
// {
// ConsoleOutput.Info.WriteLine("Optimize to Find " + methodToCall.FullName);
// tryVirtualEx = true;
// methodToCall = findMethod;
// }
// }
// }
// }
if (_constrainedCallThisType != null || thisValue.BoxedType != null)
callExpression = ConstrainedCallExpressionFor(ref methodToCall, MethodCallType.Virtual, poppedValues, s => s + suffix, out copyBackBoxedExpr);
else
callExpression = CallExpressionFor(_methodReference, methodToCall, tryVirtualEx ? MethodCallType.VirtualEx : MethodCallType.Virtual, poppedValues, s => s + suffix, !benchmarkMethod);
EmitCallExpressionAndStoreResult(ins, _typeResolver.ResolveReturnType(methodToCall), callExpression, copyBackBoxedExpr);
_constrainedCallThisType = null;
WriteCheckStepOutSequencePoint(ins);
BenchmarkSupport.EndBenchmark(benchmarkMethod, _writer);
}
break;
case Code.Call:
{
WriteStoreStepOutSequencePoint(ins);
if (_constrainedCallThisType != null)
throw new InvalidOperationException(string.Format(
"Constrained opcode was followed a Call rather than a Callvirt in method '{0}' at instruction '{1}'",
_methodReference.FullName,
ins));
var suffix = "_" + ins.Offset;
var methodToCall = (MethodReference)ins.Operand;
var callExpression = CallExpressionFor(_methodReference, methodToCall, MethodCallType.Normal, PopItemsFromStack(methodToCall.Parameters.Count + (methodToCall.HasThis ? 1 : 0), _valueStack), s => s + suffix);
EmitCallExpressionAndStoreResult(ins, _typeResolver.ResolveReturnType(methodToCall), callExpression, null);
WriteCheckStepOutSequencePoint(ins);
}
break;
case Code.Calli:
{
var site = ins.Operand as CallSite;
_writer.Write("typedef ");
_writer.Write(Globals.Naming.ForVariable(site.ReturnType));
var funcName = $"func_{SemiUniqueStableTokenGenerator.GenerateFor(site.FullName)}";
string callConvStr = "";
switch (site.CallingConvention)
{
case MethodCallingConvention.C:
callConvStr = "CDECL";
break;
case MethodCallingConvention.FastCall:
callConvStr = "FASTCALL";
break;
case MethodCallingConvention.StdCall:
callConvStr = "STDCALL";
break;
case MethodCallingConvention.ThisCall:
callConvStr = "THISCALL";
break;
}
_writer.Write(" ({0} *{1})(", callConvStr, funcName);
if (site.HasParameters)
{
for (int i = 0; i < site.Parameters.Count; ++i)
{
_writer.Write(Globals.Naming.ForVariable(site.Parameters[i].ParameterType));
if (i < site.Parameters.Count - 1)
_writer.Write(",");
}
}
_writer.WriteLine(");");
var funcPointer = _valueStack.Pop();
var callExpBuilder = new StringBuilder();
if (site.HasParameters)
{
var parameters = PopItemsFromStack(site.Parameters.Count, _valueStack);
callExpBuilder.AppendFormat("(({0}){1})(", funcName, funcPointer.Expression);
for (int i = 0; i < parameters.Count; ++i)
{
callExpBuilder.Append(Emit.Cast(site.Parameters[i].ParameterType, parameters[i].Expression));
if (i < parameters.Count - 1)
callExpBuilder.Append(",");
}
callExpBuilder.Append(")");
}
else
{
callExpBuilder.AppendFormat("(({0}){1})()", funcName, funcPointer.Expression);
}
EmitCallExpressionAndStoreResult(ins, site.ReturnType, callExpBuilder.ToString(), null);
}
break;
case Code.Ret:
WriteReturnStatement();
break;
case Code.Br:
case Code.Br_S:
WriteUnconditionalJumpTo(block, (Instruction)ins.Operand);
break;
case Code.Brfalse:
case Code.Brfalse_S:
GenerateConditionalJump(block, ins, false);
break;
case Code.Brtrue:
case Code.Brtrue_S:
GenerateConditionalJump(block, ins, true);
break;
case Code.Beq:
case Code.Beq_S:
GenerateConditionalJump(block, ins, "==", Signedness.Signed);
break;
case Code.Bge:
case Code.Bge_S:
GenerateConditionalJump(block, ins, ">=", Signedness.Signed);
break;
case Code.Bgt:
case Code.Bgt_S:
GenerateConditionalJump(block, ins, ">", Signedness.Signed);
break;
case Code.Ble:
case Code.Ble_S:
GenerateConditionalJump(block, ins, "<=", Signedness.Signed);
break;
case Code.Blt:
case Code.Blt_S:
GenerateConditionalJump(block, ins, "<", Signedness.Signed);
break;
case Code.Bne_Un:
case Code.Bne_Un_S:
GenerateConditionalJump(block, ins, "==", Signedness.Unsigned, true);
break;
case Code.Bge_Un:
case Code.Bge_Un_S:
GenerateConditionalJump(block, ins, "<", Signedness.Unsigned, true);
break;
case Code.Bgt_Un:
case Code.Bgt_Un_S:
GenerateConditionalJump(block, ins, "<=", Signedness.Unsigned, true);
break;
case Code.Ble_Un:
case Code.Ble_Un_S:
GenerateConditionalJump(block, ins, ">", Signedness.Unsigned, true);
break;
case Code.Blt_Un:
case Code.Blt_Un_S:
GenerateConditionalJump(block, ins, ">=", Signedness.Unsigned, true);
break;
case Code.Switch:
{
var value = _valueStack.Pop();
var targetInstructions = (Instruction[])ins.Operand;
var i = 0;
var successors = new List<InstructionBlock>(block.Successors);
var defaultSuccessor = successors.SingleOrDefault(b => !targetInstructions.Select(t => t.Offset).Contains(b.First.Offset));
if (defaultSuccessor != null)
{
successors.Remove(defaultSuccessor);
WriteAssignGlobalVariables(_stackAnalysis.InputVariablesFor(defaultSuccessor));
}
_writer.WriteLine($"switch ({value})");
using (NewBlock())
{
foreach (var targetInstruction in targetInstructions)
{
_writer.WriteLine($"case {i++}:");
using (NewBlock())
{
var successor = successors.First(b => b.First.Offset == targetInstruction.Offset);
WriteAssignGlobalVariables(_stackAnalysis.InputVariablesFor(successor));
WriteJump(targetInstruction);
}
}
}
}
break;
case Code.Ldind_I1:
LoadIndirect(SByteTypeReference, Int32TypeReference);
break;
case Code.Ldind_U1:
LoadIndirect(ByteTypeReference, Int32TypeReference);
break;
case Code.Ldind_I2:
LoadIndirect(Int16TypeReference, Int32TypeReference);
break;
case Code.Ldind_U2:
LoadIndirect(UInt16TypeReference, Int32TypeReference);
break;
case Code.Ldind_I4:
LoadIndirect(Int32TypeReference, Int32TypeReference);
break;
case Code.Ldind_U4:
LoadIndirect(UInt32TypeReference, Int32TypeReference);
break;
case Code.Ldind_I8:
LoadIndirect(Int64TypeReference, Int64TypeReference);
break;
case Code.Ldind_R4:
LoadIndirect(SingleTypeReference, SingleTypeReference);
break;
case Code.Ldind_R8:
LoadIndirect(DoubleTypeReference, DoubleTypeReference);
break;
case Code.Ldind_I:
LoadIndirectNativeInteger();
break;
case Code.Ldind_Ref:
LoadIndirectReference();
break;
case Code.Stind_Ref:
StoreIndirect(ObjectTypeReference);
break;
case Code.Stind_I1:
StoreIndirect(SByteTypeReference);
break;
case Code.Stind_I2:
StoreIndirect(Int16TypeReference);
break;
case Code.Stind_I4:
StoreIndirect(Int32TypeReference);
break;
case Code.Stind_I8:
StoreIndirect(Int64TypeReference);
break;
case Code.Stind_R4:
StoreIndirect(SingleTypeReference);
break;
case Code.Stind_R8:
StoreIndirect(DoubleTypeReference);
break;
case Code.Add:
WriteAdd(OverflowCheck.None);
break;
case Code.Sub:
WriteSub(OverflowCheck.None);
break;
case Code.Mul:
WriteMul(OverflowCheck.None);
break;
case Code.Div:
_divideByZeroCheckSupport.WriteDivideByZeroCheckIfNeeded(_valueStack.Peek());
WriteBinaryOperationUsingLargestOperandTypeAsResultType("/");
break;
case Code.Div_Un:
_divideByZeroCheckSupport.WriteDivideByZeroCheckIfNeeded(_valueStack.Peek());
WriteUnsignedArithmeticOperation("/");
break;
case Code.Rem:
WriteRemainderOperation();
break;
case Code.Rem_Un:
WriteUnsignedArithmeticOperation("%");
break;
case Code.And:
WriteBinaryOperationUsingLeftOperandTypeAsResultType("&");
break;
case Code.Or:
WriteBinaryOperationUsingLargestOperandTypeAsResultType("|");
break;
case Code.Xor:
WriteBinaryOperationUsingLargestOperandTypeAsResultType("^");
break;
case Code.Shl:
WriteBinaryOperationUsingLeftOperandTypeAsResultType("<<");
break;
case Code.Shr:
WriteBinaryOperationUsingLeftOperandTypeAsResultType(">>");
break;
case Code.Shr_Un:
WriteShrUn();
break;
case Code.Neg:
WriteNegateOperation();
break;
case Code.Not:
WriteNotOperation();
break;
case Code.Conv_I1:
WriteNumericConversion(SByteTypeReference);
break;
case Code.Conv_I2:
WriteNumericConversion(Int16TypeReference);
break;
case Code.Conv_I4:
WriteNumericConversion(Int32TypeReference);
break;
case Code.Conv_I8:
WriteNumericConversionI8();
break;
case Code.Conv_R4:
WriteNumericConversionFloat(SingleTypeReference);
break;
case Code.Conv_R8:
WriteNumericConversionFloat(DoubleTypeReference);
break;
case Code.Conv_U4:
WriteNumericConversion(UInt32TypeReference, Int32TypeReference);
break;
case Code.Conv_U8:
WriteNumericConversionU8();
break;
case Code.Cpobj:
{
var src = _valueStack.Pop();
var dest = _valueStack.Pop();
if (dest.Type.IsPointer)
{
var srcTypeForSize = ((TypeSpecification)src.Type).ElementType;
_writer.WriteLine($"il2cpp_codegen_memcpy({dest}, {src}, sizeof({Globals.Naming.ForVariable(srcTypeForSize)}));");
}
else
{
_writer.WriteStatement(Emit.Assign(Emit.Dereference(dest.Expression), Emit.Dereference(src.Expression)));
}
}
break;
case Code.Ldobj:
WriteLoadObject(ins, block);
break;
case Code.Ldstr:
{
var literalString = (string)ins.Operand;
MetadataToken token;
_methodDefinition.Body.GetInstructionToken(ins, out token);
_writer.AddIncludeForTypeDefinition(StringTypeReference);
_valueStack.Push(new StackInfo(_runtimeMetadataAccess.StringLiteral(literalString, token, _methodDefinition.Module.Assembly), StringTypeReference));
}
break;
case Code.Newobj:
{
WriteStoreStepOutSequencePoint(ins);
var method1 = (MethodReference)ins.Operand;
var method = _typeResolver.Resolve(method1);
var declaringType = _typeResolver.Resolve(method.DeclaringType);
var variable = NewTemp(declaringType);
_writer.AddIncludeForTypeDefinition(_typeResolver.Resolve(method1.DeclaringType));
var genericInstanceType = _methodReference.DeclaringType as GenericInstanceType;
if (genericInstanceType != null && !(method is GenericInstanceMethod))
method = TypeResolver.For(genericInstanceType).Resolve(method);
List<StackInfo> arguments;
List<TypeReference> parameterTypes;
parameterTypes = GetParameterTypes(method, _typeResolver);
arguments = PopItemsFromStack(parameterTypes.Count, _valueStack);
var argsFor = FormatArgumentsForMethodCall(parameterTypes, arguments, _sharingType);
if (declaringType.IsArray)
{
var arrayType = (ArrayType)declaringType;
if (arrayType.Rank < 2)
throw new NotImplementedException("Attempting to create a multidimensional array of rank lesser than 2");
var arrayLengths = NewTempName();
string genArrayNewInvocation = !CodeGenOptions.UseTinyRuntimeBackend
? Emit.Call("GenArrayNew", _runtimeMetadataAccess.TypeInfoFor(method1.DeclaringType), arrayLengths)
: Emit.Call("GenArrayNew", _runtimeMetadataAccess.TypeInfoFor(method1.DeclaringType), $"sizeof({Globals.Naming.ForVariable(arrayType.ElementType)})", arrayLengths);
_writer.WriteLine("{0} {1}[] = {{ {2} }};", ArrayNaming.ForArrayIndexType(), arrayLengths, Emit.CastEach(ArrayNaming.ForArrayIndexType(), argsFor).AggregateWithComma());
_writer.WriteLine("{0};", Emit.Assign(variable.IdentifierExpression, Emit.Cast(arrayType, genArrayNewInvocation)));
}
else if (declaringType.IsValueType())
{
var thisArg = Emit.AddressOf(variable.Expression);
argsFor.Insert(0, thisArg);
if (MethodSignatureWriter.NeedsHiddenMethodInfo(method, MethodCallType.Normal, true))
argsFor.Add((CodeGenOptions.EmitComments ? "/*hidden argument*/" : "") + _runtimeMetadataAccess.HiddenMethodInfo(method1));
_writer.WriteVariable(declaringType, variable.Expression);
_writer.WriteStatement(Emit.Call(_runtimeMetadataAccess.Method(method), argsFor));
}
else if (method.Name == ".ctor" && declaringType.MetadataType == MetadataType.String)
{
// pass NULL as 'this' pointer. These creation methods return the string and don't use the 'this' argument
argsFor.Insert(0, WellKnown.Constants.Null);
var suffix = "_" + ins.Offset;
var methodToCall = GetCreateStringMethod(method);
string callExpression;
if (Globals.Profile != Profile.UnityTiny)
{
callExpression = CallExpressionFor(_methodReference, methodToCall, MethodCallType.Normal, argsFor, s => s + suffix, emitNullCheckForInvocation: false);
}
else
{
callExpression = CallExpressionFor(_methodReference, methodToCall, MethodCallType.Normal, arguments, s => s + suffix, emitNullCheckForInvocation: false);
}
_writer.WriteStatement(Emit.Assign(variable.IdentifierExpression, callExpression));
}
else
{
if (CanOptimizeAwayDelegateAllocation(ins, method))
{
var stackVariable = NewTemp(declaringType);
_writer.WriteStatement($"{Globals.Naming.ForTypeNameOnly(declaringType)} {stackVariable.Expression}");
_writer.WriteStatement(Emit.Memset(Emit.AddressOf(stackVariable.Expression), 0, $"sizeof({stackVariable.Expression})"));
_writer.WriteStatement(Emit.Assign(variable.IdentifierExpression, Emit.AddressOf(stackVariable.Expression)));
}
else
{
if (!CodeGenOptions.UseTinyRuntimeBackend)
{
_writer.WriteStatement(Emit.Assign(variable.IdentifierExpression, Emit.Cast(declaringType, Emit.Call("il2cpp_codegen_object_new", _runtimeMetadataAccess.Newobj(method1)))));
}
else
{
_writer.WriteStatement(Emit.Assign(variable.IdentifierExpression, Emit.Cast(declaringType, Emit.Call("il2cpp_codegen_object_new", $"sizeof({Globals.Naming.ForTypeNameOnly(declaringType)})", _runtimeMetadataAccess.TypeInfoFor(declaringType)))));
}
}
if (CodeGenOptions.MonoRuntime)
WriteCallToClassAndInitializerAndStaticConstructorIfNeeded(declaringType, _methodDefinition, _runtimeMetadataAccess);
// add newly allocated object as the 'this' parameter
argsFor.Insert(0, variable.Expression);
if (MethodSignatureWriter.NeedsHiddenMethodInfo(method, MethodCallType.Normal, true))
argsFor.Add((CodeGenOptions.EmitComments ? "/*hidden argument*/" : "") + _runtimeMetadataAccess.HiddenMethodInfo(method1));
var def = _runtimeMetadataAccess.MethodDef(method1);
variable = new StackInfo(variable, constructorType: def.DeclaringType);
_writer.WriteStatement(Emit.Call(_runtimeMetadataAccess.Method(method1), argsFor));
if (method.DeclaringType.IsDelegate() && method.Name == ".ctor" && Globals.Profile == Profile.UnityTiny)
{
var methodDef = method.DeclaringType.Resolve().Methods.Single(m => m.IsConstructor);
method = TypeResolver.For(method.DeclaringType, method).Resolve(methodDef);
EmitTinyDelegateFieldSetters(_writer, method, variable, arguments);
}
}
_valueStack.Push(new StackInfo(variable));
WriteCheckStepOutSequencePoint(ins);
}
break;
case Code.Castclass:
WriteCastclassOrIsInst((TypeReference)ins.Operand, _valueStack.Pop(), "Castclass");
break;
case Code.Isinst:
WriteCastclassOrIsInst((TypeReference)ins.Operand, _valueStack.Pop(), "IsInst");
break;
case Code.Conv_R_Un:
WriteNumericConversionToFloatFromUnsigned();
break;
case Code.Unbox:
Unbox(ins);
break;
case Code.Throw:
{
var exc = _valueStack.Pop();
if (CodeGenOptions.EnableDebugger)
_writer.WriteStatement(Emit.RaiseManagedException(exc.ToString(),
_runtimeMetadataAccess.MethodInfo(_methodReference)));
else
_writer.WriteStatement(Emit.RaiseManagedException(exc.ToString(),
_runtimeMetadataAccess.MethodInfo(_methodReference)));
}
break;
case Code.Ldfld:
LoadField(ins);
break;
case Code.Ldflda:
LoadField(ins, true);
break;
case Code.Stfld:
StoreField(ins);
break;
case Code.Ldsflda:
case Code.Ldsfld:
case Code.Stsfld:
StaticFieldAccess(ins);
break;
case Code.Stobj:
WriteStoreObject((TypeReference)ins.Operand);
break;
case Code.Conv_Ovf_I1_Un:
WriteNumericConversionWithOverflow(ByteTypeReference, true, AppendLongLongLiteralSuffix(sbyte.MaxValue));
break;
case Code.Conv_Ovf_I2_Un:
WriteNumericConversionWithOverflow(Int16TypeReference, true, AppendLongLongLiteralSuffix(Int16.MaxValue));
break;
case Code.Conv_Ovf_I4_Un:
WriteNumericConversionWithOverflow(Int32TypeReference, true, AppendLongLongLiteralSuffix(Int32.MaxValue));
break;
case Code.Conv_Ovf_I8_Un:
WriteNumericConversionWithOverflow(Int64TypeReference, true, AppendLongLongLiteralSuffix(Int64.MaxValue));
break;
case Code.Conv_Ovf_U1_Un:
WriteNumericConversionWithOverflow(ByteTypeReference, true, AppendLongLongLiteralSuffix(byte.MaxValue));
break;
case Code.Conv_Ovf_U2_Un:
WriteNumericConversionWithOverflow(UInt16TypeReference, true, AppendLongLongLiteralSuffix(UInt16.MaxValue));
break;
case Code.Conv_Ovf_U4_Un:
WriteNumericConversionWithOverflow(UInt32TypeReference, true, AppendLongLongLiteralSuffix(UInt32.MaxValue));
break;
case Code.Conv_Ovf_U8_Un:
WriteNumericConversionWithOverflow(UInt64TypeReference, true, UInt64.MaxValue + "ULL");
break;
case Code.Conv_Ovf_I_Un:
ConvertToNaturalIntWithOverflow(SystemIntPtr, true, "INTPTR_MAX");
break;
case Code.Conv_Ovf_U_Un:
ConvertToNaturalIntWithOverflow(SystemUIntPtr, true, "UINTPTR_MAX");
break;
case Code.Box:
{
var unresolvedTypeReference = (TypeReference)ins.Operand;
var typeReference = _typeResolver.Resolve(unresolvedTypeReference);
_writer.AddIncludeForTypeDefinition(typeReference);
// do nothing for reference types
if (typeReference.IsValueType())
{
var valueExpr = _valueStack.Pop();
// Optimization: for common construct in generic code where T is a value type
// box !T
// brtrue or brfalse
if (CanApplyValueTypeBoxBranchOptimizationToInstruction(ins, block))
{
// brtrue is always true for boxed value type, unconditional branch
// brfalse is always false, fall through
var next = ins.Next;
if (next.OpCode.Code == Code.Brtrue || next.OpCode.Code == Code.Brtrue_S)
{
if (BothSuccessorsAreTheSame(block))
WriteGlobalVariableAssignmentForFirstSuccessor(block, (Instruction)next.Operand);
else
WriteGlobalVariableAssignmentForRightBranch(block, (Instruction)next.Operand);
WriteJump((Instruction)next.Operand);
}
if (BothSuccessorsAreTheSame(block))
WriteGlobalVariableAssignmentForFirstSuccessor(block, (Instruction)next.Operand);
else
WriteGlobalVariableAssignmentForLeftBranch(block, (Instruction)next.Operand);
ins = next;
break;
}
// Workaround for boxing native int as intptr
bool leftIsIntPtr = typeReference.MetadataType == MetadataType.IntPtr ||
typeReference.MetadataType == MetadataType.UIntPtr;
bool rightIsNativeInt = valueExpr.Type.IsSameType(SystemIntPtr) || valueExpr.Type.IsSameType(SystemUIntPtr);
if (leftIsIntPtr && rightIsNativeInt)
typeReference = valueExpr.Type;
// TODO: we only need this temporary because we incorrectly load types onto the evaluation stack
// For example, we don't sign extend smaller integer types to 32-bit ints.
// The IL execution stack only allows for int32 and int64 interger types.
// This means loading a short and then boxing as an int32 is valid since the short was converted to int32 upon load.
// We currently don't convert to int32 on load, meaning out box will fail since the target type size != actual value size
// Once we fully support the proper execution stack sizes and behaviour we can remove this temporary
var variable = NewTemp(typeReference);
_writer.WriteLine("{0} = {1};", variable.IdentifierExpression, CastTypeIfNeeded(valueExpr, typeReference));
if (CodeGenOptions.UseTinyRuntimeBackend && typeReference.IsNullable())
{
unresolvedTypeReference = ((GenericInstanceType)typeReference).GenericArguments[0];
StoreLocalAndPush(ObjectTypeReference, string.Format("BoxNullable<{0}, {1}>({2}, &{3})", Globals.Naming.ForVariable(typeReference), Globals.Naming.ForVariable(unresolvedTypeReference), _runtimeMetadataAccess.TypeInfoFor((TypeReference)ins.Operand), variable.Expression), boxedType: unresolvedTypeReference);
}
else
{
StoreLocalAndPush(ObjectTypeReference, string.Format("Box({0}, &{1})", _runtimeMetadataAccess.TypeInfoFor((TypeReference)ins.Operand), variable.Expression), boxedType: unresolvedTypeReference);
}
}
}
break;
case Code.Newarr:
{
var elementCount = _valueStack.Pop();
var elementType = _typeResolver.Resolve((TypeReference)ins.Operand);
var arrayType = new ArrayType(elementType);
_writer.AddIncludeForTypeDefinition(arrayType);
// This case should not be necessary if/when we get proper stack behavior.
var elementCountExpression = string.Format("(uint32_t){0}", elementCount.Expression);
StoreLocalAndPush(arrayType, Emit.Cast(arrayType, Emit.NewSZArray(arrayType, (TypeReference)ins.Operand, elementCountExpression, _runtimeMetadataAccess)));
}
break;
case Code.Ldlen:
{
var stackInfo = _valueStack.Pop();
_nullCheckSupport.WriteNullCheckIfNeeded(stackInfo);
PushExpression(UInt32TypeReference, $"(({WellKnown.TypeNames.Array}*){stackInfo})->max_length");
}
break;
case Code.Ldelema:
{
var index = _valueStack.Pop();
var array = _valueStack.Pop();
var elementType = _typeResolver.Resolve((TypeReference)ins.Operand);
var byReferenceType = new ByReferenceType(elementType);
_nullCheckSupport.WriteNullCheckIfNeeded(array);
PushExpression(byReferenceType, EmitArrayLoadElementAddress(array, index.Expression));
}
break;
case Code.Ldelem_I1:
LoadElemAndPop(SByteTypeReference);
break;
case Code.Ldelem_U1:
LoadElemAndPop(ByteTypeReference);
break;
case Code.Ldelem_I2:
LoadElemAndPop(Int16TypeReference);
break;
case Code.Ldelem_U2:
LoadElemAndPop(UInt16TypeReference);
break;
case Code.Ldelem_I4:
LoadElemAndPop(Int32TypeReference);
break;
case Code.Ldelem_U4:
LoadElemAndPop(UInt32TypeReference);
break;
case Code.Ldelem_I8:
LoadElemAndPop(Int64TypeReference);
break;
case Code.Ldelem_I:
LoadElemAndPop(IntPtrTypeReference);
break;
case Code.Ldelem_R4:
LoadElemAndPop(SingleTypeReference);
break;
case Code.Ldelem_R8:
LoadElemAndPop(DoubleTypeReference);
break;
case Code.Ldelem_Ref:
var index1 = _valueStack.Pop();
var array1 = _valueStack.Pop();
LoadElem(array1, ArrayUtilities.ArrayElementTypeOf(array1.Type), index1);
break;
case Code.Stelem_I:
case Code.Stelem_I1:
case Code.Stelem_I2:
case Code.Stelem_I4:
case Code.Stelem_I8:
case Code.Stelem_R4:
case Code.Stelem_R8:
case Code.Stelem_Any:
{
var value = _valueStack.Pop();
var index = _valueStack.Pop();
var array = _valueStack.Pop();
StoreElement(array, index, value, false);
}
break;
case Code.Ldelem_Any:
LoadElemAndPop(_typeResolver.Resolve((TypeReference)ins.Operand));
break;
case Code.Stelem_Ref:
{
var value = _valueStack.Pop();
var index = _valueStack.Pop();
var array = _valueStack.Pop();
StoreElement(array, index, value, true);
}
break;
case Code.Unbox_Any:
{
var boxedValue = _valueStack.Pop();
var type = _typeResolver.Resolve((TypeReference)ins.Operand);
_writer.AddIncludeForTypeDefinition(type);
if (type.IsValueType())
{
var byReferenceType = new ByReferenceType(type);
PushExpression(byReferenceType, Emit.Cast(new PointerType(type), Unbox((TypeReference)ins.Operand, boxedValue)));
var address = _valueStack.Pop();
PushExpression(type, Emit.InParentheses(Emit.Dereference(Emit.Cast(byReferenceType, address.Expression))));
}
else
{
WriteCastclassOrIsInst((TypeReference)ins.Operand, boxedValue, "Castclass");
}
}
break;
case Code.Conv_Ovf_I1:
WriteNumericConversionWithOverflow(SByteTypeReference, false, AppendLongLongLiteralSuffix(sbyte.MaxValue));
break;
case Code.Conv_Ovf_U1:
WriteNumericConversionWithOverflow(ByteTypeReference, false, AppendLongLongLiteralSuffix(byte.MaxValue));
break;
case Code.Conv_Ovf_I2:
WriteNumericConversionWithOverflow(Int16TypeReference, false, AppendLongLongLiteralSuffix(Int16.MaxValue));
break;
case Code.Conv_Ovf_U2:
WriteNumericConversionWithOverflow(UInt16TypeReference, false, AppendLongLongLiteralSuffix(UInt16.MaxValue));
break;
case Code.Conv_Ovf_I4:
WriteNumericConversionWithOverflow(Int32TypeReference, false, AppendLongLongLiteralSuffix(Int32.MaxValue));
break;
case Code.Conv_Ovf_U4:
WriteNumericConversionWithOverflow(UInt32TypeReference, false, AppendLongLongLiteralSuffix(UInt32.MaxValue));
break;
case Code.Conv_Ovf_I8:
WriteNumericConversionWithOverflow(Int64TypeReference, false, "(std::numeric_limits<int64_t>::max)()");
break;
case Code.Conv_Ovf_U8:
WriteNumericConversionWithOverflow(UInt64TypeReference, true, "(std::numeric_limits<uint64_t>::max)()");
break;
case Code.Refanyval:
throw new NotImplementedException();
case Code.Ckfinite:
throw new NotImplementedException();
case Code.Mkrefany:
_writer.WriteLine("#pragma message(FIXME \"mkrefany is not supported\")");
_writer.WriteLine("assert(false && \"mkrefany is not supported\");");
StoreLocalAndPush(Globals.TypeProvider.RuntimeArgumentHandleTypeReference, "{ 0 }");
break;
case Code.Ldtoken:
EmitLoadToken(ins);
break;
case Code.Conv_U2:
WriteNumericConversion(UInt16TypeReference, Int32TypeReference);
break;
case Code.Conv_U1:
WriteNumericConversion(ByteTypeReference, Int32TypeReference);
break;
case Code.Conv_I:
ConvertToNaturalInt(SystemIntPtr);
break;
case Code.Conv_Ovf_I:
ConvertToNaturalIntWithOverflow(SystemIntPtr, false, "INTPTR_MAX");
break;
case Code.Conv_Ovf_U:
ConvertToNaturalIntWithOverflow(SystemUIntPtr, false, "UINTPTR_MAX");
break;
case Code.Add_Ovf:
WriteAdd(OverflowCheck.Signed);
break;
case Code.Add_Ovf_Un:
WriteAdd(OverflowCheck.Unsigned);
break;
case Code.Mul_Ovf:
WriteMul(OverflowCheck.Signed);
break;
case Code.Mul_Ovf_Un:
WriteMul(OverflowCheck.Unsigned);
break;
case Code.Sub_Ovf:
WriteSub(OverflowCheck.Signed);
break;
case Code.Sub_Ovf_Un:
WriteSub(OverflowCheck.Unsigned);
break;
case Code.Endfinally:
var finallyOrFaultNode = node.GetEnclosingFinallyOrFaultNode();
_writer.WriteLine("IL2CPP_END_FINALLY({0})", finallyOrFaultNode.Start.Offset);
break;
case Code.Leave_S:
case Code.Leave:
// Sometimes the compiler emits two leave instructions, one after the other one.
// As long as this might create decompilation issues (usually the second leave
// instruction points to random places), we remove the second one in case it was not
// the target of a jump.
if (ShouldStripLeaveInstruction(block, ins))
{
_writer.WriteLine("; // {0}", ins);
break;
}
// _writer.WriteLine("// {0}", ins);
switch (node.Type)
{
case ExceptionSupport.NodeType.Try:
EmitCodeForLeaveFromTry(node, ins);
break;
case ExceptionSupport.NodeType.Catch:
EmitCodeForLeaveFromCatch(node, ins);
break;
case ExceptionSupport.NodeType.Finally:
case ExceptionSupport.NodeType.Fault:
EmitCodeForLeaveFromFinallyOrFault(ins);
break;
case ExceptionSupport.NodeType.Root:
case ExceptionSupport.NodeType.Block:
EmitCodeForLeaveFromBlock(node, ins);
break;
}
// NOTE(gab): not matter what, we need to make sure the value stack is emptied after Leave is executed.
_valueStack.Clear();
break;
case Code.Stind_I:
StoreIndirect(SystemIntPtr);
break;
case Code.Conv_U:
ConvertToNaturalInt(Globals.TypeProvider.SystemUIntPtr);
break;
case Code.Arglist:
_writer.WriteLine("#pragma message(FIXME \"arglist is not supported\")");
_writer.WriteLine("assert(false && \"arglist is not supported\");");
StoreLocalAndPush(Globals.TypeProvider.RuntimeArgumentHandleTypeReference, "{ 0 }");
break;
case Code.Ceq:
GenerateConditional("==", Signedness.Signed);
break;
case Code.Cgt:
GenerateConditional(">", Signedness.Signed);
break;
case Code.Cgt_Un:
GenerateConditional("<=", Signedness.Unsigned, true);
break;
case Code.Clt:
GenerateConditional("<", Signedness.Signed);
break;
case Code.Clt_Un:
GenerateConditional(">=", Signedness.Unsigned, true);
break;
case Code.Ldftn:
PushCallToLoadFunction((MethodReference)ins.Operand);
break;
case Code.Ldvirtftn:
LoadVirtualFunction(ins);
break;
case Code.Ldarg:
{
var parameterReference = (ParameterReference)ins.Operand;
var index = parameterReference.Index;
if (_methodDefinition.HasThis)
index++;
WriteLdarg(index, block, ins);
}
break;
case Code.Ldarga:
LoadArgumentAddress((ParameterReference)ins.Operand);
break;
case Code.Starg:
StoreArg(ins);
break;
case Code.Ldloca:
LoadLocalAddress((VariableReference)ins.Operand);
break;
case Code.Localloc:
{
var size = _valueStack.Pop();
var charPtr = new PointerType(SByteTypeReference);
var allocatedMem = NewTempName();
_writer.WriteLine(string.Format("{0} {1} = ({0}) alloca({2});", Globals.Naming.ForVariable(charPtr), allocatedMem, size));
if (_methodDefinition.Body.InitLocals)
_writer.WriteStatement(Emit.Memset(allocatedMem, 0, size.Expression));
PushExpression(charPtr, allocatedMem);
}
break;
case Code.Endfilter:
{
var val = _valueStack.Pop();
_writer.WriteLine($"{ExceptionSupport.LocalFilterName} = ({val}) ? true : false;");
}
break;
case Code.Unaligned:
// TODO
break;
case Code.Volatile:
AddVolatileStackEntry();
break;
case Code.Tail:
Globals.StatsService.RecordTailCall(_methodDefinition);
break;
case Code.Initobj:
{
var val = _valueStack.Pop();
var type = _typeResolver.Resolve((TypeReference)ins.Operand);
_writer.AddIncludeForTypeDefinition(_typeResolver.Resolve(type));
_writer.WriteLine($"il2cpp_codegen_initobj({val.Expression}, sizeof({Globals.Naming.ForVariable(type)}));");
}
break;
case Code.Constrained:
_constrainedCallThisType = (TypeReference)ins.Operand;
_writer.AddIncludeForTypeDefinition(_typeResolver.Resolve(_constrainedCallThisType));
break;
case Code.Cpblk:
{
var bytes = _valueStack.Pop();
var src = _valueStack.Pop();
var srcCast = string.Empty;
if (src.Type.MetadataType == MetadataType.IntPtr || src.Type.MetadataType == MetadataType.UIntPtr)
srcCast = "(const void*)";
var dst = _valueStack.Pop();
var dstCast = string.Empty;
if (dst.Type.MetadataType == MetadataType.IntPtr || dst.Type.MetadataType == MetadataType.UIntPtr)
dstCast = "(void*)";
_writer.WriteLine($"il2cpp_codegen_memcpy({dstCast}{dst}, {srcCast}{src}, {bytes});");
}
break;
case Code.Initblk:
{
var bytes = _valueStack.Pop();
var val = _valueStack.Pop();
var ptr = _valueStack.Pop();
_writer.WriteLine($"il2cpp_codegen_memset({ptr}, {val}, {bytes});");
}
break;
case Code.No:
throw new NotImplementedException();
case Code.Rethrow:
{
if (node.Type == ExceptionSupport.NodeType.Finally)
{
_writer.WriteLine("{0} = {1};", ExceptionSupport.LastUnhandledExceptionName, ExceptionSupport.LocalExceptionName);
WriteJump(node.Handler.HandlerStart);
}
else
{
if (CodeGenOptions.EnableDebugger)
_writer.WriteStatement(Emit.RaiseManagedException((string)ExceptionSupport.LocalExceptionName,
_runtimeMetadataAccess.MethodInfo(_methodReference)));
else
_writer.WriteStatement(Emit.RaiseManagedException(ExceptionSupport.LocalExceptionName,
_runtimeMetadataAccess.MethodInfo(_methodReference)));
}
}
break;
case Code.Sizeof:
{
var typeReference = _typeResolver.Resolve((TypeReference)ins.Operand);
_writer.AddIncludeForTypeDefinition(typeReference);
StoreLocalAndPush(UInt32TypeReference, $"sizeof({Globals.Naming.ForVariable(typeReference)})");
}
break;
case Code.Refanytype:
var typeToken = _valueStack.Pop();
if (typeToken.Type.FullName != "System.TypedReference" && typeToken.Type.Resolve().Module.Name == "mscorlib")
throw new InvalidOperationException();
var field = typeToken.Type.Resolve().Fields.Single(f => TypeReferenceEqualityComparer.AreEqual(f.FieldType, RuntimeTypeHandleTypeReference));
var format = string.Format("{0}.{1}()", typeToken.Expression, Globals.Naming.ForFieldGetter(field));
_valueStack.Push(new StackInfo(format, RuntimeTypeHandleTypeReference));
break;
case Code.Readonly:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private static bool CanOptimizeAwayDelegateAllocation(Instruction ins, MethodReference method)
{
if (method.DeclaringType.IsDelegate())
{
var next = ins.Next;
if (next != null && (next.OpCode == OpCodes.Call || next.OpCode == OpCodes.Callvirt))
{
var nextMethod = next.Operand as GenericInstanceMethod;
if (nextMethod != null)
{
if (nextMethod.DeclaringType.FullName == "Unity.Entities.EntityQueryBuilder" && nextMethod.Name == "ForEach")
{
return true;
}
}
}
}
return false;
}
private static bool BothSuccessorsAreTheSame(InstructionBlock block)
{
if (block.Successors.Count() == 2)
{
if (block.Successors.First() == block.Successors.Last())
return true;
}
return false;
}
private void WriteCheckSequencePoint(SequencePoint sequencePoint)
{
if (!CodeGenOptions.EnableDebugger)
return;
var sequencePointInfo = _sequencePointProvider.GetSequencePointAt(_methodDefinition, sequencePoint.Offset, SequencePointKind.Normal);
WriteCheckSequencePoint(sequencePointInfo);
}
private string GetSequencePoint(SequencePointInfo sequencePointInfo)
{
int index = _sequencePointProvider.GetSeqPointIndex(sequencePointInfo);
var sequencePoints = DebugWriter.GetSequencePointName(_methodDefinition.Module.Assembly);
_writer.AddForwardDeclaration($"IL2CPP_EXTERN_C Il2CppSequencePoint {sequencePoints}[]");
return $"({sequencePoints} + {index})";
}
private void WriteCheckSequencePoint(SequencePointInfo sequencePointInfo)
{
if (!CodeGenOptions.EnableDebugger)
return;
if (sequencePointInfo.IlOffset == SequencePointInfo.MethodEntryIlOffset)
_writer.WriteLine($"CHECK_METHOD_ENTRY_SEQ_POINT({Globals.Naming.ForMethodExecutionContextVariable()}, {GetSequencePoint(sequencePointInfo)});");
else
_writer.WriteLine($"CHECK_SEQ_POINT({Globals.Naming.ForMethodExecutionContextVariable()}, {GetSequencePoint(sequencePointInfo)});");
}
private void WriteCheckMethodExitSequencePoint(SequencePointInfo sequencePointInfo)
{
if (!CodeGenOptions.EnableDebugger)
return;
_writer.WriteLine($"CHECK_METHOD_EXIT_SEQ_POINT({Globals.Naming.ForMethodExitSequencePointChecker()}, {Globals.Naming.ForMethodExecutionContextVariable()}, {GetSequencePoint(sequencePointInfo)});");
}
private void WriteCheckStepOutSequencePoint(Instruction ins)
{
if (!CodeGenOptions.EnableDebugger)
return;
if (_sequencePointProvider.TryGetSequencePointAt(_methodDefinition, ins.Offset, SequencePointKind.StepOut, out var sequencePoint))
_writer.WriteLine($"CHECK_SEQ_POINT({Globals.Naming.ForMethodExecutionContextVariable()}, {GetSequencePoint(sequencePoint)});");
}
private void WriteStoreStepOutSequencePoint(Instruction ins)
{
if (!CodeGenOptions.EnableDebugger)
return;
if (_sequencePointProvider.TryGetSequencePointAt(_methodDefinition, ins.Offset, SequencePointKind.StepOut, out var sequencePoint))
_writer.WriteLine(
$"STORE_SEQ_POINT({Globals.Naming.ForMethodExecutionContextVariable()}, {GetSequencePoint(sequencePoint)});");
}
private void WriteCheckPausePoint(int offset)
{
if (!CodeGenOptions.EnableDebugger)
return;
if (_sequencePointProvider.MethodHasPausePointAtOffset(_methodDefinition, offset))
_writer.WriteLine("CHECK_PAUSE_POINT;");
}
private void WriteStoreTryId(ExceptionSupport.Node node)
{
if (!CodeGenOptions.EnableDebugger)
return;
_writer.WriteLine($"STORE_TRY_ID({Globals.Naming.ForMethodExecutionContextVariable()}, {node?.Id ?? -1});");
}
static string AppendLongLongLiteralSuffix<T>(T value)
{
return $"{value}LL";
}
private void WriteNumericConversionToFloatFromUnsigned()
{
var top = _valueStack.Peek();
// For floating point types, maintain the same type as the stack type. For integer types, load the type
// as unsigned with the appropriate length.
if (top.Type.MetadataType == MetadataType.Single || top.Type.MetadataType == MetadataType.Double)
WriteNumericConversion(top.Type, DoubleTypeReference);
else if (top.Type.MetadataType == MetadataType.Int64 || top.Type.MetadataType == MetadataType.UInt64)
WriteNumericConversion(UInt64TypeReference, DoubleTypeReference);
else
WriteNumericConversion(UInt32TypeReference, DoubleTypeReference);
}
private void WriteReturnStatement()
{
var returnType = _typeResolver.ResolveReturnType(_methodDefinition);
if (returnType.MetadataType == MetadataType.Void && _valueStack.Count > 0 ||
returnType.MetadataType != MetadataType.Void && _valueStack.Count > 1)
{
throw new InvalidOperationException(
string.Format("Attempting to return a value from method '{0}' when there is no value on the stack. Is this invalid IL code?", _methodDefinition.FullName));
}
if (returnType.MetadataType != MetadataType.Void)
{
var returnValue = _valueStack.Pop();
var cast = CastIfPointerType(returnType);
if (returnType.FullName != returnValue.Type.FullName && returnType.Resolve() != null && returnType.Resolve().IsEnum)
{
_writer.WriteLine("return ({0})({1});", Globals.Naming.ForVariable(returnType), returnValue.Expression);
}
else if (!string.IsNullOrEmpty(cast))
{
_writer.WriteLine("return {0}({1});", cast, returnValue.Expression);
}
else
{
_writer.WriteLine("return {0};", WriteExpressionAndCastIfNeeded(returnType, returnValue));
}
}
else
{
_writer.WriteLine("return;");
}
}
private bool CanApplyValueTypeBoxBranchOptimizationToInstruction(Instruction ins, InstructionBlock block)
{
if (ins != null && ins.OpCode.Code == Code.Box)
{
var typeReference = _typeResolver.Resolve((TypeReference)ins.Operand);
return typeReference.IsValueType() && !typeReference.IsNullable() &&
ins != block.Last && ins.Next != null &&
(ins.Next.OpCode.Code == Code.Brtrue || ins.Next.OpCode.Code == Code.Brtrue_S ||
ins.Next.OpCode.Code == Code.Brfalse || ins.Next.OpCode.Code == Code.Brfalse_S);
}
return false;
}
private string ConstrainedCallExpressionFor(ref MethodReference methodToCall, MethodCallType callType, List<StackInfo> poppedValues, Func<string, string> addUniqueSuffix, out string copyBackBoxedExpr)
{
var resolvedMethodToCall = _typeResolver.Resolve(methodToCall);
var typeResolverForMethodToCall = _typeResolver;
var genericInstanceMethod = resolvedMethodToCall as GenericInstanceMethod;
if (genericInstanceMethod != null)
typeResolverForMethodToCall = _typeResolver.Nested(genericInstanceMethod);
var thisValue = poppedValues[0];
TypeReference thisType = null;
bool shouldUnbox = false;
TypeReference unresolvedConstrainedType = _constrainedCallThisType;
if (thisValue.Type is ByReferenceType byReferenceType)
{
thisType = byReferenceType.ElementType.WithoutModifiers();
}
else if (thisValue.Type is PointerType pointerType)
{
thisType = pointerType.ElementType.WithoutModifiers();
}
else if (thisValue.BoxedType != null)
{
shouldUnbox = true;
unresolvedConstrainedType = thisValue.BoxedType;
thisType = _typeResolver.Resolve(unresolvedConstrainedType);
}
if (thisValue.BoxedType == null)
{
if (thisType == null)
throw new InvalidOperationException("Attempting to constrain an invalid type.");
var resolvedConstrainedType = _typeResolver.Resolve(unresolvedConstrainedType);
if (!TypeReferenceEqualityComparer.AreEqual(thisType.WithoutModifiers(), resolvedConstrainedType.WithoutModifiers()))
throw new InvalidOperationException(string.Format("Attempting to constrain a value of type '{0}' to type '{1}'.", thisType, resolvedConstrainedType));
}
copyBackBoxedExpr = null;
// constrained opcode has 3 cases to handle: https://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.constrained(v=vs.110).aspx
// 1. If thisType is a reference type (as opposed to a value type) then ptr is dereferenced and passed as the 'this' pointer to the callvirt of method.
// 2. If thisType is a value type and thisType implements method then ptr is passed unmodified as the 'this' pointer to a call method instruction, for the implementation of method by thisType.
// 3. If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the 'this' pointer to the callvirt method instruction.
// Reference Type
if (!thisType.IsValueType())
{
poppedValues[0] = new StackInfo(Emit.InParentheses(Emit.Dereference(thisValue.Expression)), thisType);
_writer.AddIncludeForTypeDefinition(resolvedMethodToCall.DeclaringType);
return CallExpressionFor(_methodReference, methodToCall, callType, poppedValues, addUniqueSuffix);
}
// Value Type
// Optimization: If we have generic sharing enabled for enum/int types, we can make the following optimization.
// "box" the value using stack memory and treat as a virtual call. Why?
// The virtual call will go to System.Enum where the "boxed" 'this' value is process in readonly fashion
if (CodeGenOptions.CanShareEnumTypes() && _sharingType == SharingType.Shared && thisType.IsEnum())
{
var virtualCallArguments = new List<StackInfo>(poppedValues);
if (shouldUnbox)
{
virtualCallArguments[0] = thisValue;
}
else
{
var fakeBoxedVariable = FakeBox(thisType, _runtimeMetadataAccess.TypeInfoFor(_constrainedCallThisType), thisValue.Expression);
virtualCallArguments[0] = new StackInfo(Emit.AddressOf(fakeBoxedVariable), Globals.TypeProvider.ObjectTypeReference);
}
var virtualCallExpression = CallExpressionFor(_methodReference, methodToCall, callType, virtualCallArguments, addUniqueSuffix, emitNullCheckForInvocation: false);
return virtualCallExpression;
}
// Optimization: If a value type implements a virtual method avoid boxing the the 'this' argument and make a direct call
var targetMethod = _vTableBuilder.GetVirtualMethodTargetMethodForConstrainedCallOnValueType(thisType, resolvedMethodToCall);
if (targetMethod != null && TypeReferenceEqualityComparer.AreEqual(targetMethod.DeclaringType, thisType) && !CodeGenOptions.MonoRuntime)
{
if (resolvedMethodToCall.IsGenericInstance)
targetMethod = typeResolverForMethodToCall.Resolve(targetMethod, true);
// Corner Case: We allow generic sharing for value type generic instances (e.g. List<string> -> List<object>),
// so while we can ascertain that a method is implemented on a generic value type and can avoid the box operation
// we cannot know the concrete method we are calling in the presence of sharing. Lookup the method from the vtable instead.
if (thisType.IsGenericInstance && thisType.IsValueType() && _sharingType == SharingType.Shared)
{
var directVirtualCallArguments = new List<StackInfo>(poppedValues);
if (shouldUnbox)
{
directVirtualCallArguments[0] = thisValue;
copyBackBoxedExpr = null;
}
else
{
var fakeBoxedVariable = FakeBox(thisType, _runtimeMetadataAccess.TypeInfoFor(unresolvedConstrainedType), thisValue.Expression);
directVirtualCallArguments[0] = new StackInfo(Emit.AddressOf(fakeBoxedVariable), Globals.TypeProvider.ObjectTypeReference, boxedType: unresolvedConstrainedType);
copyBackBoxedExpr = Emit.Assign(Emit.Dereference(thisValue.Expression), $"{fakeBoxedVariable}.m_Value");
}
var virtualInvokeDataVariable = addUniqueSuffix(VariableNameForVirtualInvokeDataInDirectVirtualCall);
if (resolvedMethodToCall.DeclaringType.IsInterface())
{
// Corner Case - Generic Virtual calls are slow and need handled specially
// Can we know the target method at build time and add that to the RGCTX to avoid
// this lookup at runtime? Maybe - it could be an optimization opportunity.
if (resolvedMethodToCall.IsGenericInstance)
{
if (!shouldUnbox)
poppedValues[0] = BoxThisForContraintedCallIntoNewTemp(thisValue, unresolvedConstrainedType);
return CallExpressionFor(_methodReference, methodToCall, callType, poppedValues, addUniqueSuffix);
}
_writer.WriteLine($"const VirtualInvokeData& {virtualInvokeDataVariable} = il2cpp_codegen_get_interface_invoke_data({_vTableBuilder.IndexFor(methodToCall.Resolve())}, {directVirtualCallArguments[0]}, {_runtimeMetadataAccess.TypeInfoFor(methodToCall.DeclaringType)});");
}
else
{
_writer.WriteLine($"const VirtualInvokeData& {virtualInvokeDataVariable} = il2cpp_codegen_get_virtual_invoke_data({_vTableBuilder.IndexFor(methodToCall.Resolve())}, {directVirtualCallArguments[0]});");
}
return CallExpressionFor(_methodReference, methodToCall, MethodCallType.DirectVirtual, directVirtualCallArguments, addUniqueSuffix, emitNullCheckForInvocation: false);
}
// Default Case: If thisType is a value type and thisType overrides method then ptr is passed as this value in a direct call
methodToCall = targetMethod;
_writer.AddIncludeForTypeDefinition(methodToCall.DeclaringType);
_writer.AddIncludeForTypeDefinition(_typeResolver.ResolveReturnType(methodToCall));
callType = MethodCallType.Normal;
if (shouldUnbox)
poppedValues[0] = new StackInfo(Unbox(thisType, thisValue), new ByReferenceType(thisType));
return CallExpressionFor(_methodReference, methodToCall, callType, poppedValues, addUniqueSuffix);
}
// OPTIMIZATION: If we are making a call to Enum.GetHashCode then avoid the boxing operation with virtual call and
// instead make a direct call to the underlying primitive integer type's GetHashCode
if (thisType.IsEnum() && methodToCall.Name == "GetHashCode")
{
var baseType = thisType.GetUnderlyingEnumType().Resolve();
var baseMethod = baseType.Methods.Single(m => m.Name == "GetHashCode");
methodToCall = baseMethod;
_writer.AddIncludeForTypeDefinition(baseMethod.DeclaringType);
callType = MethodCallType.Normal;
if (shouldUnbox)
poppedValues[0] = new StackInfo(Unbox(thisType, thisValue), new ByReferenceType(thisType));
return CallExpressionFor(_methodReference, methodToCall, callType, poppedValues, addUniqueSuffix);
}
// Slow Path: We are calling a virtual method defined on System.Object, System.ValueType, or System.Enum that the value type did not override.
// We must then box the 'this' argument since the base classes mentioned above expect a boxed value as 'this'.
// The boxed object could be modified and used later, therefore we must copy it back in case changes were made. This is what copyBackBoxedExpr is for.
poppedValues[0] = shouldUnbox ? thisValue : BoxThisForContraintedCallIntoNewTemp(thisValue, unresolvedConstrainedType);
copyBackBoxedExpr = shouldUnbox ? null : Emit.Assign(Emit.Dereference(thisValue.Expression), Emit.Dereference(Emit.Cast(Globals.Naming.ForVariable(thisType) + "*", Emit.Call("UnBox", poppedValues[0].Expression))));
return CallExpressionFor(_methodReference, methodToCall, callType, poppedValues, addUniqueSuffix);
}
private string FakeBox(TypeReference thisType, string typeInfoVariable, string pointerToValue)
{
var boxedVariable = NewTempName();
_writer.WriteLine($"Il2CppFakeBox<{Globals.Naming.ForVariable(thisType)}> {boxedVariable}({typeInfoVariable}, {pointerToValue});");
return boxedVariable;
}
private StackInfo BoxThisForContraintedCallIntoNewTemp(StackInfo thisValue, TypeReference unresolvedConstrainedType)
{
// We can box once and store the result in a temporary variable. This allows us to do the null check and make the function call with just one call to Box.
var boxedValue = NewTemp(Globals.TypeProvider.ObjectTypeReference);
_writer.WriteLine("{0} = {1};", boxedValue.IdentifierExpression, Emit.Call("Box", _runtimeMetadataAccess.TypeInfoFor(unresolvedConstrainedType), thisValue.Expression));
return new StackInfo(boxedValue.Expression, Globals.TypeProvider.ObjectTypeReference, boxedType: unresolvedConstrainedType);
}
private void EmitCodeForLeaveFromTry(ExceptionSupport.Node node, Instruction ins)
{
var targetOffset = ((Instruction)ins.Operand).Offset;
var finallyNodes = node.GetTargetFinallyNodesForJump(ins.Offset, targetOffset).ToArray();
if (finallyNodes.Length != 0)
{
var firstFinallyNode = finallyNodes.First();
foreach (var finallyNode in finallyNodes)
_exceptionSupport.AddLeaveTarget(finallyNode, ins);
_writer.WriteLine("IL2CPP_LEAVE(0x{0:X}, {1});",
((Instruction)ins.Operand).Offset, _labeler.FormatOffset(firstFinallyNode.Start));
}
else
{
_writer.WriteLine(_labeler.ForJump(targetOffset));
}
}
private void EmitCodeForLeaveFromCatch(ExceptionSupport.Node node, Instruction ins)
{
var targetOffset = ((Instruction)ins.Operand).Offset;
var finallyNodes = node.GetTargetFinallyNodesForJump(ins.Offset, targetOffset).ToArray();
if (finallyNodes.Length != 0)
{
var firstFinallyNode = finallyNodes.First();
foreach (var finallyNode in finallyNodes)
_exceptionSupport.AddLeaveTarget(finallyNode, ins);
_writer.WriteLine("IL2CPP_LEAVE(0x{0:X}, {1});",
((Instruction)ins.Operand).Offset,
_labeler.FormatOffset(firstFinallyNode.Start));
}
else
{
_writer.WriteLine(_labeler.ForJump(targetOffset));
}
}
private void EmitCodeForLeaveFromFinallyOrFault(Instruction ins)
{
// NOTE(gab): Finally and Fault blocks are not supposed to be exited using a Leave instruction.
// For compatibility reason, if Leave instructions are emitted inside a finally or fault block,
// we deal with them as they were normal jumps.
_writer.WriteLine(_labeler.ForJump(((Instruction)ins.Operand).Offset));
}
private void EmitCodeForLeaveFromBlock(ExceptionSupport.Node node, Instruction ins)
{
var targetOffset = ((Instruction)ins.Operand).Offset;
if (!node.IsInTryBlock && !node.IsInCatchBlock)
{
_writer.WriteLine(_labeler.ForJump(targetOffset));
}
else
{
var finallyNodes = node.GetTargetFinallyNodesForJump(ins.Offset, targetOffset).ToArray();
if (finallyNodes.Length != 0)
{
var firstFinallyNode = finallyNodes.First();
foreach (var finallyNode in finallyNodes)
_exceptionSupport.AddLeaveTarget(finallyNode, ins);
_writer.WriteLine(
"IL2CPP_LEAVE(0x{0:X}, {1});",
((Instruction)ins.Operand).Offset,
_labeler.FormatOffset(firstFinallyNode.Start));
}
else
{
_writer.WriteLine(_labeler.ForJump(targetOffset));
}
}
}
private bool ShouldStripLeaveInstruction(InstructionBlock block, Instruction ins)
{
return !_labeler.NeedsLabel(ins) && (block.First == block.Last && block.First.Previous != null && block.First.Previous.OpCode.Code == Code.Leave);
}
private void PushExpression(TypeReference typeReference, string expression, TypeReference boxedType = null, MethodReference methodExpressionIsPointingTo = null)
{
_valueStack.Push(new StackInfo(string.Format("({0})", expression), typeReference, boxedType: boxedType, methodExpressionIsPointingTo: methodExpressionIsPointingTo));
}
// NOTE NOTE NOTE: this should only be used by ldelema instruction!!
private string EmitArrayLoadElementAddress(StackInfo array, string indexExpression)
{
_arrayBoundsCheckSupport.RecordArrayBoundsCheckEmitted();
return Emit.LoadArrayElementAddress(array.Expression, indexExpression, _arrayBoundsCheckSupport.ShouldEmitBoundsChecksForMethod());
}
private void LoadArgumentAddress(ParameterReference parameter)
{
// An index of -1 indicates the this pointer is passed as an in parameter.
string parameterName;
if (parameter.Index != -1)
parameterName = ParameterNameFor(_methodDefinition, parameter.Index);
else
parameterName = WellKnown.ParameterNames.This;
_valueStack.Push(new StackInfo("(&" + parameterName + ")",
new ByReferenceType(_typeResolver.ResolveParameterType(_methodReference, parameter))));
}
private void WriteLabelForBranchTarget(Instruction ins)
{
if (DidAlreadyEmitLabelFor(ins))
return;
_emittedLabels.Add(ins);
var commentOut = "";
if (!_referencedLabels.Contains(ins))
return;
_writer.WriteLine();
_writer.WriteUnindented(string.Format("{0}{1}", commentOut, _labeler.ForLabel(ins)));
}
private bool DidAlreadyEmitLabelFor(Instruction ins)
{
return _emittedLabels.Contains(ins);
}
private void WriteJump(Instruction targetInstruction)
{
_writer.WriteLine(_labeler.ForJump(targetInstruction));
}
private void LoadLocalAddress(VariableReference variableReference)
{
_valueStack.Push(new StackInfo("(&" + Globals.Naming.ForVariableName(variableReference) + ")",
new ByReferenceType(_typeResolver.Resolve(variableReference.VariableType))));
}
private void WriteDup()
{
var top = _valueStack.Pop();
if (top.Expression == WellKnown.ParameterNames.This)
{
// NULL is dealt with in a special way
_valueStack.Push(new StackInfo(WellKnown.ParameterNames.This, top.Type));
_valueStack.Push(new StackInfo(WellKnown.ParameterNames.This, top.Type));
}
else if (top.Expression == "NULL" && top.Type.IsSystemObject())
{
// NULL is dealt with in a special way
_valueStack.Push(new StackInfo("NULL", ObjectTypeReference));
_valueStack.Push(new StackInfo("NULL", ObjectTypeReference));
}
else
{
var temp = NewTemp(top.Type);
WriteAssignment(temp.IdentifierExpression, top.Type, top);
_valueStack.Push(new StackInfo(temp));
_valueStack.Push(new StackInfo(temp));
}
}
private void WriteNotOperation()
{
var value = _valueStack.Pop();
PushExpression(value.Type, string.Format("(~{0})", value.Expression));
}
private void WriteNegateOperation()
{
var value = _valueStack.Pop();
var resultType = CalculateResultTypeForNegate(value.Type);
PushExpression(value.Type, string.Format("(-{0})", CastTypeIfNeeded(value, resultType)));
}
private TypeReference CalculateResultTypeForNegate(TypeReference type)
{
if (type.IsUnsignedIntegralType())
{
if (type.MetadataType == MetadataType.Byte || type.MetadataType == MetadataType.UInt16 || type.MetadataType == MetadataType.UInt32)
return Globals.TypeProvider.Int32TypeReference;
return Globals.TypeProvider.Int64TypeReference;
}
return type;
}
private void LoadConstant(TypeReference type, string stringValue)
{
PushExpression(type, stringValue);
}
private void StoreLocalAndPush(TypeReference type, string stringValue)
{
var variable = NewTemp(type);
_writer.WriteLine("{0} = {1};", variable.IdentifierExpression, stringValue);
_valueStack.Push(new StackInfo(variable));
}
private void StoreLocalAndPush(TypeReference type, string stringValue, TypeReference boxedType)
{
var variable = NewTemp(type);
_writer.WriteLine("{0} = {1};", variable.IdentifierExpression, stringValue);
_valueStack.Push(new StackInfo(variable.Expression, variable.Type, boxedType: boxedType));
}
private void StoreLocalAndPush(TypeReference type, string stringValue, MethodReference methodExpressionIsPointingTo)
{
var variable = NewTemp(type);
_writer.WriteLine("{0} = {1};", variable.IdentifierExpression, stringValue);
_valueStack.Push(new StackInfo(variable.Expression, variable.Type, methodExpressionIsPointingTo: methodExpressionIsPointingTo));
}
private string CallExpressionFor(MethodReference callingMethod, MethodReference unresolvedMethodToCall, MethodCallType callType, List<StackInfo> poppedValues, Func<string, string> addUniqueSuffix, bool emitNullCheckForInvocation = true)
{
var methodToCall = _typeResolver.Resolve(unresolvedMethodToCall);
var typeResolverForMethodToCall = _typeResolver;
var genericInstanceMethod = methodToCall as GenericInstanceMethod;
if (genericInstanceMethod != null)
typeResolverForMethodToCall = _typeResolver.Nested(genericInstanceMethod);
var parameterTypes = GetParameterTypes(methodToCall, typeResolverForMethodToCall);
// need to add this parameter
if (methodToCall.HasThis)
parameterTypes.Insert(0, methodToCall.DeclaringType.IsValueType() ? new ByReferenceType(methodToCall.DeclaringType) : methodToCall.DeclaringType);
var argsFor = FormatArgumentsForMethodCall(parameterTypes, poppedValues, _sharingType);
return CallExpressionFor(callingMethod, unresolvedMethodToCall, callType, argsFor, addUniqueSuffix, emitNullCheckForInvocation);
}
private string CallExpressionFor(MethodReference callingMethod, MethodReference unresolvedMethodToCall, MethodCallType callType, List<string> argsFor, Func<string, string> addUniqueSuffix, bool emitNullCheckForInvocation = true)
{
var methodToCall = _typeResolver.Resolve(unresolvedMethodToCall);
var typeResolverForMethodToCall = _typeResolver;
if (MethodSignatureWriter.NeedsHiddenMethodInfo(methodToCall, callType, false))
{
string argument;
if (CodeGenOptions.MonoRuntime)
{
argument = callType == MethodCallType.DirectVirtual
? string.Format("il2cpp_codegen_vtable_slot_method({0}, {1})", addUniqueSuffix(VariableNameForTypeInfoInConstrainedCall), _runtimeMetadataAccess.MethodInfo(unresolvedMethodToCall))
: _runtimeMetadataAccess.HiddenMethodInfo(unresolvedMethodToCall);
}
else
{
// if we have a DirectVirtual call we need to pass the method info from the virtual method we are invoking
if (callType == MethodCallType.DirectVirtual)
{
var invokeData = addUniqueSuffix(VariableNameForVirtualInvokeDataInDirectVirtualCall);
argument = $"{invokeData}.method";
}
else
{
argument = _runtimeMetadataAccess.HiddenMethodInfo(unresolvedMethodToCall);
}
}
argsFor.Add((CodeGenOptions.EmitComments ? "/*hidden argument*/" : "") + argument);
}
if (emitNullCheckForInvocation)
_nullCheckSupport.WriteNullCheckForInvocationIfNeeded(methodToCall, argsFor);
if (GenericSharingAnalysis.ShouldTryToCallStaticConstructorBeforeMethodCall(methodToCall, _methodReference))
WriteCallToClassAndInitializerAndStaticConstructorIfNeeded(unresolvedMethodToCall.DeclaringType, _methodDefinition, _runtimeMetadataAccess);
return GetMethodCallExpression(_writer, callingMethod, methodToCall, unresolvedMethodToCall, typeResolverForMethodToCall, callType, _runtimeMetadataAccess, _vTableBuilder, argsFor, _arrayBoundsCheckSupport.ShouldEmitBoundsChecksForMethod(), addUniqueSuffix);
}
private void EmitCallExpressionAndStoreResult(Instruction instruction, TypeReference returnType, string callExpression, string copyBackBoxedExpr)
{
if (returnType.IsVoid())
{
_writer.WriteStatement(callExpression);
}
else
{
// OPTIMIZATION: If the next instruction is a Pop, we don't need to actually create a local and push it's value onto the stack.
// This prevents an unused local variable in the generated code.
if (instruction.Next != null && instruction.Next.OpCode.Code == Code.Pop)
{
_writer.WriteStatement(callExpression);
// Push a dummy value onto the stack that will be immediately popped
_valueStack.Push(new StackInfo(WellKnown.Constants.Null, ObjectTypeReference));
}
else
{
var variableName = NewTemp(returnType);
_valueStack.Push(new StackInfo(variableName.Expression, returnType));
_writer.WriteStatement(Emit.Assign(variableName.IdentifierExpression, callExpression));
}
}
if (copyBackBoxedExpr != null)
_writer.WriteStatement(copyBackBoxedExpr);
}
internal static string GetMethodCallExpression(IGeneratedMethodCodeWriter writer, IRuntimeMetadataAccess metadataAccess, string thisVariableName, MethodReference callingMethod, MethodReference method, MethodCallType methodCallType, params string[] args)
{
var arguments = new List<string>();
if (method.HasThis)
arguments.Add(thisVariableName);
if (args.Length > 0)
arguments.AddRange(args);
if (MethodSignatureWriter.NeedsHiddenMethodInfo(method, methodCallType, false))
arguments.Add(metadataAccess.HiddenMethodInfo(method));
var vtableBuilder = methodCallType == MethodCallType.Virtual ? new VTableBuilder() : null;
return GetMethodCallExpression(writer, callingMethod, method, method, TypeResolver.Empty, methodCallType, metadataAccess, vtableBuilder, arguments, useArrayBoundsCheck: false);
}
internal static string GetMethodCallExpression(IGeneratedMethodCodeWriter writer, MethodReference callingMethod, MethodReference methodToCall, MethodReference unresolvedMethodtoCall, TypeResolver typeResolverForMethodToCall, MethodCallType callType, IRuntimeMetadataAccess runtimeMetadataAccess, VTableBuilder vTableBuilder, IEnumerable<string> argumentArray, bool useArrayBoundsCheck, Func<string, string> addUniqueSuffix = null)
{
if (methodToCall.DeclaringType.IsArray && methodToCall.Name == "Set")
return GetArraySetCall(argumentArray.First(), argumentArray.Skip(1).AggregateWithComma(), useArrayBoundsCheck);
if (methodToCall.DeclaringType.IsArray && methodToCall.Name == "Get")
return GetArrayGetCall(argumentArray.First(), argumentArray.Skip(1).AggregateWithComma(), useArrayBoundsCheck);
if (methodToCall.DeclaringType.IsArray && methodToCall.Name == "Address")
return GetArrayAddressCall(argumentArray.First(), argumentArray.Skip(1).AggregateWithComma(), useArrayBoundsCheck);
if (methodToCall.DeclaringType.IsSystemArray() && methodToCall.Name == "GetGenericValueImpl")
return Emit.Call("ArrayGetGenericValueImpl", argumentArray);
if (methodToCall.DeclaringType.IsSystemArray() && methodToCall.Name == "SetGenericValueImpl")
return Emit.Call("ArraySetGenericValueImpl", argumentArray);
if (GenericsUtilities.IsGenericInstanceOfCompareExchange(methodToCall))
{
var genericInstanceMethod = (GenericInstanceMethod)methodToCall;
var genericInstanceTypeName = Globals.Naming.ForVariable(genericInstanceMethod.GenericArguments[0]);
return Emit.Call(string.Format("InterlockedCompareExchangeImpl<{0}>", genericInstanceTypeName), argumentArray);
}
if (GenericsUtilities.IsGenericInstanceOfExchange(methodToCall))
{
var genericInstanceMethod = (GenericInstanceMethod)methodToCall;
var genericInstanceTypeName = Globals.Naming.ForVariable(genericInstanceMethod.GenericArguments[0]);
return Emit.Call(string.Format("InterlockedExchangeImpl<{0}>", genericInstanceTypeName), argumentArray);
}
if (IntrinsicRemap.ShouldRemap(methodToCall))
{
return Emit.Call(
IntrinsicRemap.MappedNameFor(methodToCall),
IntrinsicRemap.HasCustomArguments(methodToCall) ? IntrinsicRemap.GetCustomArguments(methodToCall, callingMethod, runtimeMetadataAccess, argumentArray) : argumentArray);
}
if (IsManagedIntrinsics.IsUnmangedCall(methodToCall))
return IsManagedIntrinsics.IsArgumentTypeUnmanaged(methodToCall);
if (IsManagedIntrinsics.IsManagedCall(methodToCall))
return IsManagedIntrinsics.IsArgumentTypeManaged(methodToCall);
if (methodToCall.Resolve().IsStatic)
{
return Emit.Call(runtimeMetadataAccess.Method(unresolvedMethodtoCall), argumentArray);
}
if (callType == MethodCallType.DirectVirtual)
{
var methodPointerType = MethodSignatureWriter.GetMethodPointerForVTable(methodToCall);
var virtualInvokeData = addUniqueSuffix(VariableNameForVirtualInvokeDataInDirectVirtualCall);
if (CodeGenOptions.MonoRuntime)
return Emit.Call("(" + Emit.Cast(MethodSignatureWriter.GetMethodPointerForVTable(methodToCall), string.Format("il2cpp_codegen_vtable_slot_method_pointer({0}, {1})", addUniqueSuffix(VariableNameForTypeInfoInConstrainedCall), runtimeMetadataAccess.MethodInfo(unresolvedMethodtoCall))) + ")", argumentArray);
else
return Emit.Call("(" + Emit.Cast(methodPointerType, $"{virtualInvokeData}.methodPtr") + ")", argumentArray);
}
if (callType != MethodCallType.Virtual || callType == MethodCallType.VirtualEx || MethodSignatureWriter.CanDevirtualizeMethodCall(methodToCall.Resolve()))
{
if (unresolvedMethodtoCall.DeclaringType.IsValueType())
return Emit.Call(runtimeMetadataAccess.Method(methodToCall), argumentArray);
return Emit.Call(runtimeMetadataAccess.Method(unresolvedMethodtoCall), argumentArray);
}
return VirtualCallFor(writer, methodToCall, unresolvedMethodtoCall, argumentArray, typeResolverForMethodToCall, runtimeMetadataAccess, vTableBuilder);
}
private static string VirtualCallFor(IGeneratedMethodCodeWriter writer, MethodReference method, MethodReference unresolvedMethod, IEnumerable<string> args, TypeResolver typeResolver, IRuntimeMetadataAccess runtimeMetadataAccess, VTableBuilder vTableBuilder)
{
var isInterface = method.DeclaringType.Resolve().IsInterface;
var arguments = new List<string>();
if (CodeGenOptions.MonoRuntime)
arguments.Add("(RuntimeMethod*)" + runtimeMetadataAccess.MethodInfo(unresolvedMethod));
else
arguments.Add(method.IsGenericInstance ? runtimeMetadataAccess.MethodInfo(unresolvedMethod) : vTableBuilder.IndexFor(method.Resolve()) + " /* " + method.FullName + " */");
if (isInterface && !method.IsGenericInstance)
arguments.Add(runtimeMetadataAccess.TypeInfoFor(unresolvedMethod.DeclaringType));
arguments.AddRange(args);
return Emit.Call(writer.VirtualCallInvokeMethod(method, typeResolver), arguments);
}
private static string GetArrayAddressCall(string array, string arguments, bool useArrayBoundsCheck)
{
return Emit.Call(string.Format("({0})->{1}", array, ArrayNaming.ForArrayItemAddressGetter(useArrayBoundsCheck)), arguments);
}
private static string GetArrayGetCall(string array, string arguments, bool useArrayBoundsCheck)
{
return Emit.Call(string.Format("({0})->{1}", array, ArrayNaming.ForArrayItemGetter(useArrayBoundsCheck)), arguments);
}
private static string GetArraySetCall(string array, string arguments, bool useArrayBoundsCheck)
{
return Emit.Call(string.Format("({0})->{1}", array, ArrayNaming.ForArrayItemSetter(useArrayBoundsCheck)), arguments);
}
private void WriteUnconditionalJumpTo(InstructionBlock block, Instruction target)
{
if (block.Successors.Count() != 1)
throw new ArgumentException("Expected only one successor for the current block", "target");
WriteAssignGlobalVariables(_stackAnalysis.InputVariablesFor(block.Successors.Single()));
WriteJump(target);
}
private void WriteCastclassOrIsInst(TypeReference targetType, StackInfo value, string operation)
{
var resolvedTypeReference = _typeResolver.Resolve(targetType);
if (value.BoxedType != null && resolvedTypeReference.IsInterface())
{
var resolvedBoxedType = _typeResolver.Resolve(value.BoxedType);
// Can't do this optimization on nullables because the box operation result
// depends on whether the nullable variable had a value
if (!resolvedBoxedType.IsNullable())
{
while (resolvedBoxedType != null)
{
var interfaces = resolvedBoxedType.GetInterfaces();
foreach (var iface in interfaces)
{
if (TypeReferenceEqualityComparer.AreEqual(iface, resolvedTypeReference))
{
_valueStack.Push(value);
return;
}
}
resolvedBoxedType = resolvedBoxedType.GetBaseType();
}
if (operation == "IsInst")
{
LoadNull();
return;
}
}
}
var variableType = resolvedTypeReference.IsValueType() ? Globals.TypeProvider.ObjectTypeReference : resolvedTypeReference;
_writer.AddIncludeForTypeDefinition(resolvedTypeReference);
var expression = Emit.Cast(variableType, GetCastclassOrIsInstCall(targetType, value, operation, resolvedTypeReference));
_valueStack.Push(new StackInfo($"({expression})", variableType, boxedType: value.BoxedType));
}
private string GetCastclassOrIsInstCall(TypeReference targetType, StackInfo value, string operation, TypeReference resolvedTypeReference)
{
if (CodeGenOptions.UseTinyRuntimeBackend && resolvedTypeReference.IsNullable())
targetType = resolvedTypeReference = ((GenericInstanceType)resolvedTypeReference).GenericArguments[0];
return Emit.Call(operation + GetOptimizedCastclassOrIsInstMethodSuffix(resolvedTypeReference, _sharingType), "(RuntimeObject*)" + value.Expression, _runtimeMetadataAccess.TypeInfoFor(targetType));
}
private static string GetOptimizedCastclassOrIsInstMethodSuffix(TypeReference resolvedTypeReference, SharingType sharingType)
{
// optimization for checks on simple types
// if the target type is a simple class we can just search in our type hierarchy using the "Class" suffixed version of the method
// if the target type is a sealed simple class we can just compare the instance type to the target type using the "Sealed" suffixed version of the method
if (sharingType == SharingType.NonShared && !resolvedTypeReference.IsInterface() && !resolvedTypeReference.IsArray && !resolvedTypeReference.IsNullable())
{
var @sealed = resolvedTypeReference.Resolve().IsSealed;
return @sealed ? "Sealed" : "Class";
}
return string.Empty;
}
MethodReference GetCreateStringMethod(MethodReference method)
{
if (method.DeclaringType.Name != "String")
throw new Exception("method.DeclaringType.Name != \"String\"");
foreach (var m in method.DeclaringType.Resolve().Methods.Where(meth => meth.Name == "CreateString"))
{
if (m.Parameters.Count != method.Parameters.Count)
continue;
bool different = false;
for (int i = 0; i < m.Parameters.Count; i++)
{
if (m.Parameters[i].ParameterType.FullName != method.Parameters[i].ParameterType.FullName)
different = true;
}
if (different)
continue;
return m;
}
throw new Exception(String.Format("Can't find proper CreateString : {0}", method.FullName));
}
private void Unbox(Instruction ins)
{
var boxedValue = _valueStack.Pop();
var type = _typeResolver.Resolve((TypeReference)ins.Operand);
_writer.AddIncludeForTypeDefinition(type);
PushExpression(new ByReferenceType(type), Emit.Cast(new PointerType(type), Unbox(type, boxedValue)));
}
private string Unbox(TypeReference type, StackInfo boxedValue)
{
var boxedExpression = WriteExpressionAndCastIfNeeded(ObjectTypeReference, boxedValue);
var resolvedType = _typeResolver.Resolve(type);
if (resolvedType.IsNullable())
{
/* From ECMA-335 III.4.32 unbox - convert boxed value type to its raw form:
*
* Typically, unbox simply computes the address of the value type
* that is already present inside of the boxed object. This approach
* is not possible when unboxing nullable value types. Because
* Nullable<T> values are converted to boxed Ts during the box operation,
* an implementation often must manufacture a new Nullable<T> on the
* heap and compute the address to the newly allocated object.
*
* Instead of allocating on the heap, we're gonna allocate it
* on the stack instead. This approach is a little bit more complex,
* but it will save us an unnecessary allocation.
*/
var boxedType = ((GenericInstanceType)resolvedType).GenericArguments[0];
if (CodeGenOptions.CanShareEnumTypes() && _sharingType == SharingType.Shared && boxedType.IsEnum())
{
// For value type generic sharing, determine if this is a generic insance type.
// If so, let's use the RGCTX to get the proper type at runtime.
var genericInstanceType = type as GenericInstanceType;
if (genericInstanceType != null)
boxedType = genericInstanceType.GenericArguments[0];
}
var boxedTypeInfo = _runtimeMetadataAccess.TypeInfoFor(boxedType);
var unboxedStorage = NewTempName();
_writer.WriteLine($"void* {unboxedStorage} = alloca(sizeof({Globals.Naming.ForVariable(resolvedType)}));");
if (CodeGenOptions.UseTinyRuntimeBackend)
_writer.WriteLine($"UnBoxNullable<{Globals.Naming.ForVariable(boxedType)}>({boxedExpression}, {boxedTypeInfo}, {unboxedStorage});");
else
_writer.WriteLine($"UnBoxNullable({boxedExpression}, {boxedTypeInfo}, {unboxedStorage});");
return unboxedStorage;
}
var typeInfo = _runtimeMetadataAccess.TypeInfoFor(type);
if (CodeGenOptions.UseTinyRuntimeBackend)
return string.Format($"UnBox<{Globals.Naming.ForVariable(resolvedType)}>({boxedExpression}, {typeInfo})");
return string.Format($"UnBox({boxedExpression}, {typeInfo})");
}
private void WriteUnsignedArithmeticOperation(string op)
{
var right = _valueStack.Pop();
var left = _valueStack.Pop();
var leftStackType = StackTypeConverter.StackTypeForBinaryOperation(left.Type);
var rightStackType = StackTypeConverter.StackTypeForBinaryOperation(right.Type);
var resultType = GetMetadataTypeOrderFor(leftStackType) < GetMetadataTypeOrderFor(rightStackType)
? GetUnsignedType(rightStackType)
: GetUnsignedType(leftStackType);
WriteBinaryOperation(
GetSignedType(resultType), // There's no signedness on the stack, but if you do a conv.i8 for example afterwards, it must treat it as signed value
string.Format("({0})({1})", Globals.Naming.ForVariable(resultType), Globals.Naming.ForVariable(leftStackType)), // Sign extension must happen against stack type, not variable type!
left.Expression, op,
string.Format("({0})({1})", Globals.Naming.ForVariable(resultType), Globals.Naming.ForVariable(rightStackType)),
right.Expression);
}
private TypeReference GetUnsignedType(TypeReference type)
{
if (type.IsSameType(SystemIntPtr) || type.IsSameType(SystemUIntPtr))
return SystemUIntPtr;
switch (type.MetadataType)
{
case MetadataType.SByte:
case MetadataType.Byte:
return ByteTypeReference;
case MetadataType.Int16:
case MetadataType.UInt16:
return UInt16TypeReference;
case MetadataType.Int32:
case MetadataType.UInt32:
return UInt32TypeReference;
case MetadataType.Int64:
case MetadataType.UInt64:
return UInt64TypeReference;
case MetadataType.IntPtr:
case MetadataType.UIntPtr:
return SystemUIntPtr;
}
return type;
}
private TypeReference GetSignedType(TypeReference type)
{
if (type.IsSameType(SystemIntPtr) || type.IsSameType(SystemUIntPtr))
return SystemIntPtr;
switch (type.MetadataType)
{
case MetadataType.SByte:
case MetadataType.Byte:
return SByteTypeReference;
case MetadataType.Int16:
case MetadataType.UInt16:
return Int16TypeReference;
case MetadataType.Int32:
case MetadataType.UInt32:
return Int32TypeReference;
case MetadataType.Int64:
case MetadataType.UInt64:
return Int64TypeReference;
case MetadataType.IntPtr:
case MetadataType.UIntPtr:
return SystemIntPtr;
}
return type;
}
private static int GetMetadataTypeOrderFor(TypeReference type)
{
if (type.IsSameType(Globals.TypeProvider.SystemIntPtr) || type.IsSameType(Globals.TypeProvider.SystemUIntPtr))
return 3;
switch (type.MetadataType)
{
case MetadataType.Byte:
case MetadataType.SByte:
return 0;
case MetadataType.UInt16:
case MetadataType.Int16:
return 1;
case MetadataType.UInt32:
case MetadataType.Int32:
return 2;
case MetadataType.IntPtr:
case MetadataType.Pointer:
case MetadataType.UIntPtr:
return 3;
case MetadataType.UInt64:
case MetadataType.Int64:
return 4;
}
throw new Exception(string.Format("Invalid metadata type for typereference {0}", type));
}
private void StoreField(Instruction ins)
{
var value = _valueStack.Pop();
var target = _valueStack.Pop();
var fieldReference = (FieldReference)ins.Operand;
if (target.Expression != WellKnown.ParameterNames.This)
_nullCheckSupport.WriteNullCheckIfNeeded(target);
EmitMemoryBarrierIfNecessary(fieldReference);
if (fieldReference.Name == WellKnown.FieldNames.IntPtrValue && TypeReferenceEqualityComparer.AreEqual(fieldReference.DeclaringType, IntPtrTypeReference))
{
_writer.WriteLine(
"*{0} = ({1});",
target,
WriteExpressionAndCastIfNeeded(SystemIntPtr, value));
return;
}
if (fieldReference.Name == WellKnown.FieldNames.UIntPtrPointer && TypeReferenceEqualityComparer.AreEqual(fieldReference.DeclaringType, UIntPtrTypeReference))
{
_writer.WriteLine(
"*{0} = ({1});",
target,
WriteExpressionAndCastIfNeeded(SystemUIntPtr, value));
return;
}
_writer.WriteLine(
"{0}->{1}({2});",
CastReferenceTypeOrNativeIntIfNeeded(target, _typeResolver.Resolve(fieldReference.DeclaringType)),
Globals.Naming.ForFieldSetter(fieldReference),
WriteExpressionAndCastIfNeeded(_typeResolver.ResolveFieldType(fieldReference), value));
}
private static string CastReferenceTypeOrNativeIntIfNeeded(StackInfo originalValue, TypeReference toType)
{
if (!toType.IsValueType())
return CastTypeIfNeeded(originalValue, toType);
if (originalValue.Type.IsIntegralPointerType())
return CastTypeIfNeeded(originalValue, new ByReferenceType(toType));
if (originalValue.Type.IsPointer)
return CastTypeIfNeeded(originalValue, new PointerType(toType));
return originalValue.Expression;
}
private static string CastTypeIfNeeded(StackInfo originalValue, TypeReference toType)
{
if (!TypeReferenceEqualityComparer.AreEqual(originalValue.Type, toType))
return string.Format("({0})", Emit.Cast(toType, originalValue.Expression));
return originalValue.Expression;
}
private static string CastIfPointerType(TypeReference type)
{
var cast = string.Empty;
if (type.IsPointer || type.IsByReference)
cast = "(" + Globals.Naming.ForVariable(type) + ")";
return cast;
}
private void WriteAdd(OverflowCheck check)
{
const string op = "+";
var right = _valueStack.Pop();
var left = _valueStack.Pop();
if (check != OverflowCheck.None)
{
var leftStackType = StackTypeConverter.StackTypeFor(left.Type);
var rightStackType = StackTypeConverter.StackTypeFor(right.Type);
if (RequiresPointerOverflowCheck(leftStackType, rightStackType))
{
WritePointerOverflowCheckUsing64Bits(op, check, left, right);
}
else if (Requires64BitOverflowCheck(leftStackType.MetadataType, rightStackType.MetadataType))
{
/* x+y: The check algorithm for signed:
if ((y >= 0 && x > max_value - y) ||
(y < 0 && x < min_value - y)) raise_exception();
For unsigned:
if (x > max_value - y) raise_exception();
*/
if (check == OverflowCheck.Signed)
{
_writer.WriteLine("if (il2cpp_codegen_check_add_overflow((int64_t){0}, (int64_t){1}))",
left.Expression, right.Expression);
_writer.WriteLine("\t{0};", Emit.RaiseManagedException("il2cpp_codegen_get_overflow_exception()",
_runtimeMetadataAccess.MethodInfo(_methodReference)));
}
else
{
_writer.WriteLine("if ((uint64_t){0} > kIl2CppUInt64Max - (uint64_t){1})", left.Expression, right.Expression);
_writer.WriteLine("\t{0};", Emit.RaiseManagedException("il2cpp_codegen_get_overflow_exception()",
_runtimeMetadataAccess.MethodInfo(_methodReference)));
}
}
else
{
// check bounds using larger arithmetic
WriteNarrowOverflowCheckUsing64Bits(op, check, left.Expression, right.Expression);
}
}
var leftType = left.Type.WithoutModifiers();
var rightType = right.Type.WithoutModifiers();
var destType = StackAnalysisUtils.ResultTypeForAdd(leftType, rightType, Globals.TypeProvider);
WriteWarningProtectedOperation("add", left, right, destType);
}
private void WriteSub(OverflowCheck check)
{
const string op = "-";
var right = _valueStack.Pop();
var left = _valueStack.Pop();
if (check != OverflowCheck.None)
{
var leftStackType = StackTypeConverter.StackTypeFor(left.Type);
var rightStackType = StackTypeConverter.StackTypeFor(right.Type);
if (RequiresPointerOverflowCheck(leftStackType, rightStackType))
{
WritePointerOverflowCheckUsing64Bits(op, check, left, right);
}
else if (Requires64BitOverflowCheck(leftStackType.MetadataType, rightStackType.MetadataType))
{
/* x+y: The check algorithm for signed:
if ((y >= 0 && x < min_value + y) ||
(y < 0 && x > max_value + y)) raise_exception();
For unsigned:
if (x < y) raise_exception();
*/
if (check == OverflowCheck.Signed)
{
_writer.WriteLine("if (il2cpp_codegen_check_sub_overflow((int64_t){0}, (int64_t){1}))",
left.Expression, right.Expression);
_writer.WriteLine("\t{0};", Emit.RaiseManagedException("il2cpp_codegen_get_overflow_exception()",
_runtimeMetadataAccess.MethodInfo(_methodReference)));
}
else
{
_writer.WriteLine("if ((uint64_t){0} < (uint64_t){1})", left.Expression, right.Expression);
_writer.WriteLine("\t{0};", Emit.RaiseManagedException("il2cpp_codegen_get_overflow_exception()",
_runtimeMetadataAccess.MethodInfo(_methodReference)));
}
}
else
{
// check bounds using larger arithmetic
WriteNarrowOverflowCheckUsing64Bits(op, check, left.Expression, right.Expression);
}
}
var leftType = left.Type.WithoutModifiers();
var rightType = right.Type.WithoutModifiers();
var destType = StackAnalysisUtils.ResultTypeForSub(leftType, rightType, Globals.TypeProvider);
WriteWarningProtectedOperation("subtract", left, right, destType);
}
private void WriteMul(OverflowCheck check)
{
const string op = "*";
var right = _valueStack.Pop();
var left = _valueStack.Pop();
if (check != OverflowCheck.None)
{
var leftStackType = StackTypeConverter.StackTypeFor(left.Type);
var rightStackType = StackTypeConverter.StackTypeFor(right.Type);
if (RequiresPointerOverflowCheck(leftStackType, rightStackType))
{
WritePointerOverflowCheckUsing64Bits(op, check, left, right);
}
else if (Requires64BitOverflowCheck(leftStackType.MetadataType, rightStackType.MetadataType))
{
/* x*y: The check algorithm for unsigned:
if (y != 0 && (x*y)/y != x) raise_exception();
TODO: use a better algorithm without division
*/
if (check == OverflowCheck.Signed)
{
_writer.WriteLine("if (il2cpp_codegen_check_mul_overflow_i64((int64_t){0}, (int64_t){1}, kIl2CppInt64Min, kIl2CppInt64Max))",
left.Expression, right.Expression);
_writer.WriteLine("\t{0};", Emit.RaiseManagedException("il2cpp_codegen_get_overflow_exception()",
_runtimeMetadataAccess.MethodInfo(_methodReference)));
}
else
{
_writer.WriteLine("if (il2cpp_codegen_check_mul_oveflow_u64({0}, {1}))",
left.Expression, right.Expression);
_writer.WriteLine("\t{0};", Emit.RaiseManagedException("il2cpp_codegen_get_overflow_exception()",
_runtimeMetadataAccess.MethodInfo(_methodReference)));
}
}
else
{
// check bounds using larger sarithmetic
WriteNarrowOverflowCheckUsing64Bits(op, check, left.Expression, right.Expression);
}
}
var leftType = left.Type.WithoutModifiers();
var rightType = right.Type.WithoutModifiers();
var destType = StackAnalysisUtils.ResultTypeForMul(leftType, rightType, Globals.TypeProvider);
WriteWarningProtectedOperation("multiply", left, right, destType);
}
private void WritePointerOverflowCheckUsing64Bits(string op, OverflowCheck check, StackInfo left, StackInfo right)
{
WritePointerOverflowCheckUsing64Bits(op, check, left.Expression, right.Expression);
}
private void WritePointerOverflowCheckUsing64Bits(string op, OverflowCheck check, string leftExpression, string rightExpression)
{
if (check == OverflowCheck.Signed)
{
_writer.WriteLine("if (((intptr_t){1} {0} (intptr_t){2} < (intptr_t)kIl2CppIntPtrMin) || ((intptr_t){1} {0} (intptr_t){2} > (intptr_t)kIl2CppIntPtrMax))",
op, leftExpression, rightExpression);
_writer.WriteLine("\t{0};", Emit.RaiseManagedException("il2cpp_codegen_get_overflow_exception()",
_runtimeMetadataAccess.MethodInfo(_methodReference)));
}
else
{
_writer.WriteLine("if ((uintptr_t){1} {0} (uintptr_t){2} > (uintptr_t)kIl2CppUIntPtrMax)",
op, leftExpression, rightExpression);
_writer.WriteLine("\t{0};", Emit.RaiseManagedException("il2cpp_codegen_get_overflow_exception()",
_runtimeMetadataAccess.MethodInfo(_methodReference)));
}
}
private void WriteNarrowOverflowCheckUsing64Bits(string op, OverflowCheck check, string leftExpression, string rightExpression)
{
// check bounds using larger arithmetic
if (check == OverflowCheck.Signed)
{
_writer.WriteLine("if (((int64_t){1} {0} (int64_t){2} < (int64_t)kIl2CppInt32Min) || ((int64_t){1} {0} (int64_t){2} > (int64_t)kIl2CppInt32Max))",
op, leftExpression, rightExpression);
_writer.WriteLine("\t{0};", Emit.RaiseManagedException("il2cpp_codegen_get_overflow_exception()",
_runtimeMetadataAccess.MethodInfo(_methodReference)));
}
else
{
// uint32_t cast is needed to prevent sign extension when converting to uint64_t
_writer.WriteLine("if ((uint64_t)(uint32_t){1} {0} (uint64_t)(uint32_t){2} > (uint64_t)(uint32_t)kIl2CppUInt32Max)",
op, leftExpression, rightExpression);
_writer.WriteLine("\t{0};", Emit.RaiseManagedException("il2cpp_codegen_get_overflow_exception()",
_runtimeMetadataAccess.MethodInfo(_methodReference)));
}
}
private static bool Requires64BitOverflowCheck(MetadataType leftStackType, MetadataType rightStackType)
{
return Requires64BitOverflowCheck(leftStackType) || Requires64BitOverflowCheck(rightStackType);
}
private static bool Requires64BitOverflowCheck(MetadataType metadataType)
{
return metadataType == MetadataType.UInt64 || metadataType == MetadataType.Int64;
}
private bool RequiresPointerOverflowCheck(TypeReference leftStackType, TypeReference rightStackType)
{
return RequiresPointerOverflowCheck(leftStackType) || RequiresPointerOverflowCheck(rightStackType);
}
private bool RequiresPointerOverflowCheck(TypeReference type)
{
return type.IsSameType(Globals.TypeProvider.SystemIntPtr) || type.IsSameType(Globals.TypeProvider.SystemUIntPtr);
}
private void EmitLoadToken(Instruction ins)
{
var operand = ins.Operand;
var typeReference = operand as TypeReference;
if (typeReference != null)
{
StoreLocalAndPush(RuntimeTypeHandleTypeReference, $"{{ reinterpret_cast<intptr_t> ({_runtimeMetadataAccess.Il2CppTypeFor (typeReference)}) }}");
_writer.AddIncludeForTypeDefinition(RuntimeTypeHandleTypeReference);
return;
}
if (CodeGenOptions.UseTinyRuntimeBackend)
throw new InvalidOperationException($"Tiny profile does not support loading field and method handles. Offending method: '{_methodReference.FullName}' in '{_methodDefinition.DeclaringType.Module.Name}'.");
var fieldReference = operand as FieldReference;
if (fieldReference != null)
{
StoreLocalAndPush(RuntimeFieldHandleTypeReference, $"{{ reinterpret_cast<intptr_t> ({_runtimeMetadataAccess.FieldInfo (fieldReference)}) }}");
_writer.AddIncludeForTypeDefinition(RuntimeFieldHandleTypeReference);
return;
}
var methodReference = operand as MethodReference;
if (methodReference != null)
{
StoreLocalAndPush(RuntimeMethodHandleTypeReference, $"{{ reinterpret_cast<intptr_t> ({_runtimeMetadataAccess.MethodInfo (methodReference)}) }}");
_writer.AddIncludeForTypeDefinition(RuntimeMethodHandleTypeReference);
_writer.AddIncludeForTypeDefinition(_typeResolver.Resolve(methodReference.DeclaringType));
return;
}
throw new ArgumentException();
}
private void LoadField(Instruction ins, bool loadAddress = false)
{
var target = _valueStack.Pop();
var fieldReference = (FieldReference)ins.Operand;
var variableType = _typeResolver.ResolveFieldType(fieldReference);
string fieldGetterExpression;
if (loadAddress)
{
variableType = new ByReferenceType(variableType);
fieldGetterExpression = Globals.Naming.ForFieldAddressGetter(fieldReference);
}
else
{
if (fieldReference.Name == WellKnown.FieldNames.IntPtrValue && TypeReferenceEqualityComparer.AreEqual(fieldReference.DeclaringType, IntPtrTypeReference))
{
StoreLocalAndPush(SystemIntPtr, target.Type.IsValueType() ? target.Expression : Emit.Dereference(target.Expression));
return;
}
if (fieldReference.Name == WellKnown.FieldNames.UIntPtrPointer && TypeReferenceEqualityComparer.AreEqual(fieldReference.DeclaringType, UIntPtrTypeReference))
{
StoreLocalAndPush(SystemUIntPtr, target.Type.IsValueType() ? target.Expression : Emit.Dereference(target.Expression));
return;
}
fieldGetterExpression = Globals.Naming.ForFieldGetter(fieldReference);
}
if (target.Expression != WellKnown.ParameterNames.This)
_nullCheckSupport.WriteNullCheckIfNeeded(target);
var local = NewTemp(variableType);
_valueStack.Push(new StackInfo(local));
var rhs = Emit.Call(
target.Type.IsValueType() && !target.Type.IsIntegralPointerType() ?
Emit.Dot(target.Expression, fieldGetterExpression) :
Emit.Arrow(CastReferenceTypeOrNativeIntIfNeeded(target, _typeResolver.Resolve(fieldReference.DeclaringType)), fieldGetterExpression));
// cast because in shared methods with constraints, the target may not be the correct type but it's still safe
if (_sharingType == SharingType.Shared)
rhs = Emit.Cast(variableType, rhs);
var emitString = Emit.Assign(local.IdentifierExpression, rhs);
_writer.WriteLine(emitString + ";");
EmitMemoryBarrierIfNecessary(fieldReference);
}
private void MonoCopyValueGet(FieldReference field, StackInfo src, StackInfo dest)
{
if (field.FieldType.IsByReference)
{
_writer.WriteLine("void **p = (void**){0}", dest.Expression);
_writer.WriteLine("*p = &{0}", src.Expression);
return;
}
switch (field.FieldType.MetadataType)
{
case MetadataType.Boolean:
case MetadataType.Byte:
case MetadataType.SByte:
_writer.WriteLine("uint8_t *p = (uint8_t*)&{0};", dest.Expression);
_writer.WriteLine("*p = {0} ? *(uint8_t*){1} : 0;", src.Expression, src.Expression);
return;
case MetadataType.Int16:
case MetadataType.UInt16:
case MetadataType.Char:
_writer.WriteLine("uint16_t *p = (uint16_t*)&{0};", dest.Expression);
_writer.WriteLine("*p = {0} ? *(uint16_t*){1} : 0;", src.Expression, src.Expression);
return;
case MetadataType.Int32:
case MetadataType.UInt32:
_writer.WriteLine("uint32_t *p = (uint32_t*)&{0};", dest.Expression);
_writer.WriteLine("*p = {0} ? *(uint32_t*){1} : 0;", src.Expression, src.Expression);
return;
case MetadataType.Int64:
case MetadataType.UInt64:
_writer.WriteLine("uint64_t *p = (uint64_t*)&{0};", dest.Expression);
_writer.WriteLine("*p = {0} ? *(uint64_t*){1} : 0;", src.Expression, src.Expression);
return;
case MetadataType.Single:
_writer.WriteLine("float *p = (float*)&{0};", dest.Expression);
_writer.WriteLine("*p = {0} ? *(float*){1} : 0;", src.Expression, src.Expression);
return;
case MetadataType.Double:
_writer.WriteLine("double *p = (double*)&{0};", dest.Expression);
_writer.WriteLine("*p = {0} ? *(double*){1} : 0;", src.Expression, src.Expression);
return;
case MetadataType.String:
case MetadataType.Array:
case MetadataType.Class:
case MetadataType.Object:
_writer.WriteLine("{0} = ({1})*(MonoObject**){2};", dest.Expression, Globals.Naming.ForVariable(dest.Type), src.Expression);
return;
default:
_writer.WriteLine("mono_copy_value({0}->type, &{1}, {2}, 1);", _runtimeMetadataAccess.FieldInfo(field), dest.Expression, src.Expression);
return;
}
}
private void MonoCopyValueSet(FieldReference field, StackInfo src, StackInfo dest)
{
if (field.FieldType.IsByReference)
{
_writer.WriteLine("void **p = (void**){0}", dest.Expression);
_writer.WriteLine("*p = &{0}", src.Expression);
return;
}
switch (field.FieldType.MetadataType)
{
case MetadataType.Boolean:
case MetadataType.Byte:
case MetadataType.SByte:
_writer.WriteLine("uint8_t *p = (uint8_t*){0};", dest.Expression);
_writer.WriteLine("*p = *(uint8_t*)&{1};", src.Expression, src.Expression);
return;
case MetadataType.Int16:
case MetadataType.UInt16:
case MetadataType.Char:
_writer.WriteLine("uint16_t *p = (uint16_t*){0};", dest.Expression);
_writer.WriteLine("*p = (uint16_t){1};", src.Expression, src.Expression);
return;
case MetadataType.Int32:
case MetadataType.UInt32:
_writer.WriteLine("uint32_t *p = (uint32_t*){0};", dest.Expression);
_writer.WriteLine("*p = (uint32_t){1};", src.Expression, src.Expression);
return;
case MetadataType.Int64:
case MetadataType.UInt64:
_writer.WriteLine("uint64_t *p = (uint64_t*){0};", dest.Expression);
_writer.WriteLine("*p = (uint64_t){1};", src.Expression, src.Expression);
return;
case MetadataType.Single:
_writer.WriteLine("float *p = (float*){0};", dest.Expression);
_writer.WriteLine("*p = (float){1};", src.Expression, src.Expression);
return;
case MetadataType.Double:
_writer.WriteLine("double *p = (double*){0};", dest.Expression);
_writer.WriteLine("*p = (double){1};", src.Expression, src.Expression);
return;
case MetadataType.String:
case MetadataType.Array:
case MetadataType.Class:
case MetadataType.Object:
_writer.WriteLine("*(MonoObject**){0} = (MonoObject*){1};", dest.Expression, src.Expression);
return;
default:
_writer.WriteLine("mono_copy_value({0}->type, {1}, &{2}, 1);", _runtimeMetadataAccess.FieldInfo(field), dest.Expression, src.Expression);
return;
}
}
private void StaticFieldAccess(Instruction ins)
{
var fieldReference = (FieldReference)ins.Operand;
// literal values should always be embedded rather than accessed via the field itself
if (fieldReference.Resolve().IsLiteral)
throw new Exception("literal values should always be embedded rather than accessed via the field itself");
WriteCallToClassAndInitializerAndStaticConstructorIfNeeded(fieldReference.DeclaringType, _methodDefinition, _runtimeMetadataAccess);
ThrowIfAccessIsForbidden(ins, fieldReference);
var resolvedFieldType = _typeResolver.ResolveFieldType(fieldReference);
var typeStaticsAccess = TypeStaticsExpressionFor(fieldReference, _typeResolver, _runtimeMetadataAccess);
TypeReference declaringType;
if (fieldReference.DeclaringType.IsGenericInstance)
declaringType = fieldReference.DeclaringType;
else
declaringType = _typeResolver.Resolve(fieldReference.DeclaringType);
var monoCodegenFunc = "il2cpp_codegen_mono_get_static_field_address";
if (fieldReference.IsThreadStatic())
monoCodegenFunc = "il2cpp_codegen_mono_get_thread_static_field_address";
if (ins.OpCode.Code == Code.Stsfld)
{
var value = _valueStack.Pop();
EmitMemoryBarrierIfNecessary();
if (fieldReference.Name == "Zero" &&
(TypeReferenceEqualityComparer.AreEqual(resolvedFieldType, IntPtrTypeReference) || TypeReferenceEqualityComparer.AreEqual(resolvedFieldType, UIntPtrTypeReference)))
{
// avoid store to (U)IntPtr.Zero since we treat loads of this field as intrinsic (0) anyway.
return;
}
if (CodeGenOptions.MonoRuntime)
{
// We need a temporary value here so we can take the address of it to pass it to the Mono static field setter API.
var temporaryValue = NewTemp(resolvedFieldType);
_writer.WriteLine("{0} = {1};", temporaryValue.IdentifierExpression, WriteExpressionAndCastIfNeeded(resolvedFieldType, value));
_writer.WriteLine("{");
var address = NewTemp(new ByReferenceType(Globals.TypeProvider.SystemVoid));
_writer.WriteLine("{0};", address.IdentifierExpression);
_writer.WriteLine("{0} = {1}({2}, {3});", address.Expression, monoCodegenFunc, _runtimeMetadataAccess.StaticData(declaringType),
_runtimeMetadataAccess.FieldInfo(fieldReference));
MonoCopyValueSet(fieldReference, temporaryValue, address);
_writer.WriteLine("}");
}
else
{
_writer.WriteLine(
Statement.Expression(
Emit.Call(
string.Format("{0}{1}", typeStaticsAccess, Globals.Naming.ForFieldSetter(fieldReference)),
WriteExpressionAndCastIfNeeded(resolvedFieldType, value))));
}
}
else
{
if (ins.OpCode.Code == Code.Ldsflda)
{
var variableType = new ByReferenceType(resolvedFieldType);
if (IsFieldAccessDirectlyToValueMetadata(fieldReference))
{
PushExpression(variableType, $"il2cpp_codegen_get_field_data({_runtimeMetadataAccess.FieldInfo (fieldReference)})");
}
else
{
string fieldAddressExpression;
if (CodeGenOptions.MonoRuntime)
{
fieldAddressExpression = Emit.Cast(Globals.Naming.ForVariable(variableType), string.Format("{0}({1}, {2})", monoCodegenFunc, _runtimeMetadataAccess.StaticData(declaringType),
_runtimeMetadataAccess.FieldInfo(fieldReference)));
}
else
{
fieldAddressExpression = Emit.Call(string.Format("{0}{1}", typeStaticsAccess, Globals.Naming.ForFieldAddressGetter(fieldReference)));
}
PushExpression(variableType, fieldAddressExpression);
}
}
else
{
if (fieldReference.Name == "Zero" && TypeReferenceEqualityComparer.AreEqual(resolvedFieldType, IntPtrTypeReference))
{
PushExpression(SystemIntPtr, "0");
return;
}
if (fieldReference.Name == "Zero" && TypeReferenceEqualityComparer.AreEqual(resolvedFieldType, UIntPtrTypeReference))
{
PushExpression(SystemUIntPtr, "0");
return;
}
var local = NewTemp(resolvedFieldType);
if (CodeGenOptions.MonoRuntime)
{
_writer.WriteLine("{0};", local.IdentifierExpression);
_writer.WriteLine("{");
var address = NewTemp(new ByReferenceType(Globals.TypeProvider.SystemVoid));
_writer.WriteLine("{0};", address.IdentifierExpression);
_writer.WriteLine("{0} = {1}({2}, {3});", address.Expression, monoCodegenFunc, _runtimeMetadataAccess.StaticData(declaringType),
_runtimeMetadataAccess.FieldInfo(fieldReference));
MonoCopyValueGet(fieldReference, address, local);
_writer.WriteLine("}");
}
else
{
_writer.WriteLine("{0};", Emit.Assign(local.IdentifierExpression, Emit.Call(string.Format("{0}{1}", typeStaticsAccess, Globals.Naming.ForFieldGetter(fieldReference)))));
}
_valueStack.Push(new StackInfo(local));
}
EmitMemoryBarrierIfNecessary();
}
}
static bool IsFieldAccessDirectlyToValueMetadata(FieldReference fieldReference)
{
// The C# compiler can optimize static field data access by loading the field data
// directly from the assembly. If this is the case, RVA will be non-zero. In other static
// field accesses, RVA will be zero.
var fieldDefinition = fieldReference.Resolve();
return fieldDefinition != null && fieldDefinition.RVA != 0;
}
private void ThrowIfAccessIsForbidden(Instruction ins, FieldReference fieldReference)
{
#if IL2CPP_GC_BUMP
// Note: loading field address may lead to storing values in that address later
if (ins.OpCode.Code != Code.Stsfld && ins.OpCode.Code != Code.Ldsflda)
return;
if (!CodeGenOptions.UseTinyRuntimeBackend)
return;
// We allow storing objects in static fields in static constructors because static constructors get invoked at startup and the managed heap cursor will never go back before them
if (_methodDefinition.IsStaticConstructor())
return;
var typeResolver = TypeResolver.For(_typeResolver.Resolve(fieldReference.DeclaringType));
if (typeResolver.Resolve(fieldReference.FieldType).IsSuitableForStaticFieldInTinyProfile())
return;
var message = new StringBuilder();
message.AppendLine("Tiny profile does not support storing reference types in static fields (except in static constructors).");
message.AppendLine($"\tOffending method: '{_methodReference.FullName}' in '{_methodDefinition.DeclaringType.Module.Name}'");
message.AppendLine($"\tOffending field: '{fieldReference.FullName}'");
throw new InvalidOperationException(message.ToString());
#endif
}
void WriteCallToClassAndInitializerAndStaticConstructorIfNeeded(TypeReference type, MethodDefinition invokingMethod, IRuntimeMetadataAccess runtimeMetadataAccess)
{
if (Globals.NoLazyStaticConstructors || !type.HasStaticConstructor())
return;
if (_classesAlreadyInitializedInBlock.Contains(type))
return;
_classesAlreadyInitializedInBlock.Add(type);
var cctor = type.Resolve().Methods.Single(Extensions.IsStaticConstructor);
if (invokingMethod != null && cctor == invokingMethod)
return;
// TODO: We can optimize this to not call this at all if the type has no cctor, but we need to
// ensure that IL2CPP_RUNTIME_CLASS_INIT will do nothing but run the cctor.
_writer.WriteLine(Statement.Expression(Emit.Call("IL2CPP_RUNTIME_CLASS_INIT", runtimeMetadataAccess.StaticData(type))));
}
internal static string TypeStaticsExpressionFor(FieldReference fieldReference, TypeResolver typeResolver, IRuntimeMetadataAccess runtimeMetadataAccess)
{
var declaringType = typeResolver.Resolve(fieldReference.DeclaringType);
if (!CodeGenOptions.UseTinyRuntimeBackend)
{
var typeInfo = runtimeMetadataAccess.StaticData(fieldReference.DeclaringType);
if (fieldReference.IsThreadStatic())
return string.Format("(({0}*)il2cpp_codegen_get_thread_static_data({1}))->",
Globals.Naming.ForThreadFieldsStruct(declaringType),
typeInfo);
return string.Format("(({0}*)il2cpp_codegen_static_fields_for({1}))->",
Globals.Naming.ForStaticFieldsStruct(declaringType),
typeInfo);
}
else
{
if (fieldReference.IsThreadStatic())
throw new NotSupportedException("There is currently no special handling for thread static variables in Tiny");
if (fieldReference.IsNormalStatic())
return "((" + Globals.Naming.ForStaticFieldsStruct(declaringType) + "*)" + Globals.Naming.ForStaticFieldsStructStorage(declaringType) + ")->";
}
return Globals.Naming.ForTypeNameOnly(declaringType) + "::";
}
private void LoadIndirect(TypeReference valueType, TypeReference storageType)
{
var address = _valueStack.Pop();
var variable = NewTemp(storageType);
_writer.WriteLine("{0} = {1};", variable.IdentifierExpression, GetLoadIndirectExpression(new PointerType(valueType), address.Expression));
if (_thisInstructionIsVolatile)
EmitMemoryBarrierIfNecessary();
_valueStack.Push(new StackInfo(variable));
}
private void LoadIndirectReference()
{
var address = _valueStack.Pop();
StoreLocalAndPush(GetPointerOrByRefType(address), GetLoadIndirectExpression(address.Type, address.Expression));
}
private void LoadIndirectNativeInteger()
{
var address = _valueStack.Pop();
var elementType = GetPointerOrByRefType(address);
if (elementType.IsIntegralPointerType())
PushExpression(elementType, string.Format("(*({0}))", address.Expression));
else
PushLoadIndirectExpression(SystemIntPtr, new PointerType(SystemIntPtr), address.Expression);
}
private void PushLoadIndirectExpression(TypeReference expressionType, TypeReference castType, string expression)
{
PushExpression(expressionType, GetLoadIndirectExpression(castType, expression));
}
private static string GetLoadIndirectExpression(TypeReference castType, string expression)
{
return string.Format("*(({0}){1})", Globals.Naming.ForVariable(castType), expression);
}
private static TypeReference GetPointerOrByRefType(StackInfo address)
{
if (TypeReferenceEqualityComparer.AreEqual(Globals.TypeProvider.SystemIntPtr, address.Type) ||
TypeReferenceEqualityComparer.AreEqual(Globals.TypeProvider.SystemUIntPtr, address.Type))
return Globals.TypeProvider.SystemVoid;
var typeReference = address.Type;
typeReference = typeReference.WithoutModifiers();
var pointerType = typeReference as PointerType;
if (pointerType != null)
return pointerType.ElementType;
var byRefType = typeReference as ByReferenceType;
if (byRefType != null)
return byRefType.ElementType;
throw new Exception();
}
private void ConvertToNaturalInt(TypeReference pointerType)
{
var value = _valueStack.Pop();
PushExpression(
pointerType,
string.Format(
"(({0}){1})",
Globals.Naming.ForVariable(pointerType),
value.Expression));
}
private void ConvertToNaturalIntWithOverflow<TMaxValueType>(TypeReference pointerType, bool treatInputAsUnsigned, TMaxValueType maxValue)
{
WriteCheckForOverflow(treatInputAsUnsigned, maxValue);
ConvertToNaturalInt(pointerType);
}
private void WriteCheckForOverflow<TMaxValue>(bool treatInputAsUnsigned, TMaxValue maxValue)
{
var value = _valueStack.Peek();
if (value.Type.IsSameType(DoubleTypeReference) || value.Type.IsSameType(SingleTypeReference))
{
_writer.WriteLine("if ({0} > (double)({1})) {2};", value.Expression, maxValue, Emit.RaiseManagedException("il2cpp_codegen_get_overflow_exception()"));
}
else
{
if (treatInputAsUnsigned)
_writer.WriteLine("if ((uint64_t)({0}) > {1}) {2};",
value.Expression,
maxValue,
Emit.RaiseManagedException("il2cpp_codegen_get_overflow_exception()",
_runtimeMetadataAccess.MethodInfo(_methodReference)));
else
_writer.WriteLine("if ((int64_t)({0}) > {1}) {2};",
value.Expression,
maxValue,
Emit.RaiseManagedException("il2cpp_codegen_get_overflow_exception()",
_runtimeMetadataAccess.MethodInfo(_methodReference)));
}
}
private void LoadElemAndPop(TypeReference typeReference)
{
var index = _valueStack.Pop();
var array = _valueStack.Pop();
LoadElem(array, typeReference, index);
}
private void StoreArg(Instruction ins)
{
var value = _valueStack.Pop();
var parameter = (ParameterReference)ins.Operand;
// Hack to work around a bug. This is encountered when converting FSharp.Core
if (parameter.Index == -1)
{
WriteAssignment(WellKnown.ParameterNames.This, _typeResolver.ResolveParameterType(_methodReference, parameter), value);
}
else
{
WriteAssignment(Globals.Naming.ForParameterName(parameter), _typeResolver.ResolveParameterType(_methodReference, parameter), value);
}
}
private void LoadElem(StackInfo array, TypeReference objectType, StackInfo index)
{
_nullCheckSupport.WriteNullCheckIfNeeded(array);
var variable = NewTemp(index.Type);
_writer.WriteLine("{0} = {1};", variable.IdentifierExpression, index.Expression);
_arrayBoundsCheckSupport.RecordArrayBoundsCheckEmitted();
var loadArrayElementExpression = Emit.LoadArrayElement(array.Expression, variable.Expression, _arrayBoundsCheckSupport.ShouldEmitBoundsChecksForMethod());
if (!TypeReferenceEqualityComparer.AreEqual(array.Type.GetElementType(), objectType))
loadArrayElementExpression = Emit.Cast(objectType, loadArrayElementExpression);
StoreLocalAndPush(objectType, loadArrayElementExpression);
}
private void StoreIndirect(TypeReference type)
{
var value = _valueStack.Pop();
var address = _valueStack.Pop();
EmitMemoryBarrierIfNecessary();
string target = Emit.Cast(type.MakePointerType(), address.Expression);
string newValue = Emit.Cast(type, value.Expression);
_writer.WriteLine("*({0}) = {1};", target, newValue);
_writer.WriteWriteBarrierIfNeeded(type, target, newValue);
}
private void StoreElement(StackInfo array, StackInfo index, StackInfo value, bool emitElementTypeCheck)
{
_nullCheckSupport.WriteNullCheckIfNeeded(array);
// It is possible to cast null to an array type and to index it. This will always throw a NullReferenceException
// so we don't even try to emit code for it. Just return after we have emitted the exception check.
if (array.Expression == WellKnown.Constants.Null)
return;
if (emitElementTypeCheck)
_writer.WriteLine(Emit.ArrayElementTypeCheck(array.Expression, value.Expression));
var elementType = ArrayUtilities.ArrayElementTypeOf(array.Type);
_arrayBoundsCheckSupport.RecordArrayBoundsCheckEmitted();
_writer.WriteLine("{0};", Emit.StoreArrayElement(array.Expression, index.Expression, Emit.Cast(elementType, value.Expression), _arrayBoundsCheckSupport.ShouldEmitBoundsChecksForMethod()));
}
private void LoadNull()
{
_valueStack.Push(new StackInfo("NULL", ObjectTypeReference));
}
private void WriteLdarg(int index, InstructionBlock block, Instruction ins)
{
if (_methodDefinition.HasThis)
index--;
if (index < 0)
{
TypeReference thisType = _typeResolver.Resolve(_methodReference.DeclaringType);
if (thisType.IsValueType())
thisType = new ByReferenceType(thisType);
_valueStack.Push(new StackInfo(WellKnown.ParameterNames.This, thisType));
return;
}
var parameterType = _typeResolver.ResolveParameterType(_methodReference, _methodReference.Parameters[index]);
var local = NewTemp(parameterType);
var loadExpression = Globals.Naming.ForParameterName(_methodDefinition.Parameters[index]);
// Don't write the actual assignment if we are going to optimize away the next instruction that uses it.
// This avoids a C++ compiler warning. In some cases we get a sequence like this:
// ldarg.1
// ldobj !!
// box !!T
// brtrue IL_001b
// so we need to look two instructions ahead.
if (!CanApplyValueTypeBoxBranchOptimizationToInstruction(ins.Next, block) && (ins.Next.OpCode.Code != Code.Ldobj || !CanApplyValueTypeBoxBranchOptimizationToInstruction(ins.Next.Next, block)))
_writer.WriteLine("{0};", Emit.Assign(local.IdentifierExpression, loadExpression));
_valueStack.Push(new StackInfo(local));
}
private void WriteNumericConversion(TypeReference inputType, TypeReference outputType)
{
var right = _valueStack.Pop();
string cast = string.Empty;
if ((TypeReferenceEqualityComparer.AreEqual(right.Type, SingleTypeReference) ||
TypeReferenceEqualityComparer.AreEqual(right.Type, DoubleTypeReference)) &&
inputType.IsUnsignedIntegralType())
{
PushExpression(
outputType,
$"il2cpp_codegen_cast_floating_point<{Globals.Naming.ForVariable(inputType)}, {Globals.Naming.ForVariable(outputType)}, {Globals.Naming.ForVariable(right.Type)}>({right})");
}
else
{
if (right.Type.MetadataType == MetadataType.Pointer)
cast = "(intptr_t)"; //ARM64 hack
PushExpression(
outputType,
string.Format(
"(({0})(({1}){2}{3}))",
Globals.Naming.ForVariable(outputType),
Globals.Naming.ForVariable(inputType),
cast,
right));
}
}
private void WriteNumericConversion(TypeReference typeReference)
{
WriteNumericConversion(typeReference, typeReference);
}
private void WriteNumericConversionWithOverflow<TMaxValue>(TypeReference typeReference, bool treatInputAsUnsigned, TMaxValue maxValue)
{
WriteCheckForOverflow(treatInputAsUnsigned, maxValue);
WriteNumericConversion(typeReference);
}
private void WriteNumericConversionI8()
{
// Conv_I8 needs a special conversion, since it has to handle both Int32 and Int64 inputs and
// make sure they they are sign extended in the C++ code correctly.
if (_valueStack.Peek().Type.MetadataType == MetadataType.UInt32)
WriteNumericConversion(Int32TypeReference);
WriteNumericConversion(Int64TypeReference, Int64TypeReference);
}
private void WriteNumericConversionU8()
{
// Conv_U8 needs a special conversion, since it has to handle both Int32 and Int64 inputs and
// make sure they they are not sign extended in the C++ code incorrectly.
if (_valueStack.Peek().Type.IsSameType(Int32TypeReference))
WriteNumericConversion(UInt32TypeReference);
WriteNumericConversion(UInt64TypeReference, Int64TypeReference);
}
private void WriteNumericConversionFloat(TypeReference outputType)
{
// When .NET loads an integer on the stack, it always loads it as a signed value.
// To get the conversion to a load correct, we need to respect this.
if (_valueStack.Peek().Type.MetadataType == MetadataType.UInt32)
WriteNumericConversion(Int32TypeReference, outputType);
else if (_valueStack.Peek().Type.MetadataType == MetadataType.UInt64)
WriteNumericConversion(Int64TypeReference, outputType);
WriteNumericConversion(outputType);
}
private void WriteLdloc(int index, InstructionBlock block, Instruction ins)
{
var variable = _methodDefinition.Body.Variables[index];
var variableType = _typeResolver.ResolveVariableType(_methodReference, variable);
var local = NewTemp(variableType);
TypeReference ctor;
_typeReferences.TryGetValue(variable, out ctor);
_valueStack.Push(new StackInfo(local, constructorType: ctor ?? local.ConstructorType));
// Don't write the actual assignment if we are going to optimize away the next instruction that uses it.
// This avoids a C++ compiler warning.
if (!CanApplyValueTypeBoxBranchOptimizationToInstruction(ins.Next, block))
_writer.WriteLine("{0};", Emit.Assign(local.IdentifierExpression, Globals.Naming.ForVariableName(variable)));
}
private void WriteStloc(int index)
{
var value = _valueStack.Pop();
var variableDefinition = _methodDefinition.Body.Variables[index];
_typeReferences[variableDefinition] = value.ConstructorType;
var typeWithoutModifiers = variableDefinition.VariableType.WithoutModifiers();
if (typeWithoutModifiers.IsPointer || typeWithoutModifiers.IsByReference || RequiresContravariantCastToStore(typeWithoutModifiers, value.Type) || RequiresIntegralCastToStore(typeWithoutModifiers, value.Type))
{
if (typeWithoutModifiers.IsIntegralType() && value.Type.IsPointer || typeWithoutModifiers.IsPointer && value.Type.IsIntegralType())
_writer.WriteLine("{0} = ({1})(intptr_t){2};", Globals.Naming.ForVariableName(variableDefinition), Globals.Naming.ForVariable(_typeResolver.Resolve(typeWithoutModifiers)), value);
else
_writer.WriteLine("{0} = ({1}){2};", Globals.Naming.ForVariableName(variableDefinition), Globals.Naming.ForVariable(_typeResolver.Resolve(typeWithoutModifiers)), value);
}
else
{
WriteAssignment(Globals.Naming.ForVariableName(variableDefinition), _typeResolver.Resolve(variableDefinition.VariableType), value);
}
}
private bool RequiresContravariantCastToStore(TypeReference destinationVariable, TypeReference sourceVariableType)
{
// This logic is not exhaustive. After some simple cases we know don't need a cast,
// we are going to make our lives easier and trust that the code the managed compiler
// generated was correct
if (!destinationVariable.IsGenericInstance || !sourceVariableType.IsGenericInstance)
return false;
if (TypeReferenceEqualityComparer.AreEqual(destinationVariable, sourceVariableType))
return false;
return true;
}
private int IntegralSizeInBytes(TypeReference integerType)
{
if (!integerType.IsIntegralType())
throw new ArgumentException("Input type must be integral type", "integerType");
switch (integerType.MetadataType)
{
case MetadataType.SByte:
case MetadataType.Byte:
return 1;
case MetadataType.Int16:
case MetadataType.UInt16:
return 2;
case MetadataType.Int32:
case MetadataType.UInt32:
return 4;
case MetadataType.Int64:
case MetadataType.UInt64:
return 8;
default:
return Int32.MaxValue;
}
}
private bool RequiresIntegralCastToStore(TypeReference destinationVariable, TypeReference sourceVariableType)
{
if (TypeReferenceEqualityComparer.AreEqual(destinationVariable, sourceVariableType))
return false;
if (!destinationVariable.IsIntegralType())
return false;
if (IntegralSizeInBytes(destinationVariable) >= 4)
return false;
return true;
}
private void GenerateConditional(string op, Signedness signedness, bool negate = false)
{
PushExpression(Int32TypeReference, ConditionalExpressionFor(op, signedness, negate) + "? 1 : 0");
}
private string ConditionalExpressionFor(string cppOperator, Signedness signedness, bool negate)
{
var right = _valueStack.Pop();
var left = _valueStack.Pop();
// Check for some cases we can simplify. This fixes some warningsAsErrors on clang
if (right.Expression == "0" && signedness == Signedness.Unsigned)
{
if (cppOperator == "<")
return negate ? "true" : "false";
if (cppOperator == ">=")
return negate ? "false" : "true";
}
var leftCast = CastExpressionForOperandOfComparision(signedness, left);
var rightCast = CastExpressionForOperandOfComparision(signedness, right);
if (IsNonPointerReferenceType(right) && IsNonPointerReferenceType(left))
{
rightCast = PrependCastToObject(rightCast);
leftCast = PrependCastToObject(leftCast);
}
var conditionalExpression = string.Format(
"(({0}{1}) {2} ({3}{4}))",
leftCast,
left.Expression,
cppOperator,
rightCast,
right.Expression);
return negate ? string.Format("(!{0})", conditionalExpression) : conditionalExpression;
}
private static bool IsNonPointerReferenceType(StackInfo stackEntry)
{
return !stackEntry.Type.IsValueType() && !stackEntry.Type.IsPointer;
}
private static string PrependCastToObject(string expression)
{
return string.Format("({0}*){1}", Globals.Naming.ForType(Globals.TypeProvider.SystemObject), expression);
}
private string CastExpressionForOperandOfComparision(Signedness signedness, StackInfo left)
{
return "(" + Globals.Naming.ForVariable(TypeForComparison(signedness, left.Type)) + ")";
}
TypeReference TypeForComparison(Signedness signedness, TypeReference type)
{
var stackTypeFor = StackTypeConverter.StackTypeFor(type);
if (stackTypeFor.IsSameType(Globals.TypeProvider.SystemIntPtr))
{
//The CIL spec is very unclear what we should do here. You would think that for signed comparison opcodes,
//we need to interpet the values on the stack as signed values, since things on the stack have no inherit signedness.
//this is what we do everywhere in this function. however, when the operands being operated on are pointers,
//in that case the .net runtime actually treats them as unsigned (even for Signed-Branch-IfGreater opcodes). weird.
//It turns out mono doesn't match .net here however, so for now I'm choosing mono behaviour as that's more pure and less hacked
return signedness == Signedness.Signed ? SystemIntPtr : SystemUIntPtr;
}
switch (stackTypeFor.MetadataType)
{
case MetadataType.Int32:
return signedness == Signedness.Signed ? Int32TypeReference : UInt32TypeReference;
case MetadataType.Int64:
return signedness == Signedness.Signed ? Int64TypeReference : UInt64TypeReference;
case MetadataType.UIntPtr:
case MetadataType.IntPtr:
return signedness == Signedness.Signed ? IntPtrTypeReference : UIntPtrTypeReference;
case MetadataType.Pointer:
case MetadataType.ByReference:
return signedness == Signedness.Signed ? SystemIntPtr : SystemUIntPtr;
default:
return type;
}
}
private void GenerateConditionalJump(InstructionBlock block, Instruction ins, bool isTrue)
{
var targetInstruction = (Instruction)ins.Operand;
var left = _valueStack.Pop();
if (_valueStack.Count == 0)
{
using (NewIfBlock(string.Format("{0}{1}", isTrue ? "" : "!", left.Expression)))
WriteJump(targetInstruction);
return;
}
WriteGlobalVariableAssignmentForLeftBranch(block, targetInstruction);
using (NewIfBlock(string.Format("{0}{1}", isTrue ? "" : "!", left.Expression)))
{
WriteGlobalVariableAssignmentForRightBranch(block, targetInstruction);
WriteJump(targetInstruction);
}
}
private void WriteGlobalVariableAssignmentForRightBranch(InstructionBlock block, Instruction targetInstruction)
{
var rightInputVariables = _stackAnalysis.InputVariablesFor(block.Successors.Single(b => b.First.Offset == targetInstruction.Offset));
WriteAssignGlobalVariables(rightInputVariables);
}
private void WriteGlobalVariableAssignmentForLeftBranch(InstructionBlock block, Instruction targetInstruction)
{
var leftInputVariables = _stackAnalysis.InputVariablesFor(block.Successors.Single(b => b.First.Offset != targetInstruction.Offset));
WriteAssignGlobalVariables(leftInputVariables);
}
private void WriteGlobalVariableAssignmentForFirstSuccessor(InstructionBlock block, Instruction targetInstruction)
{
var inputVariables = _stackAnalysis.InputVariablesFor(block.Successors.First(b => b.First.Offset == targetInstruction.Offset));
WriteAssignGlobalVariables(inputVariables);
}
enum Signedness
{
Signed,
Unsigned
}
private void GenerateConditionalJump(InstructionBlock block, Instruction ins, string cppOperator, Signedness signedness, bool negate = false)
{
var conditionalExpression = ConditionalExpressionFor(cppOperator, signedness, negate);
var targetInstruction = (Instruction)ins.Operand;
if (_valueStack.Count == 0)
{
using (NewIfBlock(conditionalExpression))
WriteJump(targetInstruction);
return;
}
var leftInputVariables = _stackAnalysis.InputVariablesFor(block.Successors.Single(b => b.First.Offset != targetInstruction.Offset));
var rightInputVariables = _stackAnalysis.InputVariablesFor(block.Successors.Single(b => b.First.Offset == targetInstruction.Offset));
WriteAssignGlobalVariables(leftInputVariables);
using (NewIfBlock(conditionalExpression))
{
WriteAssignGlobalVariables(rightInputVariables);
WriteJump(targetInstruction);
}
}
private void WriteAssignGlobalVariables(GlobalVariable[] globalVariables)
{
if (globalVariables.Length != _valueStack.Count)
throw new ArgumentException("Invalid global variables count", "globalVariables");
var stackIndex = 0;
foreach (var stackInfo in _valueStack)
{
var globalVariable = globalVariables.Single(v => v.Index == stackIndex);
if (stackInfo.Type.FullName != globalVariable.Type.FullName)
_writer.WriteLine("{0} = (({1}){2}({3}));",
globalVariable.VariableName,
Globals.Naming.ForVariable(_typeResolver.Resolve(globalVariable.Type)),
(stackInfo.Type.MetadataType == MetadataType.Pointer) ? "(intptr_t)" : "", //ARM64 hack
stackInfo.Expression);
else
_writer.WriteLine("{0} = {1};", globalVariable.VariableName, stackInfo.Expression);
++stackIndex;
}
}
private void WriteWarningProtectedOperation(TypeReference destType, string lcast, string left, string rcast, string right, string name)
{
PushExpression(
destType,
string.Format("({0})il2cpp_codegen_{1}({2}{3}, {4}{5})",
Globals.Naming.ForVariable(destType),
name,
lcast,
left,
rcast,
right));
}
private void WriteBinaryOperation(TypeReference destType, string lcast, string left, string op, string rcast, string right)
{
PushExpression(
destType,
string.Format("({0})({1}{2}{3}{4}{5})",
Globals.Naming.ForVariable(destType),
lcast,
left,
op,
rcast,
right));
}
private void WriteRemainderOperation()
{
var right = _valueStack.Pop();
var left = _valueStack.Pop();
if (right.Type.MetadataType == MetadataType.Single ||
left.Type.MetadataType == MetadataType.Single)
{
PushExpression(SingleTypeReference, string.Format("fmodf({0}, {1})", left.Expression, right.Expression));
return;
}
if (right.Type.MetadataType == MetadataType.Double ||
left.Type.MetadataType == MetadataType.Double)
{
PushExpression(DoubleTypeReference, string.Format("fmod({0}, {1})", left.Expression, right.Expression));
return;
}
WriteBinaryOperation("%", left, right, left.Type);
}
private void WriteBinaryOperationUsingLargestOperandTypeAsResultType(string op)
{
var right = _valueStack.Pop();
var left = _valueStack.Pop();
WriteBinaryOperation(op, left, right, StackAnalysis.StackAnalysisUtils.CorrectLargestTypeFor(left.Type, right.Type, Globals.TypeProvider));
}
private void WriteBinaryOperationUsingLeftOperandTypeAsResultType(string op)
{
var right = _valueStack.Pop();
var left = _valueStack.Pop();
WriteBinaryOperation(op, left, right, left.Type);
}
private void WriteBinaryOperation(string op, StackInfo left, StackInfo right, TypeReference resultType)
{
var rcast = CastExpressionForBinaryOperator(right);
var lcast = CastExpressionForBinaryOperator(left);
// The StackTypeConverter will convert pointer types to intptr_t, which is correct for
// arithmetic operations on pointer types. In this case though, we need to retain the pointer
// type, as the result might be used to pass to another method or to access a field.
if (!resultType.IsPointer)
{
try
{
resultType = StackTypeConverter.StackTypeFor(resultType);
}
catch (ArgumentException)
{
//while we're migrating between how to deal with types for stackvalues, we will use the StackType
//as a returntype if there is one, and if there's not, we fallback to our old behaviour of passing
//the more informationrich TypeReference.
}
}
WriteBinaryOperation(resultType, lcast, left.Expression, op, rcast, right.Expression);
}
private void WriteWarningProtectedOperation(string name, StackInfo left, StackInfo right, TypeReference resultType)
{
var rcast = CastExpressionForBinaryOperator(right);
var lcast = CastExpressionForBinaryOperator(left);
// The StackTypeConverter will convert pointer types to intptr_t, which is correct for
// arithmetic operations on pointer types. In this case though, we need to retain the pointer
// type, as the result might be used to pass to another method or to access a field.
if (!resultType.IsPointer)
{
try
{
resultType = StackTypeConverter.StackTypeFor(resultType);
}
catch (ArgumentException)
{
//while we're migrating between how to deal with types for stackvalues, we will use the StackType
//as a returntype if there is one, and if there's not, we fallback to our old behaviour of passing
//the more informationrich TypeReference.
}
}
WriteWarningProtectedOperation(resultType, lcast, left.Expression, rcast, right.Expression, name);
}
private string CastExpressionForBinaryOperator(StackInfo right)
{
if (right.Type.IsPointer)
return "(" + Globals.Naming.ForVariable(StackTypeConverter.StackTypeForBinaryOperation(right.Type)) + ")";
try
{
return "(" + StackTypeConverter.CppStackTypeFor(right.Type) + ")";
}
catch (ArgumentException)
{
//while we are in limbo land migrating towards more correct stacktypes, not all StackValue.Type's
//have/need a CppStackType for correct behaviour. For now, we will use a CppStackType if we need to,
//and if not we will just not emit a cast.
return "";
}
}
private void WriteShrUn()
{
var right = _valueStack.Pop();
var left = _valueStack.Pop();
var lcast = "";
var leftStackType = StackTypeConverter.StackTypeFor(left.Type);
if (leftStackType.MetadataType == MetadataType.Int32)
lcast = "(uint32_t)";
if (leftStackType.MetadataType == MetadataType.Int64)
lcast = "(uint64_t)";
WriteBinaryOperation(leftStackType, lcast, left.Expression, ">>", "", right.Expression);
}
private string NewTempName()
{
return "L_" + _tempIndex++;
}
private StackInfo NewTemp(TypeReference type)
{
if (type.ContainsGenericParameters())
throw new InvalidOperationException("Callers should resolve the type prior to calling this method.");
var variableName = NewTempName();
return new StackInfo(variableName, type);
}
private void LoadPrimitiveTypeSByte(Instruction ins, TypeReference type)
{
PushExpression(type, Emit.Cast(type, ((sbyte)ins.Operand).ToString()));
}
private void LoadPrimitiveTypeInt32(Instruction ins, TypeReference type)
{
var value = (int)ins.Operand;
var valueAsString = value.ToString();
var longValue = (long)value;
if (longValue <= int.MinValue || longValue >= int.MaxValue)
valueAsString = valueAsString + "LL";
PushExpression(type, Emit.Cast(type, valueAsString));
}
private void LoadLong(Instruction ins, TypeReference type)
{
var longValue = (long)ins.Operand;
var valueAsString = longValue + "LL";
if (longValue == long.MinValue)
valueAsString = "(std::numeric_limits<int64_t>::min)()";
if (longValue == long.MaxValue)
valueAsString = "(std::numeric_limits<int64_t>::max)()";
PushExpression(type, Emit.Cast(type, valueAsString));
}
private void LoadInt32Constant(int value)
{
_valueStack.Push(value < 0
? new StackInfo(string.Format("({0})", value), Int32TypeReference)
: new StackInfo(value.ToString(), Int32TypeReference));
}
private static List<string> FormatArgumentsForMethodCall(List<TypeReference> parameterTypes, List<StackInfo> stackValues, SharingType sharingType)
{
var paramCount = parameterTypes.Count;
var argList = new List<string>();
for (var paramIndex = 0; paramIndex < paramCount; paramIndex++)
{
var sb = new StringBuilder();
var stackValue = stackValues[paramIndex];
var paramType = parameterTypes[paramIndex];
if (paramType.IsPointer || paramType.IsByReference)
sb.Append("(" + Globals.Naming.ForVariable(paramType) + ")");
else if (VarianceSupport.IsNeededForConversion(paramType, stackValue.Type))
{
sb.Append(VarianceSupport.Apply(paramType, stackValue.Type));
}
sb.Append(WriteExpressionAndCastIfNeeded(paramType, stackValue, sharingType));
argList.Add(sb.ToString());
}
return argList;
}
private static List<TypeReference> GetParameterTypes(MethodReference method, TypeResolver typeResolverForMethodToCall)
{
var list = new List<TypeReference>(method.Parameters.Select(parameter =>
typeResolverForMethodToCall.Resolve(GenericParameterResolver.ResolveParameterTypeIfNeeded(method, parameter))));
return list;
}
private static List<StackInfo> PopItemsFromStack(int amount, Stack<StackInfo> valueStack)
{
if (amount > valueStack.Count)
throw new Exception(string.Format("Attempting to pop '{0}' values from a stack of depth '{1}'.", amount, valueStack.Count));
var poppedValues = new List<StackInfo>();
for (var i = 0; i != amount; i++)
poppedValues.Add(valueStack.Pop());
poppedValues.Reverse();
return poppedValues;
}
private static void EmitTinyDelegateFieldSetters(IGeneratedMethodCodeWriter writer, MethodReference constructor, StackInfo delegateVariable, List<StackInfo> arguments)
{
var targetMethod = arguments[1].MethodExpressionIsPointingTo;
string reversePInvokeWrapperPtrExpression = null;
if (ShouldEmitReversePInvokeWrapper(targetMethod))
{
ReversePInvokeMethodBodyWriter.Create(targetMethod).WriteMethodDeclaration(writer);
reversePInvokeWrapperPtrExpression = Globals.Naming.ForReversePInvokeWrapperMethod(targetMethod);
}
else
{
reversePInvokeWrapperPtrExpression = WellKnown.Constants.Null;
}
var invokeMethod = constructor.DeclaringType.Resolve().Methods.Single(m => m.Name == "Invoke");
var isDelegateOpenValue = !targetMethod.HasThis && targetMethod.Parameters.Count == invokeMethod.Parameters.Count ? "true" : "false";
DelegateMethodsWriter.EmitTinyDelegateExtraFieldSetters(writer, delegateVariable.Expression, reversePInvokeWrapperPtrExpression, isDelegateOpenValue);
}
private static bool ShouldEmitReversePInvokeWrapper(MethodReference targetMethod)
{
if (ReversePInvokeMethodBodyWriter.IsReversePInvokeWrapperNecessary(targetMethod))
return true;
// If we're emitting calls to dummy reverse p/invoke wrappers to help with debugging, skip open generic methods or ones on open generic types.
// IL2CPP won't emit the wrapper for these types, so a call to it will cause a native linker error.
return CodeGenOptions.EmitReversePInvokeWrapperDebuggingHelpers && !targetMethod.DeclaringType.ContainsGenericParameter &&
!targetMethod.ContainsGenericParameter;
}
private static string ParameterNameFor(MethodDefinition method, int i)
{
if (method == null) throw new ArgumentNullException("method");
return Globals.Naming.ForParameterName(method.Parameters[i]);
}
private IDisposable NewIfBlock(string conditional)
{
_writer.WriteLine("if ({0})", conditional);
return NewBlock();
}
private IDisposable NewBlock()
{
return new BlockWriter(_writer);
}
private void EmitMemoryBarrierIfNecessary(FieldReference fieldReference = null)
{
if (_thisInstructionIsVolatile || fieldReference.IsVolatile())
{
Globals.StatsService.RecordMemoryBarrierEmitted(_methodDefinition);
_writer.WriteStatement(Emit.MemoryBarrier());
_thisInstructionIsVolatile = false;
}
}
private void AddVolatileStackEntry()
{
_thisInstructionIsVolatile = true;
}
private void WriteLoadObject(Instruction ins, InstructionBlock block)
{
var address = _valueStack.Pop();
var targetType = _typeResolver.Resolve((TypeReference)ins.Operand);
var pointerType = new PointerType(targetType);
if (CanApplyValueTypeBoxBranchOptimizationToInstruction(ins.Next, block))
{
_valueStack.Push(new StackInfo(string.Format("(*({0}){1})", Globals.Naming.ForVariable(pointerType), address.Expression),
targetType));
}
else
{
_writer.AddIncludeForTypeDefinition(targetType);
var temp = NewTemp(targetType);
_writer.WriteStatement(Emit.Assign(temp.IdentifierExpression, string.Format("(*({0}){1})", Globals.Naming.ForVariable(pointerType),
address.Expression)));
_valueStack.Push(temp);
}
EmitMemoryBarrierIfNecessary();
}
private void WriteStoreObject(TypeReference type)
{
var targetType = _typeResolver.Resolve(type);
var value = _valueStack.Pop();
var address = _valueStack.Pop();
EmitMemoryBarrierIfNecessary();
var addressExpression = Emit.Cast(new PointerType(targetType), address.Expression);
_writer.WriteStatement(Emit.Assign(Emit.Dereference(addressExpression), value.Expression));
_writer.WriteWriteBarrierIfNeeded(targetType, addressExpression, value.Expression);
}
private void LoadVirtualFunction(Instruction ins)
{
var methodReference = (MethodReference)ins.Operand;
var methodDefinition = methodReference.Resolve();
var target = _valueStack.Pop();
if (methodDefinition.IsVirtual)
PushCallToLoadVirtualFunction(methodReference, methodDefinition, target.Expression);
else
PushCallToLoadFunction(methodReference);
}
private void PushCallToLoadFunction(MethodReference methodReference)
{
if (!CodeGenOptions.UseTinyRuntimeBackend)
{
_writer.AddIncludeForTypeDefinition(_typeResolver.Resolve(methodReference.DeclaringType));
PushExpression(IntPtrTypeReference, Emit.Cast(IntPtrTypeReference, _runtimeMetadataAccess.MethodInfo(methodReference)), methodExpressionIsPointingTo: methodReference);
}
else
{
var method = _typeResolver.Resolve(methodReference);
_writer.AddIncludeForMethodDeclaration(method);
StoreLocalAndPush(IntPtrTypeReference, Emit.Cast(IntPtrTypeReference, _runtimeMetadataAccess.Method(method)), method);
}
}
private void PushCallToLoadVirtualFunction(MethodReference methodReference, MethodDefinition methodDefinition, string targetExpression)
{
string methodInfo;
var isInterfaceMethod = methodReference.DeclaringType.IsInterface();
if (methodReference.IsGenericInstance)
{
methodInfo = isInterfaceMethod
? Emit.Call("il2cpp_codegen_get_generic_interface_method", _runtimeMetadataAccess.MethodInfo(methodReference), targetExpression)
: Emit.Call("il2cpp_codegen_get_generic_virtual_method", _runtimeMetadataAccess.MethodInfo(methodReference), targetExpression);
}
else
{
if (isInterfaceMethod)
{
if (CodeGenOptions.MonoRuntime)
methodInfo = Emit.Call("GetInterfaceMethodInfo", targetExpression, "(RuntimeMethod*)" + _runtimeMetadataAccess.MethodInfo(methodReference), _runtimeMetadataAccess.TypeInfoFor(methodReference.DeclaringType));
else
methodInfo = Emit.Call("GetInterfaceMethodInfo", targetExpression, _vTableBuilder.IndexFor(methodDefinition).ToString(), _runtimeMetadataAccess.TypeInfoFor(methodReference.DeclaringType));
}
else
{
if (CodeGenOptions.MonoRuntime)
methodInfo = Emit.Call("GetVirtualMethodInfo", targetExpression, _runtimeMetadataAccess.MethodInfo(methodReference));
else
methodInfo = Emit.Call("GetVirtualMethodInfo", targetExpression, _vTableBuilder.IndexFor(methodDefinition).ToString());
}
}
var method = _typeResolver.Resolve(methodReference);
if (!CodeGenOptions.UseTinyRuntimeBackend)
{
_writer.AddIncludeForTypeDefinition(_typeResolver.Resolve(methodReference.DeclaringType));
PushExpression(IntPtrTypeReference, Emit.Cast(IntPtrTypeReference, methodInfo), methodExpressionIsPointingTo: method);
}
else
{
_writer.AddIncludeForMethodDeclaration(_typeResolver.Resolve(methodReference));
PushExpression(IntPtrTypeReference, Emit.Cast(IntPtrTypeReference, methodInfo), methodExpressionIsPointingTo: method);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment