Last active
July 5, 2022 10:24
-
-
Save lfkdsk/42ee5cca8770dfe1b705077401955378 to your computer and use it in GitHub Desktop.
MethodBodyWriter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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