Reduce JIT GC allocations (#2515)
* Turn `MemoryOperand` into a struct * Remove `IntrinsicOperation` * Remove `PhiNode` * Remove `Node` * Turn `Operand` into a struct * Turn `Operation` into a struct * Clean up pool management methods * Add `Arena` allocator * Move `OperationHelper` to `Operation.Factory` * Move `OperandHelper` to `Operand.Factory` * Optimize `Operation` a bit * Fix `Arena` initialization * Rename `NativeList<T>` to `ArenaList<T>` * Reduce `Operand` size from 88 to 56 bytes * Reduce `Operation` size from 56 to 40 bytes * Add optimistic interning of Register & Constant operands * Optimize `RegisterUsage` pass a bit * Optimize `RemoveUnusedNodes` pass a bit Iterating in reverse-order allows killing dependency chains in a single pass. * Fix PPTC symbols * Optimize `BasicBlock` a bit Reduce allocations from `_successor` & `DominanceFrontiers` * Fix `Operation` resize * Make `Arena` expandable Change the arena allocator to be expandable by allocating in pages, with some of them being pooled. Currently 32 pages are pooled. An LRU removal mechanism should probably be added to it. Apparently MHR can allocate bitmaps large enough to exceed the 16MB limit for the type. * Move `Arena` & `ArenaList` to `Common` * Remove `ThreadStaticPool` & co * Add `PhiOperation` * Reduce `Operand` size from 56 from 48 bytes * Add linear-probing to `Operand` intern table * Optimize `HybridAllocator` a bit * Add `Allocators` class * Tune `ArenaAllocator` sizes * Add page removal mechanism to `ArenaAllocator` Remove pages which have not been used for more than 5s after each reset. I am on fence if this would be better using a Gen2 callback object like the one in System.Buffers.ArrayPool<T>, to trim the pool. Because right now if a large translation happens, the pages will be freed only after a reset. This reset may not happen for a while because no new translation is hit, but the arena base sizes are rather small. * Fix `OOM` when allocating larger than page size in `ArenaAllocator` Tweak resizing mechanism for Operand.Uses and Assignemnts. * Optimize `Optimizer` a bit * Optimize `Operand.Add<T>/Remove<T>` a bit * Clean up `PreAllocator` * Fix phi insertion order Reduce codegen diffs. * Fix code alignment * Use new heuristics for degree of parallelism * Suppress warnings * Address gdkchan's feedback Renamed `GetValue()` to `GetValueUnsafe()` to make it more clear that `Operand.Value` should usually not be modified directly. * Add fast path to `ArenaAllocator` * Assembly for `ArenaAllocator.Allocate(ulong)`: .L0: mov rax, [rcx+0x18] lea r8, [rax+rdx] cmp r8, [rcx+0x10] ja short .L2 .L1: mov rdx, [rcx+8] add rax, [rdx+8] mov [rcx+0x18], r8 ret .L2: jmp ArenaAllocator.AllocateSlow(UInt64) A few variable/field had to be changed to ulong so that RyuJIT avoids emitting zero-extends. * Implement a new heuristic to free pooled pages. If an arena is used often, it is more likely that its pages will be needed, so the pages are kept for longer (e.g: during PPTC rebuild or burst sof compilations). If is not used often, then it is more likely that its pages will not be needed (e.g: after PPTC rebuild or bursts of compilations). * Address riperiperi's feedback * Use `EqualityComparer<T>` in `IntrusiveList<T>` Avoids a potential GC hole in `Equals(T, T)`.
This commit is contained in:
parent
cd4530f29c
commit
22b2cb39af
91 changed files with 2354 additions and 2142 deletions
|
@ -1,37 +1,47 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
class BasicBlock : IIntrusiveListNode<BasicBlock>
|
||||
class BasicBlock : IEquatable<BasicBlock>, IIntrusiveListNode<BasicBlock>
|
||||
{
|
||||
private readonly List<BasicBlock> _successors;
|
||||
private const uint MaxSuccessors = 2;
|
||||
|
||||
private int _succCount;
|
||||
private BasicBlock _succ0;
|
||||
private BasicBlock _succ1;
|
||||
private HashSet<BasicBlock> _domFrontiers;
|
||||
|
||||
public int Index { get; set; }
|
||||
|
||||
public BasicBlockFrequency Frequency { get; set; }
|
||||
|
||||
public BasicBlock ListPrevious { get; set; }
|
||||
public BasicBlock ListNext { get; set; }
|
||||
|
||||
public IntrusiveList<Node> Operations { get; }
|
||||
|
||||
public IntrusiveList<Operation> Operations { get; }
|
||||
public List<BasicBlock> Predecessors { get; }
|
||||
|
||||
public HashSet<BasicBlock> DominanceFrontiers { get; }
|
||||
public BasicBlock ImmediateDominator { get; set; }
|
||||
|
||||
public int SuccessorCount => _successors.Count;
|
||||
public int SuccessorsCount => _succCount;
|
||||
|
||||
public HashSet<BasicBlock> DominanceFrontiers
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_domFrontiers == null)
|
||||
{
|
||||
_domFrontiers = new HashSet<BasicBlock>();
|
||||
}
|
||||
|
||||
return _domFrontiers;
|
||||
}
|
||||
}
|
||||
|
||||
public BasicBlock() : this(index: -1) { }
|
||||
|
||||
public BasicBlock(int index)
|
||||
{
|
||||
_successors = new List<BasicBlock>();
|
||||
|
||||
Operations = new IntrusiveList<Node>();
|
||||
Operations = new IntrusiveList<Operation>();
|
||||
Predecessors = new List<BasicBlock>();
|
||||
DominanceFrontiers = new HashSet<BasicBlock>();
|
||||
|
||||
Index = index;
|
||||
}
|
||||
|
@ -40,54 +50,92 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
{
|
||||
if (block == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(block));
|
||||
ThrowNull(nameof(block));
|
||||
}
|
||||
|
||||
if ((uint)_succCount + 1 > MaxSuccessors)
|
||||
{
|
||||
ThrowSuccessorOverflow();
|
||||
}
|
||||
|
||||
block.Predecessors.Add(this);
|
||||
|
||||
_successors.Add(block);
|
||||
GetSuccessorUnsafe(_succCount++) = block;
|
||||
}
|
||||
|
||||
public void RemoveSuccessor(int index)
|
||||
{
|
||||
BasicBlock oldBlock = _successors[index];
|
||||
if ((uint)index >= (uint)_succCount)
|
||||
{
|
||||
ThrowOutOfRange(nameof(index));
|
||||
}
|
||||
|
||||
ref BasicBlock oldBlock = ref GetSuccessorUnsafe(index);
|
||||
|
||||
oldBlock.Predecessors.Remove(this);
|
||||
oldBlock = null;
|
||||
|
||||
_successors.RemoveAt(index);
|
||||
if (index == 0)
|
||||
{
|
||||
_succ0 = _succ1;
|
||||
}
|
||||
|
||||
_succCount--;
|
||||
}
|
||||
|
||||
public BasicBlock GetSuccessor(int index)
|
||||
{
|
||||
return _successors[index];
|
||||
if ((uint)index >= (uint)_succCount)
|
||||
{
|
||||
ThrowOutOfRange(nameof(index));
|
||||
}
|
||||
|
||||
return GetSuccessorUnsafe(index);
|
||||
}
|
||||
|
||||
private ref BasicBlock GetSuccessorUnsafe(int index)
|
||||
{
|
||||
return ref Unsafe.Add(ref _succ0, index);
|
||||
}
|
||||
|
||||
public void SetSuccessor(int index, BasicBlock block)
|
||||
{
|
||||
if (block == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(block));
|
||||
ThrowNull(nameof(block));
|
||||
}
|
||||
|
||||
BasicBlock oldBlock = _successors[index];
|
||||
if ((uint)index >= (uint)_succCount)
|
||||
{
|
||||
ThrowOutOfRange(nameof(index));
|
||||
}
|
||||
|
||||
ref BasicBlock oldBlock = ref GetSuccessorUnsafe(index);
|
||||
|
||||
oldBlock.Predecessors.Remove(this);
|
||||
block.Predecessors.Add(this);
|
||||
|
||||
_successors[index] = block;
|
||||
|
||||
oldBlock = block;
|
||||
}
|
||||
|
||||
public void Append(Node node)
|
||||
public void Append(Operation node)
|
||||
{
|
||||
var lastOp = Operations.Last as Operation;
|
||||
Operation last = Operations.Last;
|
||||
|
||||
// Append node before terminal or to end if no terminal.
|
||||
switch (lastOp?.Instruction)
|
||||
if (last == default)
|
||||
{
|
||||
Operations.AddLast(node);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (last.Instruction)
|
||||
{
|
||||
case Instruction.Return:
|
||||
case Instruction.Tailcall:
|
||||
case Instruction.BranchIf:
|
||||
Operations.AddBefore(lastOp, node);
|
||||
Operations.AddBefore(last, node);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -96,9 +144,23 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
}
|
||||
}
|
||||
|
||||
public Node GetLastOp()
|
||||
private static void ThrowNull(string name) => throw new ArgumentNullException(name);
|
||||
private static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name);
|
||||
private static void ThrowSuccessorOverflow() => throw new OverflowException($"BasicBlock can only have {MaxSuccessors} successors.");
|
||||
|
||||
public bool Equals(BasicBlock other)
|
||||
{
|
||||
return Operations.Last;
|
||||
return other == this;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as BasicBlock);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
interface IIntrusiveListNode<T> where T : class
|
||||
interface IIntrusiveListNode<T>
|
||||
{
|
||||
T ListPrevious { get; set; }
|
||||
T ListNext { get; set; }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
enum Instruction
|
||||
enum Instruction : ushort
|
||||
{
|
||||
Add,
|
||||
BitwiseAnd,
|
||||
|
@ -63,6 +63,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
Extended,
|
||||
Fill,
|
||||
LoadFromContext,
|
||||
Phi,
|
||||
Spill,
|
||||
SpillArg,
|
||||
StoreToContext
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
enum Intrinsic
|
||||
enum Intrinsic : ushort
|
||||
{
|
||||
X86Addpd,
|
||||
X86Addps,
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
class IntrinsicOperation : Operation
|
||||
{
|
||||
public Intrinsic Intrinsic { get; }
|
||||
|
||||
public IntrinsicOperation(Intrinsic intrin, Operand dest, params Operand[] sources) : base(Instruction.Extended, dest, sources)
|
||||
{
|
||||
Intrinsic = intrin;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
using System.Diagnostics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
|
@ -7,7 +9,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
/// Represents a efficient linked list that stores the pointer on the object directly and does not allocate.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the list items</typeparam>
|
||||
class IntrusiveList<T> where T : class, IIntrusiveListNode<T>
|
||||
class IntrusiveList<T> where T : IEquatable<T>, IIntrusiveListNode<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// First item of the list, or null if empty.
|
||||
|
@ -24,22 +26,34 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
/// </summary>
|
||||
public int Count { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IntrusiveList{T}"/> class.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException"><typeparamref name="T"/> is not pointer sized.</exception>
|
||||
public IntrusiveList()
|
||||
{
|
||||
if (Unsafe.SizeOf<T>() != IntPtr.Size)
|
||||
{
|
||||
throw new ArgumentException("T must be a reference type or a pointer sized struct.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a item as the first item of the list.
|
||||
/// </summary>
|
||||
/// <param name="newNode">Item to be added</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddFirst(T newNode)
|
||||
public T AddFirst(T newNode)
|
||||
{
|
||||
if (First != null)
|
||||
if (!EqualsNull(First))
|
||||
{
|
||||
AddBefore(First, newNode);
|
||||
return AddBefore(First, newNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(newNode.ListPrevious == null);
|
||||
Debug.Assert(newNode.ListNext == null);
|
||||
Debug.Assert(Last == null);
|
||||
Debug.Assert(EqualsNull(newNode.ListPrevious));
|
||||
Debug.Assert(EqualsNull(newNode.ListNext));
|
||||
Debug.Assert(EqualsNull(Last));
|
||||
|
||||
First = newNode;
|
||||
Last = newNode;
|
||||
|
@ -47,6 +61,8 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
Debug.Assert(Count == 0);
|
||||
|
||||
Count = 1;
|
||||
|
||||
return newNode;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,17 +71,17 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
/// </summary>
|
||||
/// <param name="newNode">Item to be added</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddLast(T newNode)
|
||||
public T AddLast(T newNode)
|
||||
{
|
||||
if (Last != null)
|
||||
if (!EqualsNull(Last))
|
||||
{
|
||||
AddAfter(Last, newNode);
|
||||
return AddAfter(Last, newNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(newNode.ListPrevious == null);
|
||||
Debug.Assert(newNode.ListNext == null);
|
||||
Debug.Assert(First == null);
|
||||
Debug.Assert(EqualsNull(newNode.ListPrevious));
|
||||
Debug.Assert(EqualsNull(newNode.ListNext));
|
||||
Debug.Assert(EqualsNull(First));
|
||||
|
||||
First = newNode;
|
||||
Last = newNode;
|
||||
|
@ -73,6 +89,8 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
Debug.Assert(Count == 0);
|
||||
|
||||
Count = 1;
|
||||
|
||||
return newNode;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,20 +103,20 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T AddBefore(T node, T newNode)
|
||||
{
|
||||
Debug.Assert(newNode.ListPrevious == null);
|
||||
Debug.Assert(newNode.ListNext == null);
|
||||
Debug.Assert(EqualsNull(newNode.ListPrevious));
|
||||
Debug.Assert(EqualsNull(newNode.ListNext));
|
||||
|
||||
newNode.ListPrevious = node.ListPrevious;
|
||||
newNode.ListNext = node;
|
||||
|
||||
node.ListPrevious = newNode;
|
||||
|
||||
if (newNode.ListPrevious != null)
|
||||
if (!EqualsNull(newNode.ListPrevious))
|
||||
{
|
||||
newNode.ListPrevious.ListNext = newNode;
|
||||
}
|
||||
|
||||
if (First == node)
|
||||
if (Equals(First, node))
|
||||
{
|
||||
First = newNode;
|
||||
}
|
||||
|
@ -117,20 +135,20 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T AddAfter(T node, T newNode)
|
||||
{
|
||||
Debug.Assert(newNode.ListPrevious == null);
|
||||
Debug.Assert(newNode.ListNext == null);
|
||||
Debug.Assert(EqualsNull(newNode.ListPrevious));
|
||||
Debug.Assert(EqualsNull(newNode.ListNext));
|
||||
|
||||
newNode.ListPrevious = node;
|
||||
newNode.ListNext = node.ListNext;
|
||||
|
||||
node.ListNext = newNode;
|
||||
|
||||
if (newNode.ListNext != null)
|
||||
if (!EqualsNull(newNode.ListNext))
|
||||
{
|
||||
newNode.ListNext.ListPrevious = newNode;
|
||||
}
|
||||
|
||||
if (Last == node)
|
||||
if (Equals(Last, node))
|
||||
{
|
||||
Last = newNode;
|
||||
}
|
||||
|
@ -147,32 +165,44 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Remove(T node)
|
||||
{
|
||||
if (node.ListPrevious != null)
|
||||
if (!EqualsNull(node.ListPrevious))
|
||||
{
|
||||
node.ListPrevious.ListNext = node.ListNext;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(First == node);
|
||||
Debug.Assert(Equals(First, node));
|
||||
|
||||
First = node.ListNext;
|
||||
}
|
||||
|
||||
if (node.ListNext != null)
|
||||
if (!EqualsNull(node.ListNext))
|
||||
{
|
||||
node.ListNext.ListPrevious = node.ListPrevious;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(Last == node);
|
||||
Debug.Assert(Equals(Last, node));
|
||||
|
||||
Last = node.ListPrevious;
|
||||
}
|
||||
|
||||
node.ListPrevious = null;
|
||||
node.ListNext = null;
|
||||
node.ListPrevious = default;
|
||||
node.ListNext = default;
|
||||
|
||||
Count--;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool EqualsNull(T a)
|
||||
{
|
||||
return EqualityComparer<T>.Default.Equals(a, default);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool Equals(T a, T b)
|
||||
{
|
||||
return EqualityComparer<T>.Default.Equals(a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,54 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
class MemoryOperand : Operand
|
||||
unsafe struct MemoryOperand
|
||||
{
|
||||
public Operand BaseAddress { get; set; }
|
||||
public Operand Index { get; set; }
|
||||
|
||||
public Multiplier Scale { get; private set; }
|
||||
|
||||
public int Displacement { get; private set; }
|
||||
|
||||
public MemoryOperand() { }
|
||||
|
||||
public MemoryOperand With(
|
||||
OperandType type,
|
||||
Operand baseAddress,
|
||||
Operand index = null,
|
||||
Multiplier scale = Multiplier.x1,
|
||||
int displacement = 0)
|
||||
private struct Data
|
||||
{
|
||||
With(OperandKind.Memory, type);
|
||||
BaseAddress = baseAddress;
|
||||
Index = index;
|
||||
Scale = scale;
|
||||
Displacement = displacement;
|
||||
return this;
|
||||
#pragma warning disable CS0649
|
||||
public byte Kind;
|
||||
public byte Type;
|
||||
#pragma warning restore CS0649
|
||||
public byte Scale;
|
||||
public Operand BaseAddress;
|
||||
public Operand Index;
|
||||
public int Displacement;
|
||||
}
|
||||
|
||||
private Data* _data;
|
||||
|
||||
public MemoryOperand(Operand operand)
|
||||
{
|
||||
Debug.Assert(operand.Kind == OperandKind.Memory);
|
||||
|
||||
_data = (Data*)Unsafe.As<Operand, IntPtr>(ref operand);
|
||||
}
|
||||
|
||||
public Operand BaseAddress
|
||||
{
|
||||
get => _data->BaseAddress;
|
||||
set => _data->BaseAddress = value;
|
||||
}
|
||||
|
||||
public Operand Index
|
||||
{
|
||||
get => _data->Index;
|
||||
set => _data->Index = value;
|
||||
}
|
||||
|
||||
public Multiplier Scale
|
||||
{
|
||||
get => (Multiplier)_data->Scale;
|
||||
set => _data->Scale = (byte)value;
|
||||
}
|
||||
|
||||
public int Displacement
|
||||
{
|
||||
get => _data->Displacement;
|
||||
set => _data->Displacement = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,309 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
class Node : IIntrusiveListNode<Node>
|
||||
{
|
||||
public Node ListPrevious { get; set; }
|
||||
public Node ListNext { get; set; }
|
||||
|
||||
public Operand Destination
|
||||
{
|
||||
get => _destinations.Count != 0 ? GetDestination(0) : null;
|
||||
set => SetDestination(value);
|
||||
}
|
||||
|
||||
private readonly List<Operand> _destinations;
|
||||
private readonly List<Operand> _sources;
|
||||
private bool _clearedDest;
|
||||
|
||||
public int DestinationsCount => _destinations.Count;
|
||||
public int SourcesCount => _sources.Count;
|
||||
|
||||
private void Resize(List<Operand> list, int size)
|
||||
{
|
||||
if (list.Count > size)
|
||||
{
|
||||
list.RemoveRange(size, list.Count - size);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (list.Count < size)
|
||||
{
|
||||
list.Add(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Node()
|
||||
{
|
||||
_destinations = new List<Operand>();
|
||||
_sources = new List<Operand>();
|
||||
}
|
||||
|
||||
public Node(Operand destination, int sourcesCount) : this()
|
||||
{
|
||||
Destination = destination;
|
||||
|
||||
Resize(_sources, sourcesCount);
|
||||
}
|
||||
|
||||
private void Reset(int sourcesCount)
|
||||
{
|
||||
_clearedDest = true;
|
||||
_sources.Clear();
|
||||
ListPrevious = null;
|
||||
ListNext = null;
|
||||
|
||||
Resize(_sources, sourcesCount);
|
||||
}
|
||||
|
||||
public Node With(Operand destination, int sourcesCount)
|
||||
{
|
||||
Reset(sourcesCount);
|
||||
Destination = destination;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Node With(Operand[] destinations, int sourcesCount)
|
||||
{
|
||||
Reset(sourcesCount);
|
||||
SetDestinations(destinations ?? throw new ArgumentNullException(nameof(destinations)));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Operand GetDestination(int index)
|
||||
{
|
||||
return _destinations[index];
|
||||
}
|
||||
|
||||
public Operand GetSource(int index)
|
||||
{
|
||||
return _sources[index];
|
||||
}
|
||||
|
||||
public void SetDestination(int index, Operand destination)
|
||||
{
|
||||
if (!_clearedDest)
|
||||
{
|
||||
RemoveAssignment(_destinations[index]);
|
||||
}
|
||||
|
||||
AddAssignment(destination);
|
||||
|
||||
_clearedDest = false;
|
||||
|
||||
_destinations[index] = destination;
|
||||
}
|
||||
|
||||
public void SetSource(int index, Operand source)
|
||||
{
|
||||
RemoveUse(_sources[index]);
|
||||
|
||||
AddUse(source);
|
||||
|
||||
_sources[index] = source;
|
||||
}
|
||||
|
||||
private void RemoveOldDestinations()
|
||||
{
|
||||
if (!_clearedDest)
|
||||
{
|
||||
for (int index = 0; index < _destinations.Count; index++)
|
||||
{
|
||||
RemoveAssignment(_destinations[index]);
|
||||
}
|
||||
}
|
||||
|
||||
_clearedDest = false;
|
||||
}
|
||||
|
||||
public void SetDestination(Operand destination)
|
||||
{
|
||||
RemoveOldDestinations();
|
||||
|
||||
if (destination == null)
|
||||
{
|
||||
_destinations.Clear();
|
||||
_clearedDest = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Resize(_destinations, 1);
|
||||
|
||||
_destinations[0] = destination;
|
||||
|
||||
AddAssignment(destination);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDestinations(Operand[] destinations)
|
||||
{
|
||||
RemoveOldDestinations();
|
||||
|
||||
Resize(_destinations, destinations.Length);
|
||||
|
||||
for (int index = 0; index < destinations.Length; index++)
|
||||
{
|
||||
Operand newOp = destinations[index];
|
||||
|
||||
_destinations[index] = newOp;
|
||||
|
||||
AddAssignment(newOp);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveOldSources()
|
||||
{
|
||||
for (int index = 0; index < _sources.Count; index++)
|
||||
{
|
||||
RemoveUse(_sources[index]);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSource(Operand source)
|
||||
{
|
||||
RemoveOldSources();
|
||||
|
||||
if (source == null)
|
||||
{
|
||||
_sources.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
Resize(_sources, 1);
|
||||
|
||||
_sources[0] = source;
|
||||
|
||||
AddUse(source);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSources(Operand[] sources)
|
||||
{
|
||||
RemoveOldSources();
|
||||
|
||||
Resize(_sources, sources.Length);
|
||||
|
||||
for (int index = 0; index < sources.Length; index++)
|
||||
{
|
||||
Operand newOp = sources[index];
|
||||
|
||||
_sources[index] = newOp;
|
||||
|
||||
AddUse(newOp);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddAssignment(Operand op)
|
||||
{
|
||||
if (op == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (op.Kind == OperandKind.LocalVariable)
|
||||
{
|
||||
op.Assignments.Add(this);
|
||||
}
|
||||
else if (op.Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memOp = (MemoryOperand)op;
|
||||
|
||||
if (memOp.BaseAddress != null)
|
||||
{
|
||||
memOp.BaseAddress.Assignments.Add(this);
|
||||
}
|
||||
|
||||
if (memOp.Index != null)
|
||||
{
|
||||
memOp.Index.Assignments.Add(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveAssignment(Operand op)
|
||||
{
|
||||
if (op == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (op.Kind == OperandKind.LocalVariable)
|
||||
{
|
||||
op.Assignments.Remove(this);
|
||||
}
|
||||
else if (op.Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memOp = (MemoryOperand)op;
|
||||
|
||||
if (memOp.BaseAddress != null)
|
||||
{
|
||||
memOp.BaseAddress.Assignments.Remove(this);
|
||||
}
|
||||
|
||||
if (memOp.Index != null)
|
||||
{
|
||||
memOp.Index.Assignments.Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUse(Operand op)
|
||||
{
|
||||
if (op == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (op.Kind == OperandKind.LocalVariable)
|
||||
{
|
||||
op.Uses.Add(this);
|
||||
}
|
||||
else if (op.Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memOp = (MemoryOperand)op;
|
||||
|
||||
if (memOp.BaseAddress != null)
|
||||
{
|
||||
memOp.BaseAddress.Uses.Add(this);
|
||||
}
|
||||
|
||||
if (memOp.Index != null)
|
||||
{
|
||||
memOp.Index.Uses.Add(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveUse(Operand op)
|
||||
{
|
||||
if (op == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (op.Kind == OperandKind.LocalVariable)
|
||||
{
|
||||
op.Uses.Remove(this);
|
||||
}
|
||||
else if (op.Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memOp = (MemoryOperand)op;
|
||||
|
||||
if (memOp.BaseAddress != null)
|
||||
{
|
||||
memOp.BaseAddress.Uses.Remove(this);
|
||||
}
|
||||
|
||||
if (memOp.Index != null)
|
||||
{
|
||||
memOp.Index.Uses.Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
using ARMeilleure.Common;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -6,96 +7,108 @@ using System.Runtime.CompilerServices;
|
|||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
class Operand
|
||||
unsafe struct Operand : IEquatable<Operand>
|
||||
{
|
||||
public OperandKind Kind { get; private set; }
|
||||
public OperandType Type { get; private set; }
|
||||
internal struct Data
|
||||
{
|
||||
public byte Kind;
|
||||
public byte Type;
|
||||
public byte SymbolType;
|
||||
public ushort AssignmentsCount;
|
||||
public ushort AssignmentsCapacity;
|
||||
public ushort UsesCount;
|
||||
public ushort UsesCapacity;
|
||||
public Operation* Assignments;
|
||||
public Operation* Uses;
|
||||
public ulong Value;
|
||||
public ulong SymbolValue;
|
||||
}
|
||||
|
||||
public ulong Value { get; private set; }
|
||||
private Data* _data;
|
||||
|
||||
public List<Node> Assignments { get; }
|
||||
public List<Node> Uses { get; }
|
||||
public OperandKind Kind
|
||||
{
|
||||
get => (OperandKind)_data->Kind;
|
||||
private set => _data->Kind = (byte)value;
|
||||
}
|
||||
|
||||
public OperandType Type
|
||||
{
|
||||
get => (OperandType)_data->Type;
|
||||
private set => _data->Type = (byte)value;
|
||||
}
|
||||
|
||||
public ulong Value
|
||||
{
|
||||
get => _data->Value;
|
||||
private set => _data->Value = value;
|
||||
}
|
||||
|
||||
public Symbol Symbol
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(Kind != OperandKind.Memory);
|
||||
|
||||
return new Symbol((SymbolType)_data->SymbolType, _data->SymbolValue);
|
||||
}
|
||||
private set
|
||||
{
|
||||
Debug.Assert(Kind != OperandKind.Memory);
|
||||
|
||||
if (value.Type == SymbolType.None)
|
||||
{
|
||||
_data->SymbolType = (byte)SymbolType.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
_data->SymbolType = (byte)value.Type;
|
||||
_data->SymbolValue = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlySpan<Operation> Assignments
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(Kind != OperandKind.Memory);
|
||||
|
||||
return new ReadOnlySpan<Operation>(_data->Assignments, _data->AssignmentsCount);
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlySpan<Operation> Uses
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(Kind != OperandKind.Memory);
|
||||
|
||||
return new ReadOnlySpan<Operation>(_data->Uses, _data->UsesCount);
|
||||
}
|
||||
}
|
||||
|
||||
public int UsesCount => _data->UsesCount;
|
||||
public int AssignmentsCount => _data->AssignmentsCount;
|
||||
|
||||
public Symbol Symbol { get; private set; }
|
||||
public bool Relocatable => Symbol.Type != SymbolType.None;
|
||||
|
||||
public Operand()
|
||||
{
|
||||
Assignments = new List<Node>();
|
||||
Uses = new List<Node>();
|
||||
}
|
||||
|
||||
public Operand(OperandKind kind, OperandType type = OperandType.None) : this()
|
||||
{
|
||||
Kind = kind;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public Operand With(
|
||||
OperandKind kind,
|
||||
OperandType type = OperandType.None,
|
||||
ulong value = 0,
|
||||
Symbol symbol = default)
|
||||
{
|
||||
Kind = kind;
|
||||
Type = type;
|
||||
|
||||
Value = value;
|
||||
|
||||
Symbol = symbol;
|
||||
|
||||
Assignments.Clear();
|
||||
Uses.Clear();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Operand With(int value)
|
||||
{
|
||||
return With(OperandKind.Constant, OperandType.I32, (uint)value);
|
||||
}
|
||||
|
||||
public Operand With(uint value)
|
||||
{
|
||||
return With(OperandKind.Constant, OperandType.I32, value);
|
||||
}
|
||||
|
||||
public Operand With(long value)
|
||||
{
|
||||
return With(OperandKind.Constant, OperandType.I64, (ulong)value);
|
||||
}
|
||||
|
||||
public Operand With(long value, Symbol symbol)
|
||||
{
|
||||
return With(OperandKind.Constant, OperandType.I64, (ulong)value, symbol);
|
||||
}
|
||||
|
||||
public Operand With(ulong value)
|
||||
{
|
||||
return With(OperandKind.Constant, OperandType.I64, value);
|
||||
}
|
||||
|
||||
public Operand With(float value)
|
||||
{
|
||||
return With(OperandKind.Constant, OperandType.FP32, (ulong)BitConverter.SingleToInt32Bits(value));
|
||||
}
|
||||
|
||||
public Operand With(double value)
|
||||
{
|
||||
return With(OperandKind.Constant, OperandType.FP64, (ulong)BitConverter.DoubleToInt64Bits(value));
|
||||
}
|
||||
|
||||
public Operand With(int index, RegisterType regType, OperandType type)
|
||||
{
|
||||
return With(OperandKind.Register, type, (ulong)((int)regType << 24 | index));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Register GetRegister()
|
||||
{
|
||||
Debug.Assert(Kind == OperandKind.Register);
|
||||
|
||||
return new Register((int)Value & 0xffffff, (RegisterType)(Value >> 24));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public MemoryOperand GetMemory()
|
||||
{
|
||||
Debug.Assert(Kind == OperandKind.Memory);
|
||||
|
||||
return new MemoryOperand(this);
|
||||
}
|
||||
|
||||
public int GetLocalNumber()
|
||||
{
|
||||
Debug.Assert(Kind == OperandKind.LocalVariable);
|
||||
|
@ -133,6 +146,11 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
return BitConverter.Int64BitsToDouble((long)Value);
|
||||
}
|
||||
|
||||
internal ref ulong GetValueUnsafe()
|
||||
{
|
||||
return ref _data->Value;
|
||||
}
|
||||
|
||||
internal void NumberLocal(int number)
|
||||
{
|
||||
if (Kind != OperandKind.LocalVariable)
|
||||
|
@ -143,6 +161,158 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
Value = (ulong)number;
|
||||
}
|
||||
|
||||
public void AddAssignment(Operation operation)
|
||||
{
|
||||
if (Kind == OperandKind.LocalVariable)
|
||||
{
|
||||
Add(operation, ref _data->Assignments, ref _data->AssignmentsCount, ref _data->AssignmentsCapacity);
|
||||
}
|
||||
else if (Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memOp = GetMemory();
|
||||
Operand addr = memOp.BaseAddress;
|
||||
Operand index = memOp.Index;
|
||||
|
||||
if (addr != default)
|
||||
{
|
||||
Add(operation, ref addr._data->Assignments, ref addr._data->AssignmentsCount, ref addr._data->AssignmentsCapacity);
|
||||
}
|
||||
|
||||
if (index != default)
|
||||
{
|
||||
Add(operation, ref index._data->Assignments, ref index._data->AssignmentsCount, ref index._data->AssignmentsCapacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAssignment(Operation operation)
|
||||
{
|
||||
if (Kind == OperandKind.LocalVariable)
|
||||
{
|
||||
Remove(operation, ref _data->Assignments, ref _data->AssignmentsCount);
|
||||
}
|
||||
else if (Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memOp = GetMemory();
|
||||
Operand addr = memOp.BaseAddress;
|
||||
Operand index = memOp.Index;
|
||||
|
||||
if (addr != default)
|
||||
{
|
||||
Remove(operation, ref addr._data->Assignments, ref addr._data->AssignmentsCount);
|
||||
}
|
||||
|
||||
if (index != default)
|
||||
{
|
||||
Remove(operation, ref index._data->Assignments, ref index._data->AssignmentsCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddUse(Operation operation)
|
||||
{
|
||||
if (Kind == OperandKind.LocalVariable)
|
||||
{
|
||||
Add(operation, ref _data->Uses, ref _data->UsesCount, ref _data->UsesCapacity);
|
||||
}
|
||||
else if (Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memOp = GetMemory();
|
||||
Operand addr = memOp.BaseAddress;
|
||||
Operand index = memOp.Index;
|
||||
|
||||
if (addr != default)
|
||||
{
|
||||
Add(operation, ref addr._data->Uses, ref addr._data->UsesCount, ref addr._data->UsesCapacity);
|
||||
}
|
||||
|
||||
if (index != default)
|
||||
{
|
||||
Add(operation, ref index._data->Uses, ref index._data->UsesCount, ref index._data->UsesCapacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveUse(Operation operation)
|
||||
{
|
||||
if (Kind == OperandKind.LocalVariable)
|
||||
{
|
||||
Remove(operation, ref _data->Uses, ref _data->UsesCount);
|
||||
}
|
||||
else if (Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memOp = GetMemory();
|
||||
Operand addr = memOp.BaseAddress;
|
||||
Operand index = memOp.Index;
|
||||
|
||||
if (addr != default)
|
||||
{
|
||||
Remove(operation, ref addr._data->Uses, ref addr._data->UsesCount);
|
||||
}
|
||||
|
||||
if (index != default)
|
||||
{
|
||||
Remove(operation, ref index._data->Uses, ref index._data->UsesCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void New<T>(ref T* data, ref ushort count, ref ushort capacity, ushort initialCapacity) where T : unmanaged
|
||||
{
|
||||
count = 0;
|
||||
capacity = initialCapacity;
|
||||
data = Allocators.References.Allocate<T>(initialCapacity);
|
||||
}
|
||||
|
||||
private static void Add<T>(T item, ref T* data, ref ushort count, ref ushort capacity) where T : unmanaged
|
||||
{
|
||||
if (count < capacity)
|
||||
{
|
||||
data[(uint)count++] = item;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Could not add item in the fast path, fallback onto the slow path.
|
||||
ExpandAdd(item, ref data, ref count, ref capacity);
|
||||
|
||||
static void ExpandAdd(T item, ref T* data, ref ushort count, ref ushort capacity)
|
||||
{
|
||||
ushort newCount = checked((ushort)(count + 1));
|
||||
ushort newCapacity = (ushort)Math.Min(capacity * 2, ushort.MaxValue);
|
||||
|
||||
var oldSpan = new Span<T>(data, count);
|
||||
|
||||
capacity = newCapacity;
|
||||
data = Allocators.References.Allocate<T>(capacity);
|
||||
|
||||
oldSpan.CopyTo(new Span<T>(data, count));
|
||||
|
||||
data[count] = item;
|
||||
count = newCount;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Remove<T>(in T item, ref T* data, ref ushort count) where T : unmanaged
|
||||
{
|
||||
var span = new Span<T>(data, count);
|
||||
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(span[i], item))
|
||||
{
|
||||
if (i + 1 < count)
|
||||
{
|
||||
span.Slice(i + 1).CopyTo(span.Slice(i));
|
||||
}
|
||||
|
||||
count--;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (Kind == OperandKind.LocalVariable)
|
||||
|
@ -154,5 +324,201 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
return (int)Value ^ ((int)Kind << 16) ^ ((int)Type << 20);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(Operand operand)
|
||||
{
|
||||
return operand._data == _data;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Operand operand && Equals(operand);
|
||||
}
|
||||
|
||||
public static bool operator ==(Operand a, Operand b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Operand a, Operand b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
public static class Factory
|
||||
{
|
||||
private const int InternTableSize = 256;
|
||||
private const int InternTableProbeLength = 8;
|
||||
|
||||
[ThreadStatic]
|
||||
private static Data* _internTable;
|
||||
|
||||
private static Data* InternTable
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_internTable == null)
|
||||
{
|
||||
_internTable = (Data*)NativeAllocator.Instance.Allocate((uint)sizeof(Data) * InternTableSize);
|
||||
|
||||
// Make sure the table is zeroed.
|
||||
new Span<Data>(_internTable, InternTableSize).Clear();
|
||||
}
|
||||
|
||||
return _internTable;
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand Make(OperandKind kind, OperandType type, ulong value, Symbol symbol = default)
|
||||
{
|
||||
Debug.Assert(kind != OperandKind.None);
|
||||
|
||||
Data* data = null;
|
||||
|
||||
// If constant or register, then try to look up in the intern table before allocating.
|
||||
if (kind == OperandKind.Constant || kind == OperandKind.Register)
|
||||
{
|
||||
uint hash = (uint)HashCode.Combine(kind, type, value);
|
||||
|
||||
// Look in the next InternTableProbeLength slots for a match.
|
||||
for (uint i = 0; i < InternTableProbeLength; i++)
|
||||
{
|
||||
Operand interned = new();
|
||||
interned._data = &InternTable[(hash + i) % InternTableSize];
|
||||
|
||||
// If slot matches the allocation request then return that slot.
|
||||
if (interned.Kind == kind && interned.Type == type && interned.Value == value && interned.Symbol == symbol)
|
||||
{
|
||||
return interned;
|
||||
}
|
||||
// Otherwise if the slot is not occupied, we store in that slot.
|
||||
else if (interned.Kind == OperandKind.None)
|
||||
{
|
||||
data = interned._data;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we could not get a slot from the intern table, we allocate somewhere else and store there.
|
||||
if (data == null)
|
||||
{
|
||||
data = Allocators.Operands.Allocate<Data>();
|
||||
}
|
||||
|
||||
*data = default;
|
||||
|
||||
Operand result = new();
|
||||
result._data = data;
|
||||
result.Value = value;
|
||||
result.Kind = kind;
|
||||
result.Type = type;
|
||||
|
||||
if (kind != OperandKind.Memory)
|
||||
{
|
||||
result.Symbol = symbol;
|
||||
}
|
||||
|
||||
// If local variable, then the use and def list is initialized with default sizes.
|
||||
if (kind == OperandKind.LocalVariable)
|
||||
{
|
||||
New(ref result._data->Assignments, ref result._data->AssignmentsCount, ref result._data->AssignmentsCapacity, 1);
|
||||
New(ref result._data->Uses, ref result._data->UsesCount, ref result._data->UsesCapacity, 4);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Operand Const(OperandType type, long value)
|
||||
{
|
||||
Debug.Assert(type is OperandType.I32 or OperandType.I64);
|
||||
|
||||
return type == OperandType.I32 ? Const((int)value) : Const(value);
|
||||
}
|
||||
|
||||
public static Operand Const(bool value)
|
||||
{
|
||||
return Const(value ? 1 : 0);
|
||||
}
|
||||
|
||||
public static Operand Const(int value)
|
||||
{
|
||||
return Const((uint)value);
|
||||
}
|
||||
|
||||
public static Operand Const(uint value)
|
||||
{
|
||||
return Make(OperandKind.Constant, OperandType.I32, value);
|
||||
}
|
||||
|
||||
public static Operand Const(long value)
|
||||
{
|
||||
return Const(value, symbol: default);
|
||||
}
|
||||
|
||||
public static Operand Const<T>(ref T reference, Symbol symbol = default)
|
||||
{
|
||||
return Const((long)Unsafe.AsPointer(ref reference), symbol);
|
||||
}
|
||||
|
||||
public static Operand Const(long value, Symbol symbol)
|
||||
{
|
||||
return Make(OperandKind.Constant, OperandType.I64, (ulong)value, symbol);
|
||||
}
|
||||
|
||||
public static Operand Const(ulong value)
|
||||
{
|
||||
return Make(OperandKind.Constant, OperandType.I64, value);
|
||||
}
|
||||
|
||||
public static Operand ConstF(float value)
|
||||
{
|
||||
return Make(OperandKind.Constant, OperandType.FP32, (ulong)BitConverter.SingleToInt32Bits(value));
|
||||
}
|
||||
|
||||
public static Operand ConstF(double value)
|
||||
{
|
||||
return Make(OperandKind.Constant, OperandType.FP64, (ulong)BitConverter.DoubleToInt64Bits(value));
|
||||
}
|
||||
|
||||
public static Operand Label()
|
||||
{
|
||||
return Make(OperandKind.Label, OperandType.None, 0);
|
||||
}
|
||||
|
||||
public static Operand Local(OperandType type)
|
||||
{
|
||||
return Make(OperandKind.LocalVariable, type, 0);
|
||||
}
|
||||
|
||||
public static Operand Register(int index, RegisterType regType, OperandType type)
|
||||
{
|
||||
return Make(OperandKind.Register, type, (ulong)((int)regType << 24 | index));
|
||||
}
|
||||
|
||||
public static Operand Undef()
|
||||
{
|
||||
return Make(OperandKind.Undefined, OperandType.None, 0);
|
||||
}
|
||||
|
||||
public static Operand MemoryOp(
|
||||
OperandType type,
|
||||
Operand baseAddress,
|
||||
Operand index = default,
|
||||
Multiplier scale = Multiplier.x1,
|
||||
int displacement = 0)
|
||||
{
|
||||
Operand result = Make(OperandKind.Memory, type, 0);
|
||||
|
||||
MemoryOperand memory = result.GetMemory();
|
||||
memory.BaseAddress = baseAddress;
|
||||
memory.Index = index;
|
||||
memory.Scale = scale;
|
||||
memory.Displacement = displacement;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
using ARMeilleure.Common;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
static class OperandHelper
|
||||
{
|
||||
public static Operand Const(OperandType type, long value)
|
||||
{
|
||||
return type == OperandType.I32 ? Operand().With((int)value) : Operand().With(value);
|
||||
}
|
||||
|
||||
public static Operand Const(bool value)
|
||||
{
|
||||
return Operand().With(value ? 1 : 0);
|
||||
}
|
||||
|
||||
public static Operand Const(int value)
|
||||
{
|
||||
return Operand().With(value);
|
||||
}
|
||||
|
||||
public static Operand Const(uint value)
|
||||
{
|
||||
return Operand().With(value);
|
||||
}
|
||||
|
||||
public static Operand Const(long value)
|
||||
{
|
||||
return Operand().With(value);
|
||||
}
|
||||
|
||||
public static Operand Const(long value, Symbol symbol)
|
||||
{
|
||||
return Operand().With(value, symbol);
|
||||
}
|
||||
|
||||
public static Operand Const(ulong value)
|
||||
{
|
||||
return Operand().With(value);
|
||||
}
|
||||
|
||||
public static unsafe Operand Const<T>(ref T reference, Symbol symbol = default)
|
||||
{
|
||||
return Operand().With((long)Unsafe.AsPointer(ref reference), symbol);
|
||||
}
|
||||
|
||||
public static Operand ConstF(float value)
|
||||
{
|
||||
return Operand().With(value);
|
||||
}
|
||||
|
||||
public static Operand ConstF(double value)
|
||||
{
|
||||
return Operand().With(value);
|
||||
}
|
||||
|
||||
public static Operand Label()
|
||||
{
|
||||
return Operand().With(OperandKind.Label);
|
||||
}
|
||||
|
||||
public static Operand Local(OperandType type)
|
||||
{
|
||||
return Operand().With(OperandKind.LocalVariable, type);
|
||||
}
|
||||
|
||||
public static Operand Register(int index, RegisterType regType, OperandType type)
|
||||
{
|
||||
return Operand().With(index, regType, type);
|
||||
}
|
||||
|
||||
public static Operand Undef()
|
||||
{
|
||||
return Operand().With(OperandKind.Undefined);
|
||||
}
|
||||
|
||||
public static MemoryOperand MemoryOp(
|
||||
OperandType type,
|
||||
Operand baseAddress,
|
||||
Operand index = null,
|
||||
Multiplier scale = Multiplier.x1,
|
||||
int displacement = 0)
|
||||
{
|
||||
return MemoryOperand().With(type, baseAddress, index, scale, displacement);
|
||||
}
|
||||
|
||||
#region "ThreadStaticPool"
|
||||
public static void PrepareOperandPool(int groupId = 0)
|
||||
{
|
||||
ThreadStaticPool<Operand>.PreparePool(groupId, ChunkSizeLimit.Large);
|
||||
ThreadStaticPool<MemoryOperand>.PreparePool(groupId, ChunkSizeLimit.Small);
|
||||
}
|
||||
|
||||
private static Operand Operand()
|
||||
{
|
||||
return ThreadStaticPool<Operand>.Instance.Allocate();
|
||||
}
|
||||
|
||||
private static MemoryOperand MemoryOperand()
|
||||
{
|
||||
return ThreadStaticPool<MemoryOperand>.Instance.Allocate();
|
||||
}
|
||||
|
||||
public static void ResetOperandPool(int groupId = 0)
|
||||
{
|
||||
ThreadStaticPool<MemoryOperand>.ResetPool(groupId);
|
||||
ThreadStaticPool<Operand>.ResetPool(groupId);
|
||||
}
|
||||
|
||||
public static void DisposeOperandPools()
|
||||
{
|
||||
ThreadStaticPool<Operand>.DisposePools();
|
||||
ThreadStaticPool<MemoryOperand>.DisposePools();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
{
|
||||
enum OperandKind
|
||||
{
|
||||
None,
|
||||
Constant,
|
||||
Label,
|
||||
LocalVariable,
|
||||
|
|
|
@ -1,89 +1,180 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
class Operation : Node
|
||||
unsafe struct Operation : IEquatable<Operation>, IIntrusiveListNode<Operation>
|
||||
{
|
||||
public Instruction Instruction { get; private set; }
|
||||
|
||||
public Operation() : base() { }
|
||||
|
||||
public Operation(
|
||||
Instruction instruction,
|
||||
Operand destination,
|
||||
Operand[] sources) : base(destination, sources.Length)
|
||||
internal struct Data
|
||||
{
|
||||
Instruction = instruction;
|
||||
public ushort Instruction;
|
||||
public ushort Intrinsic;
|
||||
public ushort SourcesCount;
|
||||
public ushort DestinationsCount;
|
||||
public Operation ListPrevious;
|
||||
public Operation ListNext;
|
||||
public Operand* Destinations;
|
||||
public Operand* Sources;
|
||||
}
|
||||
|
||||
for (int index = 0; index < sources.Length; index++)
|
||||
private Data* _data;
|
||||
|
||||
public Instruction Instruction
|
||||
{
|
||||
get => (Instruction)_data->Instruction;
|
||||
private set => _data->Instruction = (ushort)value;
|
||||
}
|
||||
|
||||
public Intrinsic Intrinsic
|
||||
{
|
||||
get => (Intrinsic)_data->Intrinsic;
|
||||
private set => _data->Intrinsic = (ushort)value;
|
||||
}
|
||||
|
||||
public Operation ListPrevious
|
||||
{
|
||||
get => _data->ListPrevious;
|
||||
set => _data->ListPrevious = value;
|
||||
}
|
||||
|
||||
public Operation ListNext
|
||||
{
|
||||
get => _data->ListNext;
|
||||
set => _data->ListNext = value;
|
||||
}
|
||||
|
||||
public Operand Destination
|
||||
{
|
||||
get => _data->DestinationsCount != 0 ? GetDestination(0) : default;
|
||||
set => SetDestination(value);
|
||||
}
|
||||
|
||||
public int DestinationsCount => _data->DestinationsCount;
|
||||
public int SourcesCount => _data->SourcesCount;
|
||||
|
||||
private Span<Operand> Destinations => new(_data->Destinations, _data->DestinationsCount);
|
||||
private Span<Operand> Sources => new(_data->Sources, _data->SourcesCount);
|
||||
|
||||
public PhiOperation AsPhi()
|
||||
{
|
||||
Debug.Assert(Instruction == Instruction.Phi);
|
||||
|
||||
return new PhiOperation(this);
|
||||
}
|
||||
|
||||
public Operand GetDestination(int index)
|
||||
{
|
||||
return Destinations[index];
|
||||
}
|
||||
|
||||
public Operand GetSource(int index)
|
||||
{
|
||||
return Sources[index];
|
||||
}
|
||||
|
||||
public void SetDestination(int index, Operand dest)
|
||||
{
|
||||
ref Operand curDest = ref Destinations[index];
|
||||
|
||||
RemoveAssignment(curDest);
|
||||
AddAssignment(dest);
|
||||
|
||||
curDest = dest;
|
||||
}
|
||||
|
||||
public void SetSource(int index, Operand src)
|
||||
{
|
||||
ref Operand curSrc = ref Sources[index];
|
||||
|
||||
RemoveUse(curSrc);
|
||||
AddUse(src);
|
||||
|
||||
curSrc = src;
|
||||
}
|
||||
|
||||
private void RemoveOldDestinations()
|
||||
{
|
||||
for (int i = 0; i < _data->DestinationsCount; i++)
|
||||
{
|
||||
SetSource(index, sources[index]);
|
||||
RemoveAssignment(_data->Destinations[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public Operation With(Instruction instruction, Operand destination)
|
||||
public void SetDestination(Operand dest)
|
||||
{
|
||||
With(destination, 0);
|
||||
Instruction = instruction;
|
||||
return this;
|
||||
}
|
||||
RemoveOldDestinations();
|
||||
|
||||
public Operation With(Instruction instruction, Operand destination, Operand[] sources)
|
||||
{
|
||||
With(destination, sources.Length);
|
||||
Instruction = instruction;
|
||||
|
||||
for (int index = 0; index < sources.Length; index++)
|
||||
if (dest == default)
|
||||
{
|
||||
SetSource(index, sources[index]);
|
||||
_data->DestinationsCount = 0;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Operation With(Instruction instruction, Operand destination,
|
||||
Operand source0)
|
||||
{
|
||||
With(destination, 1);
|
||||
Instruction = instruction;
|
||||
|
||||
SetSource(0, source0);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Operation With(Instruction instruction, Operand destination,
|
||||
Operand source0, Operand source1)
|
||||
{
|
||||
With(destination, 2);
|
||||
Instruction = instruction;
|
||||
|
||||
SetSource(0, source0);
|
||||
SetSource(1, source1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Operation With(Instruction instruction, Operand destination,
|
||||
Operand source0, Operand source1, Operand source2)
|
||||
{
|
||||
With(destination, 3);
|
||||
Instruction = instruction;
|
||||
|
||||
SetSource(0, source0);
|
||||
SetSource(1, source1);
|
||||
SetSource(2, source2);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Operation With(
|
||||
Instruction instruction,
|
||||
Operand[] destinations,
|
||||
Operand[] sources)
|
||||
{
|
||||
With(destinations, sources.Length);
|
||||
Instruction = instruction;
|
||||
|
||||
for (int index = 0; index < sources.Length; index++)
|
||||
else
|
||||
{
|
||||
SetSource(index, sources[index]);
|
||||
EnsureCapacity(ref _data->Destinations, ref _data->DestinationsCount, 1);
|
||||
|
||||
_data->Destinations[0] = dest;
|
||||
|
||||
AddAssignment(dest);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDestinations(Operand[] dests)
|
||||
{
|
||||
RemoveOldDestinations();
|
||||
|
||||
EnsureCapacity(ref _data->Destinations, ref _data->DestinationsCount, dests.Length);
|
||||
|
||||
for (int index = 0; index < dests.Length; index++)
|
||||
{
|
||||
Operand newOp = dests[index];
|
||||
|
||||
_data->Destinations[index] = newOp;
|
||||
|
||||
AddAssignment(newOp);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveOldSources()
|
||||
{
|
||||
for (int index = 0; index < _data->SourcesCount; index++)
|
||||
{
|
||||
RemoveUse(_data->Sources[index]);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSource(Operand src)
|
||||
{
|
||||
RemoveOldSources();
|
||||
|
||||
if (src == default)
|
||||
{
|
||||
_data->SourcesCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
EnsureCapacity(ref _data->Sources, ref _data->SourcesCount, 1);
|
||||
|
||||
_data->Sources[0] = src;
|
||||
|
||||
AddUse(src);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSources(Operand[] srcs)
|
||||
{
|
||||
RemoveOldSources();
|
||||
|
||||
EnsureCapacity(ref _data->Sources, ref _data->SourcesCount, srcs.Length);
|
||||
|
||||
for (int index = 0; index < srcs.Length; index++)
|
||||
{
|
||||
Operand newOp = srcs[index];
|
||||
|
||||
_data->Sources[index] = newOp;
|
||||
|
||||
AddUse(newOp);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void TurnIntoCopy(Operand source)
|
||||
|
@ -92,5 +183,194 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||
|
||||
SetSource(source);
|
||||
}
|
||||
|
||||
private void AddAssignment(Operand op)
|
||||
{
|
||||
if (op != default)
|
||||
{
|
||||
op.AddAssignment(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveAssignment(Operand op)
|
||||
{
|
||||
if (op != default)
|
||||
{
|
||||
op.RemoveAssignment(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUse(Operand op)
|
||||
{
|
||||
if (op != default)
|
||||
{
|
||||
op.AddUse(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveUse(Operand op)
|
||||
{
|
||||
if (op != default)
|
||||
{
|
||||
op.RemoveUse(this);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(Operation operation)
|
||||
{
|
||||
return operation._data == _data;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Operation operation && Equals(operation);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine((IntPtr)_data);
|
||||
}
|
||||
|
||||
public static bool operator ==(Operation a, Operation b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Operation a, Operation b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void EnsureCapacity(ref Operand* list, ref ushort capacity, int newCapacity)
|
||||
{
|
||||
if (newCapacity > ushort.MaxValue)
|
||||
{
|
||||
ThrowOverflow(newCapacity);
|
||||
}
|
||||
// We only need to allocate a new buffer if we're increasing the size.
|
||||
else if (newCapacity > capacity)
|
||||
{
|
||||
list = Allocators.References.Allocate<Operand>((uint)newCapacity);
|
||||
}
|
||||
|
||||
capacity = (ushort)newCapacity;
|
||||
}
|
||||
|
||||
private static void ThrowOverflow(int count) =>
|
||||
throw new OverflowException($"Exceeded maximum size for Source or Destinations. Required {count}.");
|
||||
|
||||
public static class Factory
|
||||
{
|
||||
private static Operation Make(Instruction inst, int destCount, int srcCount)
|
||||
{
|
||||
Data* data = Allocators.Operations.Allocate<Data>();
|
||||
*data = default;
|
||||
|
||||
Operation result = new();
|
||||
result._data = data;
|
||||
result.Instruction = inst;
|
||||
|
||||
EnsureCapacity(ref result._data->Destinations, ref result._data->DestinationsCount, destCount);
|
||||
EnsureCapacity(ref result._data->Sources, ref result._data->SourcesCount, srcCount);
|
||||
|
||||
result.Destinations.Clear();
|
||||
result.Sources.Clear();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Operation Operation(Instruction inst, Operand dest)
|
||||
{
|
||||
Operation result = Make(inst, 0, 0);
|
||||
result.SetDestination(dest);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Operation Operation(Instruction inst, Operand dest, Operand src0)
|
||||
{
|
||||
Operation result = Make(inst, 0, 1);
|
||||
result.SetDestination(dest);
|
||||
result.SetSource(0, src0);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Operation Operation(Instruction inst, Operand dest, Operand src0, Operand src1)
|
||||
{
|
||||
Operation result = Make(inst, 0, 2);
|
||||
result.SetDestination(dest);
|
||||
result.SetSource(0, src0);
|
||||
result.SetSource(1, src1);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Operation Operation(Instruction inst, Operand dest, Operand src0, Operand src1, Operand src2)
|
||||
{
|
||||
Operation result = Make(inst, 0, 3);
|
||||
result.SetDestination(dest);
|
||||
result.SetSource(0, src0);
|
||||
result.SetSource(1, src1);
|
||||
result.SetSource(2, src2);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Operation Operation(Instruction inst, Operand dest, int srcCount)
|
||||
{
|
||||
Operation result = Make(inst, 0, srcCount);
|
||||
result.SetDestination(dest);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Operation Operation(Instruction inst, Operand dest, Operand[] srcs)
|
||||
{
|
||||
Operation result = Make(inst, 0, srcs.Length);
|
||||
|
||||
result.SetDestination(dest);
|
||||
|
||||
for (int index = 0; index < srcs.Length; index++)
|
||||
{
|
||||
result.SetSource(index, srcs[index]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Operation Operation(Intrinsic intrin, Operand dest, params Operand[] srcs)
|
||||
{
|
||||
Operation result = Make(Instruction.Extended, 0, srcs.Length);
|
||||
|
||||
result.Intrinsic = intrin;
|
||||
result.SetDestination(dest);
|
||||
|
||||
for (int index = 0; index < srcs.Length; index++)
|
||||
{
|
||||
result.SetSource(index, srcs[index]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Operation Operation(Instruction inst, Operand[] dests, Operand[] srcs)
|
||||
{
|
||||
Operation result = Make(inst, dests.Length, srcs.Length);
|
||||
|
||||
for (int index = 0; index < dests.Length; index++)
|
||||
{
|
||||
result.SetDestination(index, dests[index]);
|
||||
}
|
||||
|
||||
for (int index = 0; index < srcs.Length; index++)
|
||||
{
|
||||
result.SetSource(index, srcs[index]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Operation PhiOperation(Operand dest, int srcCount)
|
||||
{
|
||||
return Operation(Instruction.Phi, dest, srcCount * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
using ARMeilleure.Common;
|
||||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
static class OperationHelper
|
||||
{
|
||||
public static Operation Operation(Instruction instruction, Operand destination)
|
||||
{
|
||||
return Operation().With(instruction, destination);
|
||||
}
|
||||
|
||||
public static Operation Operation(Instruction instruction, Operand destination,
|
||||
Operand[] sources)
|
||||
{
|
||||
return Operation().With(instruction, destination, sources);
|
||||
}
|
||||
|
||||
public static Operation Operation(Instruction instruction, Operand destination,
|
||||
Operand source0)
|
||||
{
|
||||
return Operation().With(instruction, destination, source0);
|
||||
}
|
||||
|
||||
public static Operation Operation(Instruction instruction, Operand destination,
|
||||
Operand source0, Operand source1)
|
||||
{
|
||||
return Operation().With(instruction, destination, source0, source1);
|
||||
}
|
||||
|
||||
public static Operation Operation(Instruction instruction, Operand destination,
|
||||
Operand source0, Operand source1, Operand source2)
|
||||
{
|
||||
return Operation().With(instruction, destination, source0, source1, source2);
|
||||
}
|
||||
|
||||
public static Operation Operation(
|
||||
Instruction instruction,
|
||||
Operand[] destinations,
|
||||
Operand[] sources)
|
||||
{
|
||||
return Operation().With(instruction, destinations, sources);
|
||||
}
|
||||
|
||||
#region "ThreadStaticPool"
|
||||
public static void PrepareOperationPool(int groupId = 0)
|
||||
{
|
||||
ThreadStaticPool<Operation>.PreparePool(groupId, ChunkSizeLimit.Medium);
|
||||
}
|
||||
|
||||
private static Operation Operation()
|
||||
{
|
||||
return ThreadStaticPool<Operation>.Instance.Allocate();
|
||||
}
|
||||
|
||||
public static void ResetOperationPool(int groupId = 0)
|
||||
{
|
||||
ThreadStaticPool<Operation>.ResetPool(groupId);
|
||||
}
|
||||
|
||||
public static void DisposeOperationPools()
|
||||
{
|
||||
ThreadStaticPool<Operation>.DisposePools();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
class PhiNode : Node
|
||||
{
|
||||
private BasicBlock[] _blocks;
|
||||
|
||||
public PhiNode(Operand destination, int predecessorsCount) : base(destination, predecessorsCount)
|
||||
{
|
||||
_blocks = new BasicBlock[predecessorsCount];
|
||||
}
|
||||
|
||||
public BasicBlock GetBlock(int index)
|
||||
{
|
||||
return _blocks[index];
|
||||
}
|
||||
|
||||
public void SetBlock(int index, BasicBlock block)
|
||||
{
|
||||
_blocks[index] = block;
|
||||
}
|
||||
}
|
||||
}
|
37
ARMeilleure/IntermediateRepresentation/PhiOperation.cs
Normal file
37
ARMeilleure/IntermediateRepresentation/PhiOperation.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using ARMeilleure.Translation;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
struct PhiOperation
|
||||
{
|
||||
private readonly Operation _operation;
|
||||
|
||||
public PhiOperation(Operation operation)
|
||||
{
|
||||
_operation = operation;
|
||||
}
|
||||
|
||||
public int SourcesCount => _operation.SourcesCount / 2;
|
||||
|
||||
public BasicBlock GetBlock(ControlFlowGraph cfg, int index)
|
||||
{
|
||||
return cfg.PostOrderBlocks[cfg.PostOrderMap[_operation.GetSource(index * 2).AsInt32()]];
|
||||
}
|
||||
|
||||
public void SetBlock(int index, BasicBlock block)
|
||||
{
|
||||
_operation.SetSource(index * 2, Const(block.Index));
|
||||
}
|
||||
|
||||
public Operand GetSource(int index)
|
||||
{
|
||||
return _operation.GetSource(index * 2 + 1);
|
||||
}
|
||||
|
||||
public void SetSource(int index, Operand operand)
|
||||
{
|
||||
_operation.SetSource(index * 2 + 1, operand);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue