Improve branch operations (#1442)

* Add Compare instruction

* Add BranchIf instruction

* Use test when BranchIf & Compare against 0

* Propagate Compare into BranchIfTrue/False use

- Propagate Compare operations into their BranchIfTrue/False use and
  turn these into a BranchIf.

- Clean up Comparison enum.

* Replace BranchIfTrue/False with BranchIf

* Use BranchIf in EmitPtPointerLoad

- Using BranchIf early instead of BranchIfTrue/False improves LCQ and
  reduces the amount of work needed by the Optimizer.

  EmitPtPointerLoader was a/the big producer of BranchIfTrue/False.

- Fix asserts firing when assembling BitwiseAnd because of type
  mismatch in EmitStoreExclusive. This is harmless and should not
  cause any diffs.

* Increment PPTC interval version

* Improve IRDumper for BranchIf & Compare

* Use BranchIf in EmitNativeCall

* Clean up

* Do not emit test when immediately preceded by and
This commit is contained in:
Ficture Seven 2020-08-05 02:52:33 +04:00 committed by GitHub
parent a33dc2f491
commit ee22517d92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 311 additions and 145 deletions

View File

@ -2,6 +2,8 @@ using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Translation;
using System.Diagnostics;
using static ARMeilleure.IntermediateRepresentation.OperandHelper;
namespace ARMeilleure.CodeGen.Optimizations
{
static class Optimizer
@ -42,13 +44,25 @@ namespace ARMeilleure.CodeGen.Optimizations
Simplification.RunPass(operation);
if (DestIsLocalVar(operation) && IsPropagableCopy(operation))
if (DestIsLocalVar(operation))
{
PropagateCopy(operation);
if (IsPropagableCompare(operation))
{
modified |= PropagateCompare(operation);
RemoveNode(block, node);
if (modified && IsUnused(operation))
{
RemoveNode(block, node);
}
}
else if (IsPropagableCopy(operation))
{
PropagateCopy(operation);
modified = true;
RemoveNode(block, node);
modified = true;
}
}
node = nextNode;
@ -88,6 +102,91 @@ namespace ARMeilleure.CodeGen.Optimizations
while (modified);
}
private static bool PropagateCompare(Operation compOp)
{
// Try to propagate Compare operations into their BranchIf uses, when these BranchIf uses are in the form
// of:
//
// - BranchIf %x, 0x0, Equal ;; i.e BranchIfFalse %x
// - BranchIf %x, 0x0, NotEqual ;; i.e BranchIfTrue %x
//
// The commutative property of Equal and NotEqual is taken into consideration as well.
//
// For example:
//
// %x = Compare %a, %b, comp
// BranchIf %x, 0x0, NotEqual
//
// =>
//
// BranchIf %a, %b, comp
static bool IsZeroBranch(Operation operation, out Comparison compType)
{
compType = Comparison.Equal;
if (operation.Instruction != Instruction.BranchIf)
{
return false;
}
Operand src1 = operation.GetSource(0);
Operand src2 = operation.GetSource(1);
Operand comp = operation.GetSource(2);
compType = (Comparison)comp.AsInt32();
return (src1.Kind == OperandKind.Constant && src1.Value == 0) ||
(src2.Kind == OperandKind.Constant && src2.Value == 0);
}
bool modified = false;
Operand dest = compOp.Destination;
Operand src1 = compOp.GetSource(0);
Operand src2 = compOp.GetSource(1);
Operand comp = compOp.GetSource(2);
Comparison compType = (Comparison)comp.AsInt32();
Node[] uses = dest.Uses.ToArray();
foreach (Node use in uses)
{
if (!(use is Operation operation))
{
continue;
}
// If operation is a BranchIf and has a constant value 0 in its RHS or LHS source operands.
if (IsZeroBranch(operation, out Comparison otherCompType))
{
Comparison propCompType;
if (otherCompType == Comparison.NotEqual)
{
propCompType = compType;
}
else if (otherCompType == Comparison.Equal)
{
propCompType = compType.Invert();
}
else
{
continue;
}
operation.SetSource(0, src1);
operation.SetSource(1, src2);
operation.SetSource(2, Const((int)propCompType));
modified = true;
}
}
return modified;
}
private static void PropagateCopy(Operation copyOp)
{
// Propagate copy source operand to all uses of the destination operand.
@ -143,6 +242,11 @@ namespace ARMeilleure.CodeGen.Optimizations
|| operation.Instruction == Instruction.CompareAndSwap8);
}
private static bool IsPropagableCompare(Operation operation)
{
return operation.Instruction == Instruction.Compare;
}
private static bool IsPropagableCopy(Operation operation)
{
if (operation.Instruction != Instruction.Copy)

View File

@ -33,24 +33,14 @@ namespace ARMeilleure.CodeGen.X86
Add(Instruction.BitwiseNot, GenerateBitwiseNot);
Add(Instruction.BitwiseOr, GenerateBitwiseOr);
Add(Instruction.Branch, GenerateBranch);
Add(Instruction.BranchIfFalse, GenerateBranchIfFalse);
Add(Instruction.BranchIfTrue, GenerateBranchIfTrue);
Add(Instruction.BranchIf, GenerateBranchIf);
Add(Instruction.ByteSwap, GenerateByteSwap);
Add(Instruction.Call, GenerateCall);
Add(Instruction.Clobber, GenerateClobber);
Add(Instruction.Compare, GenerateCompare);
Add(Instruction.CompareAndSwap, GenerateCompareAndSwap);
Add(Instruction.CompareAndSwap16, GenerateCompareAndSwap16);
Add(Instruction.CompareAndSwap8, GenerateCompareAndSwap8);
Add(Instruction.CompareEqual, GenerateCompareEqual);
Add(Instruction.CompareGreater, GenerateCompareGreater);
Add(Instruction.CompareGreaterOrEqual, GenerateCompareGreaterOrEqual);
Add(Instruction.CompareGreaterOrEqualUI, GenerateCompareGreaterOrEqualUI);
Add(Instruction.CompareGreaterUI, GenerateCompareGreaterUI);
Add(Instruction.CompareLess, GenerateCompareLess);
Add(Instruction.CompareLessOrEqual, GenerateCompareLessOrEqual);
Add(Instruction.CompareLessOrEqualUI, GenerateCompareLessOrEqualUI);
Add(Instruction.CompareLessUI, GenerateCompareLessUI);
Add(Instruction.CompareNotEqual, GenerateCompareNotEqual);
Add(Instruction.ConditionalSelect, GenerateConditionalSelect);
Add(Instruction.ConvertI64ToI32, GenerateConvertI64ToI32);
Add(Instruction.ConvertToFP, GenerateConvertToFP);
@ -474,6 +464,8 @@ namespace ARMeilleure.CodeGen.X86
Debug.Assert(dest.Type.IsInteger());
// Note: GenerateCompareCommon makes the assumption that BitwiseAnd will emit only a single `and`
// instruction.
context.Assembler.And(dest, src2, dest.Type);
}
@ -525,22 +517,17 @@ namespace ARMeilleure.CodeGen.X86
context.JumpTo(context.CurrBlock.Branch);
}
private static void GenerateBranchIfFalse(CodeGenContext context, Operation operation)
private static void GenerateBranchIf(CodeGenContext context, Operation operation)
{
Operand source = operation.GetSource(0);
Operand comp = operation.GetSource(2);
context.Assembler.Test(source, source, source.Type);
Debug.Assert(comp.Kind == OperandKind.Constant);
context.JumpTo(X86Condition.Equal, context.CurrBlock.Branch);
}
var cond = ((Comparison)comp.AsInt32()).ToX86Condition();
private static void GenerateBranchIfTrue(CodeGenContext context, Operation operation)
{
Operand source = operation.GetSource(0);
GenerateCompareCommon(context, operation);
context.Assembler.Test(source, source, source.Type);
context.JumpTo(X86Condition.NotEqual, context.CurrBlock.Branch);
context.JumpTo(cond, context.CurrBlock.Branch);
}
private static void GenerateByteSwap(CodeGenContext context, Operation operation)
@ -566,6 +553,60 @@ namespace ARMeilleure.CodeGen.X86
// register allocator, we don't need to produce any code.
}
private static void GenerateCompare(CodeGenContext context, Operation operation)
{
Operand dest = operation.Destination;
Operand comp = operation.GetSource(2);
Debug.Assert(dest.Type == OperandType.I32);
Debug.Assert(comp.Kind == OperandKind.Constant);
var cond = ((Comparison)comp.AsInt32()).ToX86Condition();
GenerateCompareCommon(context, operation);
context.Assembler.Setcc(dest, cond);
context.Assembler.Movzx8(dest, dest, OperandType.I32);
}
private static void GenerateCompareCommon(CodeGenContext context, Operation operation)
{
Operand src1 = operation.GetSource(0);
Operand src2 = operation.GetSource(1);
EnsureSameType(src1, src2);
Debug.Assert(src1.Type.IsInteger());
if (src2.Kind == OperandKind.Constant && src2.Value == 0)
{
if (MatchOperation(operation.ListPrevious, Instruction.BitwiseAnd, src1.Type, src1.GetRegister()))
{
// Since the `test` and `and` instruction set the status flags in the same way, we can omit the
// `test r,r` instruction when it is immediately preceded by an `and r,*` instruction.
//
// For example:
//
// and eax, 0x3
// test eax, eax
// jz .L0
//
// =>
//
// and eax, 0x3
// jz .L0
}
else
{
context.Assembler.Test(src1, src1, src1.Type);
}
}
else
{
context.Assembler.Cmp(src1, src2, src1.Type);
}
}
private static void GenerateCompareAndSwap(CodeGenContext context, Operation operation)
{
Operand src1 = operation.GetSource(0);
@ -615,71 +656,6 @@ namespace ARMeilleure.CodeGen.X86
context.Assembler.Cmpxchg8(memOp, src3);
}
private static void GenerateCompareEqual(CodeGenContext context, Operation operation)
{
GenerateCompare(context, operation, X86Condition.Equal);
}
private static void GenerateCompareGreater(CodeGenContext context, Operation operation)
{
GenerateCompare(context, operation, X86Condition.Greater);
}
private static void GenerateCompareGreaterOrEqual(CodeGenContext context, Operation operation)
{
GenerateCompare(context, operation, X86Condition.GreaterOrEqual);
}
private static void GenerateCompareGreaterOrEqualUI(CodeGenContext context, Operation operation)
{
GenerateCompare(context, operation, X86Condition.AboveOrEqual);
}
private static void GenerateCompareGreaterUI(CodeGenContext context, Operation operation)
{
GenerateCompare(context, operation, X86Condition.Above);
}
private static void GenerateCompareLess(CodeGenContext context, Operation operation)
{
GenerateCompare(context, operation, X86Condition.Less);
}
private static void GenerateCompareLessOrEqual(CodeGenContext context, Operation operation)
{
GenerateCompare(context, operation, X86Condition.LessOrEqual);
}
private static void GenerateCompareLessOrEqualUI(CodeGenContext context, Operation operation)
{
GenerateCompare(context, operation, X86Condition.BelowOrEqual);
}
private static void GenerateCompareLessUI(CodeGenContext context, Operation operation)
{
GenerateCompare(context, operation, X86Condition.Below);
}
private static void GenerateCompareNotEqual(CodeGenContext context, Operation operation)
{
GenerateCompare(context, operation, X86Condition.NotEqual);
}
private static void GenerateCompare(CodeGenContext context, Operation operation, X86Condition condition)
{
Operand dest = operation.Destination;
Operand src1 = operation.GetSource(0);
Operand src2 = operation.GetSource(1);
EnsureSameType(src1, src2);
Debug.Assert(dest.Type == OperandType.I32);
context.Assembler.Cmp(src1, src2, src1.Type);
context.Assembler.Setcc(dest, condition);
context.Assembler.Movzx8(dest, dest, OperandType.I32);
}
private static void GenerateConditionalSelect(CodeGenContext context, Operation operation)
{
Operand dest = operation.Destination;
@ -1561,6 +1537,25 @@ namespace ARMeilleure.CodeGen.X86
context.Assembler.Pshufd(dest, dest, 0xfc);
}
private static bool MatchOperation(Node node, Instruction inst, OperandType destType, Register destReg)
{
if (!(node is Operation operation) || node.DestinationsCount == 0)
{
return false;
}
if (operation.Instruction != inst)
{
return false;
}
Operand dest = operation.Destination;
return dest.Kind == OperandKind.Register &&
dest.Type == destType &&
dest.GetRegister() == destReg;
}
[Conditional("DEBUG")]
private static void ValidateUnOp(Operand dest, Operand source)
{

View File

@ -154,7 +154,7 @@ namespace ARMeilleure.CodeGen.X86
// -- Doing so may allow us to encode the constant as operand 2 and avoid a copy.
// - If the constant is on operand 2, we check if the instruction supports it,
// if not, we also add a copy. 64-bits constants are usually not supported.
if (IsCommutative(inst))
if (IsCommutative(operation))
{
src2 = operation.GetSource(1);
@ -1348,16 +1348,8 @@ namespace ARMeilleure.CodeGen.X86
case Instruction.BitwiseAnd:
case Instruction.BitwiseExclusiveOr:
case Instruction.BitwiseOr:
case Instruction.CompareEqual:
case Instruction.CompareGreater:
case Instruction.CompareGreaterOrEqual:
case Instruction.CompareGreaterOrEqualUI:
case Instruction.CompareGreaterUI:
case Instruction.CompareLess:
case Instruction.CompareLessOrEqual:
case Instruction.CompareLessOrEqualUI:
case Instruction.CompareLessUI:
case Instruction.CompareNotEqual:
case Instruction.BranchIf:
case Instruction.Compare:
case Instruction.Multiply:
case Instruction.RotateRight:
case Instruction.ShiftLeft:
@ -1376,18 +1368,28 @@ namespace ARMeilleure.CodeGen.X86
return false;
}
private static bool IsCommutative(Instruction inst)
private static bool IsCommutative(Operation operation)
{
switch (inst)
switch (operation.Instruction)
{
case Instruction.Add:
case Instruction.BitwiseAnd:
case Instruction.BitwiseExclusiveOr:
case Instruction.BitwiseOr:
case Instruction.CompareEqual:
case Instruction.CompareNotEqual:
case Instruction.Multiply:
return true;
case Instruction.BranchIf:
case Instruction.Compare:
{
Operand comp = operation.GetSource(2);
Debug.Assert(comp.Kind == OperandKind.Constant);
var compType = (Comparison)comp.AsInt32();
return compType == Comparison.Equal || compType == Comparison.NotEqual;
}
}
return false;

View File

@ -1,3 +1,6 @@
using ARMeilleure.IntermediateRepresentation;
using System;
namespace ARMeilleure.CodeGen.X86
{
enum X86Condition
@ -19,4 +22,26 @@ namespace ARMeilleure.CodeGen.X86
LessOrEqual = 0xe,
Greater = 0xf
}
static class ComparisonX86Extensions
{
public static X86Condition ToX86Condition(this Comparison comp)
{
return comp switch
{
Comparison.Equal => X86Condition.Equal,
Comparison.NotEqual => X86Condition.NotEqual,
Comparison.Greater => X86Condition.Greater,
Comparison.LessOrEqual => X86Condition.LessOrEqual,
Comparison.GreaterUI => X86Condition.Above,
Comparison.LessOrEqualUI => X86Condition.BelowOrEqual,
Comparison.GreaterOrEqual => X86Condition.GreaterOrEqual,
Comparison.Less => X86Condition.Less,
Comparison.GreaterOrEqualUI => X86Condition.AboveOrEqual,
Comparison.LessUI => X86Condition.Below,
_ => throw new ArgumentException(null, nameof(comp))
};
}
}
}

View File

@ -198,6 +198,8 @@ namespace ARMeilleure.Diagnostics
break;
case Operation operation:
bool comparison = false;
_builder.Append(operation.Instruction);
if (operation.Instruction == Instruction.Extended)
@ -206,17 +208,32 @@ namespace ARMeilleure.Diagnostics
_builder.Append('.').Append(intrinOp.Intrinsic);
}
else if (operation.Instruction == Instruction.BranchIf ||
operation.Instruction == Instruction.Compare)
{
comparison = true;
}
_builder.Append(' ');
for (int index = 0; index < operation.SourcesCount; index++)
{
DumpOperand(operation.GetSource(index));
Operand source = operation.GetSource(index);
if (index < operation.SourcesCount - 1)
{
DumpOperand(source);
_builder.Append(", ");
}
else if (comparison)
{
_builder.Append((Comparison)source.AsInt32());
}
else
{
DumpOperand(source);
}
}
break;
}

View File

@ -163,17 +163,18 @@ namespace ARMeilleure.Instructions
context.LoadFromContext();
// Note: The return value of a translated function is always an Int64 with the
// address execution has returned to. We expect this address to be immediately after the
// current instruction, if it isn't we keep returning until we reach the dispatcher.
// Note: The return value of a translated function is always an Int64 with the address execution has
// returned to. We expect this address to be immediately after the current instruction, if it isn't we
// keep returning until we reach the dispatcher.
Operand nextAddr = Const((long)op.Address + op.OpCodeSizeInBytes);
// Try to continue within this block.
// If the return address isn't to our next instruction, we need to return so the JIT can figure out what to do.
// If the return address isn't to our next instruction, we need to return so the JIT can figure out
// what to do.
Operand lblContinue = context.GetLabel(nextAddr.Value);
// We need to clear out the call flag for the return address before comparing it.
context.BranchIfTrue(lblContinue, context.ICompareEqual(context.BitwiseAnd(returnAddress, Const(~CallFlag)), nextAddr));
context.BranchIf(lblContinue, context.BitwiseAnd(returnAddress, Const(~CallFlag)), nextAddr, Comparison.Equal);
context.Return(returnAddress);
}

View File

@ -92,7 +92,7 @@ namespace ARMeilleure.Instructions
Operand exAddr = context.Load(address.Type, exAddrPtr);
// STEP 1: Check if we have exclusive access to this memory region. If not, fail and skip store.
Operand maskedAddress = context.BitwiseAnd(address, Const(GetExclusiveAddressMask()));
Operand maskedAddress = context.BitwiseAnd(address, Const(address.Type, GetExclusiveAddressMask()));
Operand exFailed = context.ICompareNotEqual(exAddr, maskedAddress);

View File

@ -403,7 +403,7 @@ namespace ARMeilleure.Instructions
if (lblSlowPath != null)
{
context.BranchIfTrue(lblSlowPath, context.ICompareLessOrEqual(pte, Const(0L)));
context.BranchIf(lblSlowPath, pte, Const(0L), Comparison.LessOrEqual);
}
else
{
@ -414,7 +414,7 @@ namespace ARMeilleure.Instructions
Operand lblNotWatched = Label();
// Is the page currently being monitored for modifications? If so we need to call MarkRegionAsModified.
context.BranchIfTrue(lblNotWatched, context.ICompareGreaterOrEqual(pte, Const(0L)));
context.BranchIf(lblNotWatched, pte, Const(0L), Comparison.GreaterOrEqual);
// Mark the region as modified. Size here doesn't matter as address is assumed to be size aligned here.
context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.MarkRegionAsModified)), address, Const(1UL));

View File

@ -0,0 +1,24 @@
namespace ARMeilleure.IntermediateRepresentation
{
enum Comparison
{
Equal = 0,
NotEqual = 1,
Greater = 2,
LessOrEqual = 3,
GreaterUI = 4,
LessOrEqualUI = 5,
GreaterOrEqual = 6,
Less = 7,
GreaterOrEqualUI = 8,
LessUI = 9
}
static class ComparisonExtensions
{
public static Comparison Invert(this Comparison comp)
{
return (Comparison)((int)comp ^ 1);
}
}
}

View File

@ -8,23 +8,13 @@ namespace ARMeilleure.IntermediateRepresentation
BitwiseNot,
BitwiseOr,
Branch,
BranchIfFalse,
BranchIfTrue,
BranchIf,
ByteSwap,
Call,
Compare,
CompareAndSwap,
CompareAndSwap16,
CompareAndSwap8,
CompareEqual,
CompareGreater,
CompareGreaterOrEqual,
CompareGreaterOrEqualUI,
CompareGreaterUI,
CompareLess,
CompareLessOrEqual,
CompareLessOrEqualUI,
CompareLessUI,
CompareNotEqual,
ConditionalSelect,
ConvertI64ToI32,
ConvertToFP,

View File

@ -62,18 +62,21 @@ namespace ARMeilleure.Translation
BranchToLabel(label);
}
public void BranchIfFalse(Operand label, Operand op1)
public void BranchIf(Operand label, Operand op1, Operand op2, Comparison comp)
{
Add(Instruction.BranchIfFalse, null, op1);
Add(Instruction.BranchIf, null, op1, op2, Const((int)comp));
BranchToLabel(label);
}
public void BranchIfFalse(Operand label, Operand op1)
{
BranchIf(label, op1, Const(op1.Type, 0), Comparison.Equal);
}
public void BranchIfTrue(Operand label, Operand op1)
{
Add(Instruction.BranchIfTrue, null, op1);
BranchToLabel(label);
BranchIf(label, op1, Const(op1.Type, 0), Comparison.NotEqual);
}
public Operand ByteSwap(Operand op1)
@ -243,54 +246,59 @@ namespace ARMeilleure.Translation
return Add(Instruction.DivideUI, Local(op1.Type), op1, op2);
}
public Operand ICompare(Operand op1, Operand op2, Comparison comp)
{
return Add(Instruction.Compare, Local(OperandType.I32), op1, op2, Const((int)comp));
}
public Operand ICompareEqual(Operand op1, Operand op2)
{
return Add(Instruction.CompareEqual, Local(OperandType.I32), op1, op2);
return ICompare(op1, op2, Comparison.Equal);
}
public Operand ICompareGreater(Operand op1, Operand op2)
{
return Add(Instruction.CompareGreater, Local(OperandType.I32), op1, op2);
return ICompare(op1, op2, Comparison.Greater);
}
public Operand ICompareGreaterOrEqual(Operand op1, Operand op2)
{
return Add(Instruction.CompareGreaterOrEqual, Local(OperandType.I32), op1, op2);
return ICompare(op1, op2, Comparison.GreaterOrEqual);
}
public Operand ICompareGreaterOrEqualUI(Operand op1, Operand op2)
{
return Add(Instruction.CompareGreaterOrEqualUI, Local(OperandType.I32), op1, op2);
return ICompare(op1, op2, Comparison.GreaterOrEqualUI);
}
public Operand ICompareGreaterUI(Operand op1, Operand op2)
{
return Add(Instruction.CompareGreaterUI, Local(OperandType.I32), op1, op2);
return ICompare(op1, op2, Comparison.GreaterUI);
}
public Operand ICompareLess(Operand op1, Operand op2)
{
return Add(Instruction.CompareLess, Local(OperandType.I32), op1, op2);
return ICompare(op1, op2, Comparison.Less);
}
public Operand ICompareLessOrEqual(Operand op1, Operand op2)
{
return Add(Instruction.CompareLessOrEqual, Local(OperandType.I32), op1, op2);
return ICompare(op1, op2, Comparison.LessOrEqual);
}
public Operand ICompareLessOrEqualUI(Operand op1, Operand op2)
{
return Add(Instruction.CompareLessOrEqualUI, Local(OperandType.I32), op1, op2);
return ICompare(op1, op2, Comparison.LessOrEqualUI);
}
public Operand ICompareLessUI(Operand op1, Operand op2)
{
return Add(Instruction.CompareLessUI, Local(OperandType.I32), op1, op2);
return ICompare(op1, op2, Comparison.LessUI);
}
public Operand ICompareNotEqual(Operand op1, Operand op2)
{
return Add(Instruction.CompareNotEqual, Local(OperandType.I32), op1, op2);
return ICompare(op1, op2, Comparison.NotEqual);
}
public Operand Load(OperandType type, Operand address)

View File

@ -20,7 +20,7 @@ namespace ARMeilleure.Translation.PTC
{
private const string HeaderMagic = "PTChd";
private const int InternalVersion = 18; //! To be incremented manually for each change to the ARMeilleure project.
private const int InternalVersion = 19; //! To be incremented manually for each change to the ARMeilleure project.
private const string BaseDir = "Ryujinx";