Skip to content

Commit

Permalink
[compiler][arm64] Pattern matching ccmp
Browse files Browse the repository at this point in the history
Combine chains of Word32And, Word32Or and comparison nodes into the
new ConditionalSet and ConditionalBranch FlagsContinuations. We begin
the search from Word32And and Word32Or nodes.

The AArch64 ccmp instruction conditionally executes a comparison:
- If the instruction is predicated true, it performs a comparison of
  two inputs operands.
- If the instruction is predicated false, it passes along a
  predefined value for nzcv.

So, for a sequence (Word32And (CompareA) (CompareB)), CompareA would
become a normal cmp, and CompareB would become a ccmp. The logical
combining of the boolean results is achieved by predicating the ccmp
and providing a default 'false' flag result:
  cmp
  ccmp not(CondB), CondA
  cset CondB

The ccmp is predicated on CondA - so the ccmp will only execute iff
cmp is true, and cset will produce 1 iff the ccmp produces a true
value based on CondB. By having the ccmp provide not(CondB) as the
default value, the cset will produce a zero if ccmp doesn't execute.

These default nzcv values are chosen arbitrarily, each code just
needs to set the right bit for the desired 'condition'. As a concrete
example:
  (Word32And (Int32LessThan a, b), (Word32Equal c, d))

The default 'false' nzcv needs to represent signed greater than or
equal, ge, which is represented by N==V and we choose to use 'NoFlag'
but NzcV would be equally valid:
  cmp a, b
  ccmp c, d, nzcv, eq
  cset lt

Combining comparisons with OR is a little more complicated, as
sequentially executing a ccmp converts the logic to an AND. So, for a
sequence (Word32Or (CompareA) (CompareB)) we create an equivalent
chain using AND: (not (Word32And (not CompareA) (not CompareB))) and
generate the chain as we did before:
  cmp
  ccmp CondB, not(CondA)
  cset CondB

Change-Id: I73255e5f349ce6bcc351b18bd38ac409a4f368be
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5317579
Reviewed-by: Darius Mercadier <dmercadier@chromium.org>
Commit-Queue: Rodolph Perfetta <rodolph.perfetta@arm.com>
Cr-Commit-Position: refs/heads/main@{#93647}
  • Loading branch information
sparker-arm authored and V8 LUCI CQ committed Apr 30, 2024
1 parent 07ee5d4 commit 4421c36
Show file tree
Hide file tree
Showing 23 changed files with 1,783 additions and 32 deletions.
4 changes: 0 additions & 4 deletions src/codegen/arm64/assembler-arm64.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4444,10 +4444,6 @@ bool Assembler::IsImmLogical(uint64_t value, unsigned width, unsigned* n,
return true;
}

bool Assembler::IsImmConditionalCompare(int64_t immediate) {
return is_uint5(immediate);
}

bool Assembler::IsImmFP32(uint32_t bits) {
// Valid values will have the form:
// aBbb.bbbc.defg.h000.0000.0000.0000.0000
Expand Down
5 changes: 4 additions & 1 deletion src/codegen/arm64/assembler-arm64.h
Original file line number Diff line number Diff line change
Expand Up @@ -2809,6 +2809,10 @@ class V8_EXPORT_PRIVATE Assembler : public AssemblerBase {
(is_uint12(immediate >> 12) && ((immediate & 0xFFF) == 0));
}

static constexpr bool IsImmConditionalCompare(int64_t immediate) {
return is_uint5(immediate);
}

static bool IsImmLogical(uint64_t value, unsigned width, unsigned* n,
unsigned* imm_s, unsigned* imm_r);

Expand Down Expand Up @@ -3116,7 +3120,6 @@ class V8_EXPORT_PRIVATE Assembler : public AssemblerBase {
void ConditionalCompare(const Register& rn, const Operand& operand,
StatusFlags nzcv, Condition cond,
ConditionalCompareOp op);
static bool IsImmConditionalCompare(int64_t immediate);

void AddSubWithCarry(const Register& rd, const Register& rn,
const Operand& operand, FlagsUpdate S,
Expand Down
11 changes: 11 additions & 0 deletions src/compiler/backend/arm/code-generator-arm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ class ArmOperandConverter final : public InstructionOperandConverter {
SBit OutputSBit() const {
switch (instr_->flags_mode()) {
case kFlags_branch:
case kFlags_conditional_branch:
case kFlags_deoptimize:
case kFlags_set:
case kFlags_conditional_set:
case kFlags_trap:
case kFlags_select:
return SetCC;
Expand Down Expand Up @@ -3679,6 +3681,15 @@ void CodeGenerator::AssembleArchBoolean(Instruction* instr,
__ mov(reg, Operand(1), LeaveCC, cc);
}

void CodeGenerator::AssembleArchConditionalBoolean(Instruction* instr) {
UNREACHABLE();
}

void CodeGenerator::AssembleArchConditionalBranch(Instruction* instr,
BranchInfo* branch) {
UNREACHABLE();
}

void CodeGenerator::AssembleArchBinarySearchSwitch(Instruction* instr) {
ArmOperandConverter i(this, instr);
Register input = i.InputRegister(0);
Expand Down
133 changes: 133 additions & 0 deletions src/compiler/backend/arm64/code-generator-arm64.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3247,6 +3247,139 @@ void CodeGenerator::AssembleArchBoolean(Instruction* instr,
__ Cset(reg, cc);
}

// Given condition, return a value for nzcv which represents it. This is used
// for the default condition for ccmp.
inline StatusFlags ConditionToDefaultFlags(Condition condition) {
switch (condition) {
default:
UNREACHABLE();
case eq:
return ZFlag; // Z == 1
case ne:
return NoFlag; // Z == 0
case hs:
return CFlag; // C == 1
case lo:
return NoFlag; // C == 0
case mi:
return NFlag; // N == 1
case pl:
return NoFlag; // N == 0
case vs:
return VFlag; // V == 1
case vc:
return NoFlag; // V == 0
case hi:
return CFlag; // C == 1 && Z == 0
case ls:
return NoFlag; // C == 0 || Z == 1
case ge:
return NoFlag; // N == V
case lt:
return NFlag; // N != V
case gt:
return NoFlag; // Z == 0 && N == V
case le:
return ZFlag; // Z == 1 || N != V
}
}

void AssembleConditionalCompareChain(Instruction* instr, int64_t num_ccmps,
size_t ccmp_base_index,
CodeGenerator* gen) {
Arm64OperandConverter i(gen, instr);
// The first two, or three operands are the compare that begins the chain.
// These operands are used when the first compare, the one with the
// continuation attached, is generated.
// Then, each five provide:
// - cmp opcode
// - compare lhs
// - compare rhs
// - default flags
// - user condition
for (unsigned n = 0; n < num_ccmps; ++n) {
size_t opcode_index = ccmp_base_index + kCcmpOffsetOfOpcode;
size_t compare_lhs_index = ccmp_base_index + kCcmpOffsetOfLhs;
size_t compare_rhs_index = ccmp_base_index + kCcmpOffsetOfRhs;
size_t default_condition_index =
ccmp_base_index + kCcmpOffsetOfDefaultFlags;
size_t compare_condition_index =
ccmp_base_index + kCcmpOffsetOfCompareCondition;
ccmp_base_index += kNumCcmpOperands;
DCHECK_LT(ccmp_base_index, instr->InputCount() - 1);

InstructionCode code = static_cast<InstructionCode>(
i.ToConstant(instr->InputAt(opcode_index)).ToInt64());

FlagsCondition default_condition = static_cast<FlagsCondition>(
i.ToConstant(instr->InputAt(default_condition_index)).ToInt64());

StatusFlags default_flags =
ConditionToDefaultFlags(FlagsConditionToCondition(default_condition));

FlagsCondition compare_condition = static_cast<FlagsCondition>(
i.ToConstant(instr->InputAt(compare_condition_index)).ToInt64());

if (code == kArm64Cmp) {
gen->masm()->Ccmp(i.InputRegister64(compare_lhs_index),
i.InputOperand64(compare_rhs_index), default_flags,
FlagsConditionToCondition(compare_condition));
} else {
DCHECK_EQ(code, kArm64Cmp32);
gen->masm()->Ccmp(i.InputRegister32(compare_lhs_index),
i.InputOperand32(compare_rhs_index), default_flags,
FlagsConditionToCondition(compare_condition));
}
}
}

// Assemble a conditional compare and boolean materializations after this
// instruction.
void CodeGenerator::AssembleArchConditionalBoolean(Instruction* instr) {
// Materialize a full 64-bit 1 or 0 value. The result register is always the
// last output of the instruction.
DCHECK_NE(0u, instr->OutputCount());
Arm64OperandConverter i(this, instr);
Register reg = i.OutputRegister(instr->OutputCount() - 1);
DCHECK_GE(instr->InputCount(), 6);

// Input ordering:
// > InputCount - 1: number of ccmps.
// > InputCount - 2: branch condition.
size_t num_ccmps_index =
instr->InputCount() - kConditionalSetEndOffsetOfNumCcmps;
size_t set_condition_index =
instr->InputCount() - kConditionalSetEndOffsetOfCondition;
int64_t num_ccmps = i.ToConstant(instr->InputAt(num_ccmps_index)).ToInt64();
size_t ccmp_base_index = set_condition_index - kNumCcmpOperands * num_ccmps;
AssembleConditionalCompareChain(instr, num_ccmps, ccmp_base_index, this);

FlagsCondition set_condition = static_cast<FlagsCondition>(
i.ToConstant(instr->InputAt(set_condition_index)).ToInt64());
__ Cset(reg, FlagsConditionToCondition(set_condition));
}

void CodeGenerator::AssembleArchConditionalBranch(Instruction* instr,
BranchInfo* branch) {
DCHECK_GE(instr->InputCount(), 6);
Arm64OperandConverter i(this, instr);
// Input ordering:
// > InputCount - 1: false block.
// > InputCount - 2: true block.
// > InputCount - 3: number of ccmps.
// > InputCount - 4: branch condition.
size_t num_ccmps_index =
instr->InputCount() - kConditionalBranchEndOffsetOfNumCcmps;
int64_t num_ccmps = i.ToConstant(instr->InputAt(num_ccmps_index)).ToInt64();
size_t ccmp_base_index = instr->InputCount() -
kConditionalBranchEndOffsetOfCondition -
kNumCcmpOperands * num_ccmps;
AssembleConditionalCompareChain(instr, num_ccmps, ccmp_base_index, this);
Condition cc = FlagsConditionToCondition(branch->condition);
__ B(cc, branch->true_label);
if (!branch->fallthru) __ B(branch->false_label);
}

void CodeGenerator::AssembleArchSelect(Instruction* instr,
FlagsCondition condition) {
Arm64OperandConverter i(this, instr);
Expand Down

0 comments on commit 4421c36

Please sign in to comment.