5e724cf24e
* Delete DelegateTypes.cs * Delete DelegateCache.cs * Add files via upload * Update Horizon.cs * Update Program.cs * Update MainWindow.cs * Update Aot.cs * Update RelocEntry.cs * Update Translator.cs * Update MemoryManager.cs * Update InstEmitMemoryHelper.cs * Update Delegates.cs * Nit. * Nit. * Nit. * 10 fewer MSIL bytes for us * Add comment. Nits. * Update Translator.cs * Update Aot.cs * Nits. * Opt.. * Opt.. * Opt.. * Opt.. * Allow to change compression level. * Update MemoryManager.cs * Update Translator.cs * Manage corner cases during the save phase. Nits. * Update Aot.cs * Translator response tweak for Aot disabled. Nit. * Nit. * Nits. * Create DelegateHelpers.cs * Update Delegates.cs * Nit. * Nit. * Nits. * Fix due to #784. * Fixes due to #757 & #841. * Fix due to #846. * Fix due to #847. * Use MethodInfo for managed method calls. Use IR methods instead of managed methods about Max/Min (S/U). Follow-ups & Nits. * Add missing exception messages. Reintroduce slow path for Fmov_Vi. Implement slow path for Fmov_Si. * Switch to the new folder structure. Nits. * Impl. index-based relocation information. Impl. cache file version field. * Nit. * Address gdkchan comments. Mainly: - fixed cache file corruption issue on exit; - exposed a way to disable AOT on the GUI. * Address AcK77 comment. * Address Thealexbarney, jduncanator & emmauss comments. Header magic, CpuId (FI) & Aot -> Ptc. * Adaptation to the new application reloading system. Improvements to the call system of managed methods. Follow-ups. Nits. * Get the same boot times as on master when PTC is disabled. * Profiled Aot. * A32 support (#897). * #975 support (1 of 2). * #975 support (2 of 2). * Rebase fix & nits. * Some fixes and nits (still one bug left). * One fix & nits. * Tests fix (by gdk) & nits. * Support translations not only in high quality and rejit. Nits. * Added possibility to skip translations and continue execution, using `ESC` key. * Update SettingsWindow.cs * Update GLRenderer.cs * Update Ptc.cs * Disabled Profiled PTC by default as requested in the past by gdk. * Fix rejit bug. Increased number of parallel translations. Add stack unwinding stuffs support (1 of 2). Nits. * Add stack unwinding stuffs support (2 of 2). Tuned number of parallel translations. * Restored the ability to assemble jumps with 8-bit offset when Profiled PTC is disabled or during profiling. Modifications due to rebase. Nits. * Limited profiling of the functions to be translated to the addresses belonging to the range of static objects only. * Nits. * Nits. * Update Delegates.cs * Nit. * Update InstEmitSimdArithmetic.cs * Address riperiperi comments. * Fixed the issue of unjustifiably longer boot times at the second boot than at the first boot, measured at the same time or reference point and with the same number of translated functions. * Implemented a simple redundant load/save mechanism. Halved the value of Decoder.MaxInstsPerFunction more appropriate for the current performance of the Translator. Replaced by Logger.PrintError to Logger.PrintDebug in TexturePool.cs about the supposed invalid texture format to avoid the spawn of the log. Nits. * Nit. Improved Logger.PrintError in TexturePool.cs to avoid log spawn. Added missing code for FZ handling (in output) for fp max/min instructions (slow paths). * Add configuration migration for PTC Co-authored-by: Thog <me@thog.eu>
633 lines
18 KiB
C#
633 lines
18 KiB
C#
using ARMeilleure.Diagnostics;
|
|
using ARMeilleure.IntermediateRepresentation;
|
|
using ARMeilleure.State;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
|
|
using static ARMeilleure.IntermediateRepresentation.OperandHelper;
|
|
|
|
namespace ARMeilleure.Translation
|
|
{
|
|
using PTC;
|
|
|
|
class EmitterContext
|
|
{
|
|
private Dictionary<Operand, BasicBlock> _irLabels;
|
|
|
|
private IntrusiveList<BasicBlock> _irBlocks;
|
|
|
|
private BasicBlock _irBlock;
|
|
|
|
private bool _needsNewBlock;
|
|
|
|
public EmitterContext()
|
|
{
|
|
_irLabels = new Dictionary<Operand, BasicBlock>();
|
|
|
|
_irBlocks = new IntrusiveList<BasicBlock>();
|
|
|
|
_needsNewBlock = true;
|
|
}
|
|
|
|
public Operand Add(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.Add, Local(op1.Type), op1, op2);
|
|
}
|
|
|
|
public Operand BitwiseAnd(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.BitwiseAnd, Local(op1.Type), op1, op2);
|
|
}
|
|
|
|
public Operand BitwiseExclusiveOr(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.BitwiseExclusiveOr, Local(op1.Type), op1, op2);
|
|
}
|
|
|
|
public Operand BitwiseNot(Operand op1)
|
|
{
|
|
return Add(Instruction.BitwiseNot, Local(op1.Type), op1);
|
|
}
|
|
|
|
public Operand BitwiseOr(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.BitwiseOr, Local(op1.Type), op1, op2);
|
|
}
|
|
|
|
public void Branch(Operand label)
|
|
{
|
|
Add(Instruction.Branch, null);
|
|
|
|
BranchToLabel(label);
|
|
}
|
|
|
|
public void BranchIfFalse(Operand label, Operand op1)
|
|
{
|
|
Add(Instruction.BranchIfFalse, null, op1);
|
|
|
|
BranchToLabel(label);
|
|
}
|
|
|
|
public void BranchIfTrue(Operand label, Operand op1)
|
|
{
|
|
Add(Instruction.BranchIfTrue, null, op1);
|
|
|
|
BranchToLabel(label);
|
|
}
|
|
|
|
public Operand ByteSwap(Operand op1)
|
|
{
|
|
return Add(Instruction.ByteSwap, Local(op1.Type), op1);
|
|
}
|
|
|
|
public Operand Call(MethodInfo info, params Operand[] callArgs)
|
|
{
|
|
if (Ptc.State == PtcState.Disabled)
|
|
{
|
|
IntPtr funcPtr = Delegates.GetDelegateFuncPtr(info);
|
|
|
|
OperandType returnType = GetOperandType(info.ReturnType);
|
|
|
|
Symbols.Add((ulong)funcPtr.ToInt64(), info.Name);
|
|
|
|
return Call(Const(funcPtr.ToInt64()), returnType, callArgs);
|
|
}
|
|
else
|
|
{
|
|
int index = Delegates.GetDelegateIndex(info);
|
|
|
|
IntPtr funcPtr = Delegates.GetDelegateFuncPtrByIndex(index);
|
|
|
|
OperandType returnType = GetOperandType(info.ReturnType);
|
|
|
|
Symbols.Add((ulong)funcPtr.ToInt64(), info.Name);
|
|
|
|
return Call(Const(funcPtr.ToInt64(), true, index), returnType, callArgs);
|
|
}
|
|
}
|
|
|
|
private static OperandType GetOperandType(Type type)
|
|
{
|
|
if (type == typeof(bool) || type == typeof(byte) ||
|
|
type == typeof(char) || type == typeof(short) ||
|
|
type == typeof(int) || type == typeof(sbyte) ||
|
|
type == typeof(ushort) || type == typeof(uint))
|
|
{
|
|
return OperandType.I32;
|
|
}
|
|
else if (type == typeof(long) || type == typeof(ulong))
|
|
{
|
|
return OperandType.I64;
|
|
}
|
|
else if (type == typeof(double))
|
|
{
|
|
return OperandType.FP64;
|
|
}
|
|
else if (type == typeof(float))
|
|
{
|
|
return OperandType.FP32;
|
|
}
|
|
else if (type == typeof(V128))
|
|
{
|
|
return OperandType.V128;
|
|
}
|
|
else if (type == typeof(void))
|
|
{
|
|
return OperandType.None;
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentException($"Invalid type \"{type.Name}\".");
|
|
}
|
|
}
|
|
|
|
public Operand Call(Operand address, OperandType returnType, params Operand[] callArgs)
|
|
{
|
|
Operand[] args = new Operand[callArgs.Length + 1];
|
|
|
|
args[0] = address;
|
|
|
|
Array.Copy(callArgs, 0, args, 1, callArgs.Length);
|
|
|
|
if (returnType != OperandType.None)
|
|
{
|
|
return Add(Instruction.Call, Local(returnType), args);
|
|
}
|
|
else
|
|
{
|
|
return Add(Instruction.Call, null, args);
|
|
}
|
|
}
|
|
|
|
public void Tailcall(Operand address, params Operand[] callArgs)
|
|
{
|
|
Operand[] args = new Operand[callArgs.Length + 1];
|
|
|
|
args[0] = address;
|
|
|
|
Array.Copy(callArgs, 0, args, 1, callArgs.Length);
|
|
|
|
Add(Instruction.Tailcall, null, args);
|
|
|
|
_needsNewBlock = true;
|
|
}
|
|
|
|
public Operand CompareAndSwap(Operand address, Operand expected, Operand desired)
|
|
{
|
|
return Add(Instruction.CompareAndSwap, Local(desired.Type), address, expected, desired);
|
|
}
|
|
|
|
public Operand ConditionalSelect(Operand op1, Operand op2, Operand op3)
|
|
{
|
|
return Add(Instruction.ConditionalSelect, Local(op2.Type), op1, op2, op3);
|
|
}
|
|
|
|
public Operand ConvertI64ToI32(Operand op1)
|
|
{
|
|
if (op1.Type != OperandType.I64)
|
|
{
|
|
throw new ArgumentException($"Invalid operand type \"{op1.Type}\".");
|
|
}
|
|
|
|
return Add(Instruction.ConvertI64ToI32, Local(OperandType.I32), op1);
|
|
}
|
|
|
|
public Operand ConvertToFP(OperandType type, Operand op1)
|
|
{
|
|
return Add(Instruction.ConvertToFP, Local(type), op1);
|
|
}
|
|
|
|
public Operand ConvertToFPUI(OperandType type, Operand op1)
|
|
{
|
|
return Add(Instruction.ConvertToFPUI, Local(type), op1);
|
|
}
|
|
|
|
public Operand Copy(Operand op1)
|
|
{
|
|
return Add(Instruction.Copy, Local(op1.Type), op1);
|
|
}
|
|
|
|
public Operand Copy(Operand dest, Operand op1)
|
|
{
|
|
if (dest.Kind != OperandKind.Register)
|
|
{
|
|
throw new ArgumentException($"Invalid dest operand kind \"{dest.Kind}\".");
|
|
}
|
|
|
|
return Add(Instruction.Copy, dest, op1);
|
|
}
|
|
|
|
public Operand CountLeadingZeros(Operand op1)
|
|
{
|
|
return Add(Instruction.CountLeadingZeros, Local(op1.Type), op1);
|
|
}
|
|
|
|
public Operand Divide(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.Divide, Local(op1.Type), op1, op2);
|
|
}
|
|
|
|
public Operand DivideUI(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.DivideUI, Local(op1.Type), op1, op2);
|
|
}
|
|
|
|
public Operand ICompareEqual(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.CompareEqual, Local(OperandType.I32), op1, op2);
|
|
}
|
|
|
|
public Operand ICompareGreater(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.CompareGreater, Local(OperandType.I32), op1, op2);
|
|
}
|
|
|
|
public Operand ICompareGreaterOrEqual(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.CompareGreaterOrEqual, Local(OperandType.I32), op1, op2);
|
|
}
|
|
|
|
public Operand ICompareGreaterOrEqualUI(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.CompareGreaterOrEqualUI, Local(OperandType.I32), op1, op2);
|
|
}
|
|
|
|
public Operand ICompareGreaterUI(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.CompareGreaterUI, Local(OperandType.I32), op1, op2);
|
|
}
|
|
|
|
public Operand ICompareLess(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.CompareLess, Local(OperandType.I32), op1, op2);
|
|
}
|
|
|
|
public Operand ICompareLessOrEqual(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.CompareLessOrEqual, Local(OperandType.I32), op1, op2);
|
|
}
|
|
|
|
public Operand ICompareLessOrEqualUI(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.CompareLessOrEqualUI, Local(OperandType.I32), op1, op2);
|
|
}
|
|
|
|
public Operand ICompareLessUI(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.CompareLessUI, Local(OperandType.I32), op1, op2);
|
|
}
|
|
|
|
public Operand ICompareNotEqual(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.CompareNotEqual, Local(OperandType.I32), op1, op2);
|
|
}
|
|
|
|
public Operand Load(OperandType type, Operand address)
|
|
{
|
|
return Add(Instruction.Load, Local(type), address);
|
|
}
|
|
|
|
public Operand Load16(Operand address)
|
|
{
|
|
return Add(Instruction.Load16, Local(OperandType.I32), address);
|
|
}
|
|
|
|
public Operand Load8(Operand address)
|
|
{
|
|
return Add(Instruction.Load8, Local(OperandType.I32), address);
|
|
}
|
|
|
|
public Operand LoadArgument(OperandType type, int index)
|
|
{
|
|
return Add(Instruction.LoadArgument, Local(type), Const(index));
|
|
}
|
|
|
|
public void LoadFromContext()
|
|
{
|
|
_needsNewBlock = true;
|
|
|
|
Add(Instruction.LoadFromContext);
|
|
}
|
|
|
|
public Operand Multiply(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.Multiply, Local(op1.Type), op1, op2);
|
|
}
|
|
|
|
public Operand Multiply64HighSI(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.Multiply64HighSI, Local(OperandType.I64), op1, op2);
|
|
}
|
|
|
|
public Operand Multiply64HighUI(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.Multiply64HighUI, Local(OperandType.I64), op1, op2);
|
|
}
|
|
|
|
public Operand Negate(Operand op1)
|
|
{
|
|
return Add(Instruction.Negate, Local(op1.Type), op1);
|
|
}
|
|
|
|
public void Return()
|
|
{
|
|
Add(Instruction.Return);
|
|
|
|
_needsNewBlock = true;
|
|
}
|
|
|
|
public void Return(Operand op1)
|
|
{
|
|
Add(Instruction.Return, null, op1);
|
|
|
|
_needsNewBlock = true;
|
|
}
|
|
|
|
public Operand RotateRight(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.RotateRight, Local(op1.Type), op1, op2);
|
|
}
|
|
|
|
public Operand ShiftLeft(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.ShiftLeft, Local(op1.Type), op1, op2);
|
|
}
|
|
|
|
public Operand ShiftRightSI(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.ShiftRightSI, Local(op1.Type), op1, op2);
|
|
}
|
|
|
|
public Operand ShiftRightUI(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.ShiftRightUI, Local(op1.Type), op1, op2);
|
|
}
|
|
|
|
public Operand SignExtend16(OperandType type, Operand op1)
|
|
{
|
|
return Add(Instruction.SignExtend16, Local(type), op1);
|
|
}
|
|
|
|
public Operand SignExtend32(OperandType type, Operand op1)
|
|
{
|
|
return Add(Instruction.SignExtend32, Local(type), op1);
|
|
}
|
|
|
|
public Operand SignExtend8(OperandType type, Operand op1)
|
|
{
|
|
return Add(Instruction.SignExtend8, Local(type), op1);
|
|
}
|
|
|
|
public void Store(Operand address, Operand value)
|
|
{
|
|
Add(Instruction.Store, null, address, value);
|
|
}
|
|
|
|
public void Store16(Operand address, Operand value)
|
|
{
|
|
Add(Instruction.Store16, null, address, value);
|
|
}
|
|
|
|
public void Store8(Operand address, Operand value)
|
|
{
|
|
Add(Instruction.Store8, null, address, value);
|
|
}
|
|
|
|
public void StoreToContext()
|
|
{
|
|
Add(Instruction.StoreToContext);
|
|
|
|
_needsNewBlock = true;
|
|
}
|
|
|
|
public Operand Subtract(Operand op1, Operand op2)
|
|
{
|
|
return Add(Instruction.Subtract, Local(op1.Type), op1, op2);
|
|
}
|
|
|
|
public Operand VectorCreateScalar(Operand value)
|
|
{
|
|
return Add(Instruction.VectorCreateScalar, Local(OperandType.V128), value);
|
|
}
|
|
|
|
public Operand VectorExtract(OperandType type, Operand vector, int index)
|
|
{
|
|
return Add(Instruction.VectorExtract, Local(type), vector, Const(index));
|
|
}
|
|
|
|
public Operand VectorExtract16(Operand vector, int index)
|
|
{
|
|
return Add(Instruction.VectorExtract16, Local(OperandType.I32), vector, Const(index));
|
|
}
|
|
|
|
public Operand VectorExtract8(Operand vector, int index)
|
|
{
|
|
return Add(Instruction.VectorExtract8, Local(OperandType.I32), vector, Const(index));
|
|
}
|
|
|
|
public Operand VectorInsert(Operand vector, Operand value, int index)
|
|
{
|
|
return Add(Instruction.VectorInsert, Local(OperandType.V128), vector, value, Const(index));
|
|
}
|
|
|
|
public Operand VectorInsert16(Operand vector, Operand value, int index)
|
|
{
|
|
return Add(Instruction.VectorInsert16, Local(OperandType.V128), vector, value, Const(index));
|
|
}
|
|
|
|
public Operand VectorInsert8(Operand vector, Operand value, int index)
|
|
{
|
|
return Add(Instruction.VectorInsert8, Local(OperandType.V128), vector, value, Const(index));
|
|
}
|
|
|
|
public Operand VectorZero()
|
|
{
|
|
return Add(Instruction.VectorZero, Local(OperandType.V128));
|
|
}
|
|
|
|
public Operand VectorZeroUpper64(Operand vector)
|
|
{
|
|
return Add(Instruction.VectorZeroUpper64, Local(OperandType.V128), vector);
|
|
}
|
|
|
|
public Operand VectorZeroUpper96(Operand vector)
|
|
{
|
|
return Add(Instruction.VectorZeroUpper96, Local(OperandType.V128), vector);
|
|
}
|
|
|
|
public Operand ZeroExtend16(OperandType type, Operand op1)
|
|
{
|
|
return Add(Instruction.ZeroExtend16, Local(type), op1);
|
|
}
|
|
|
|
public Operand ZeroExtend32(OperandType type, Operand op1)
|
|
{
|
|
return Add(Instruction.ZeroExtend32, Local(type), op1);
|
|
}
|
|
|
|
public Operand ZeroExtend8(OperandType type, Operand op1)
|
|
{
|
|
return Add(Instruction.ZeroExtend8, Local(type), op1);
|
|
}
|
|
|
|
private void NewNextBlockIfNeeded()
|
|
{
|
|
if (_needsNewBlock)
|
|
{
|
|
NewNextBlock();
|
|
}
|
|
}
|
|
|
|
private Operand Add(Instruction inst, Operand dest = null)
|
|
{
|
|
NewNextBlockIfNeeded();
|
|
|
|
Operation operation = OperationHelper.Operation(inst, dest);
|
|
|
|
_irBlock.Operations.AddLast(operation);
|
|
|
|
return dest;
|
|
}
|
|
|
|
private Operand Add(Instruction inst, Operand dest, Operand[] sources)
|
|
{
|
|
NewNextBlockIfNeeded();
|
|
|
|
Operation operation = OperationHelper.Operation(inst, dest, sources);
|
|
|
|
_irBlock.Operations.AddLast(operation);
|
|
|
|
return dest;
|
|
}
|
|
|
|
private Operand Add(Instruction inst, Operand dest, Operand source0)
|
|
{
|
|
NewNextBlockIfNeeded();
|
|
|
|
Operation operation = OperationHelper.Operation(inst, dest, source0);
|
|
|
|
_irBlock.Operations.AddLast(operation);
|
|
|
|
return dest;
|
|
}
|
|
|
|
private Operand Add(Instruction inst, Operand dest, Operand source0, Operand source1)
|
|
{
|
|
NewNextBlockIfNeeded();
|
|
|
|
Operation operation = OperationHelper.Operation(inst, dest, source0, source1);
|
|
|
|
_irBlock.Operations.AddLast(operation);
|
|
|
|
return dest;
|
|
}
|
|
|
|
private Operand Add(Instruction inst, Operand dest, Operand source0, Operand source1, Operand source2)
|
|
{
|
|
NewNextBlockIfNeeded();
|
|
|
|
Operation operation = OperationHelper.Operation(inst, dest, source0, source1, source2);
|
|
|
|
_irBlock.Operations.AddLast(operation);
|
|
|
|
return dest;
|
|
}
|
|
|
|
public Operand AddIntrinsic(Intrinsic intrin, params Operand[] args)
|
|
{
|
|
return Add(intrin, Local(OperandType.V128), args);
|
|
}
|
|
|
|
public Operand AddIntrinsicInt(Intrinsic intrin, params Operand[] args)
|
|
{
|
|
return Add(intrin, Local(OperandType.I32), args);
|
|
}
|
|
|
|
public Operand AddIntrinsicLong(Intrinsic intrin, params Operand[] args)
|
|
{
|
|
return Add(intrin, Local(OperandType.I64), args);
|
|
}
|
|
|
|
private Operand Add(Intrinsic intrin, Operand dest, params Operand[] sources)
|
|
{
|
|
if (_needsNewBlock)
|
|
{
|
|
NewNextBlock();
|
|
}
|
|
|
|
IntrinsicOperation operation = new IntrinsicOperation(intrin, dest, sources);
|
|
|
|
_irBlock.Operations.AddLast(operation);
|
|
|
|
return dest;
|
|
}
|
|
|
|
private void BranchToLabel(Operand label)
|
|
{
|
|
if (!_irLabels.TryGetValue(label, out BasicBlock branchBlock))
|
|
{
|
|
branchBlock = new BasicBlock();
|
|
|
|
_irLabels.Add(label, branchBlock);
|
|
}
|
|
|
|
_irBlock.Branch = branchBlock;
|
|
|
|
_needsNewBlock = true;
|
|
}
|
|
|
|
public void MarkLabel(Operand label)
|
|
{
|
|
if (_irLabels.TryGetValue(label, out BasicBlock nextBlock))
|
|
{
|
|
nextBlock.Index = _irBlocks.Count;
|
|
|
|
_irBlocks.AddLast(nextBlock);
|
|
|
|
NextBlock(nextBlock);
|
|
}
|
|
else
|
|
{
|
|
NewNextBlock();
|
|
|
|
_irLabels.Add(label, _irBlock);
|
|
}
|
|
}
|
|
|
|
private void NewNextBlock()
|
|
{
|
|
BasicBlock block = new BasicBlock(_irBlocks.Count);
|
|
|
|
_irBlocks.AddLast(block);
|
|
|
|
NextBlock(block);
|
|
}
|
|
|
|
private void NextBlock(BasicBlock nextBlock)
|
|
{
|
|
if (_irBlock != null && !EndsWithUnconditional(_irBlock))
|
|
{
|
|
_irBlock.Next = nextBlock;
|
|
}
|
|
|
|
_irBlock = nextBlock;
|
|
|
|
_needsNewBlock = false;
|
|
}
|
|
|
|
private static bool EndsWithUnconditional(BasicBlock block)
|
|
{
|
|
return block.Operations.Last is Operation lastOp &&
|
|
(lastOp.Instruction == Instruction.Branch ||
|
|
lastOp.Instruction == Instruction.Return ||
|
|
lastOp.Instruction == Instruction.Tailcall);
|
|
}
|
|
|
|
public ControlFlowGraph GetControlFlowGraph()
|
|
{
|
|
return new ControlFlowGraph(_irBlocks.First, _irBlocks);
|
|
}
|
|
}
|
|
}
|