Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ARM64-SVE: Implement IF_SVE_DW_2A, IF_SVE_DW_2B, IF_SVE_EB_1B #97800

Merged
merged 3 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/coreclr/jit/codegenarm64test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5665,6 +5665,26 @@ void CodeGen::genArm64EmitterUnitTestsSve()
theEmitter->emitIns_R_R_R(INS_sve_whilewr, EA_8BYTE, REG_P7, REG_R14, REG_R15,
INS_OPTS_SCALABLE_D); // WHILEWR <Pd>.<T>, <Xn>, <Xm>

// IF_SVE_DW_2A
theEmitter->emitIns_R_R_I(INS_sve_pext, EA_SCALABLE, REG_P0, REG_P8, 0,
INS_OPTS_SCALABLE_B); // PEXT <Pd>.<T>, <PNn>[<imm>]
theEmitter->emitIns_R_R_I(INS_sve_pext, EA_SCALABLE, REG_P1, REG_P9, 1,
INS_OPTS_SCALABLE_H); // PEXT <Pd>.<T>, <PNn>[<imm>]
theEmitter->emitIns_R_R_I(INS_sve_pext, EA_SCALABLE, REG_P2, REG_P10, 2,
INS_OPTS_SCALABLE_S); // PEXT <Pd>.<T>, <PNn>[<imm>]
theEmitter->emitIns_R_R_I(INS_sve_pext, EA_SCALABLE, REG_P3, REG_P11, 3,
INS_OPTS_SCALABLE_D); // PEXT <Pd>.<T>, <PNn>[<imm>]

// IF_SVE_DW_2B
theEmitter->emitIns_R_R_I(INS_sve_pext, EA_SCALABLE, REG_P8, REG_P12, 0, INS_OPTS_SCALABLE_B,
INS_SCALABLE_OPTS_WITH_PREDICATE_PAIR); // PEXT {<Pd1>.<T>, <Pd2>.<T>}, <PNn>[<imm>]
theEmitter->emitIns_R_R_I(INS_sve_pext, EA_SCALABLE, REG_P9, REG_P13, 1, INS_OPTS_SCALABLE_H,
INS_SCALABLE_OPTS_WITH_PREDICATE_PAIR); // PEXT {<Pd1>.<T>, <Pd2>.<T>}, <PNn>[<imm>]
theEmitter->emitIns_R_R_I(INS_sve_pext, EA_SCALABLE, REG_P10, REG_P14, 0, INS_OPTS_SCALABLE_S,
INS_SCALABLE_OPTS_WITH_PREDICATE_PAIR); // PEXT {<Pd1>.<T>, <Pd2>.<T>}, <PNn>[<imm>]
theEmitter->emitIns_R_R_I(INS_sve_pext, EA_SCALABLE, REG_P11, REG_P15, 1, INS_OPTS_SCALABLE_D,
INS_SCALABLE_OPTS_WITH_PREDICATE_PAIR); // PEXT {<Pd1>.<T>, <Pd2>.<T>}, <PNn>[<imm>]

// IF_SVE_DX_3A
theEmitter->emitIns_R_R_R(INS_sve_whilege, EA_8BYTE, REG_P0, REG_R0, REG_R1, INS_OPTS_SCALABLE_B,
INS_SCALABLE_OPTS_WITH_PREDICATE_PAIR); // WHILEGE {<Pd1>.<T>, <Pd2>.<T>}, <Xn>, <Xm>
Expand Down Expand Up @@ -5750,6 +5770,12 @@ void CodeGen::genArm64EmitterUnitTestsSve()
theEmitter->emitIns_R_I(INS_sve_mov, EA_SCALABLE, REG_V7, 127, INS_OPTS_SCALABLE_D,
INS_SCALABLE_OPTS_SHIFT); // MOV <Zd>.<T>, #<imm>{, <shift>}

// IF_SVE_EB_1B
theEmitter->emitIns_R(INS_sve_fmov, EA_SCALABLE, REG_V0, INS_OPTS_SCALABLE_B); // FMOV <Zd>.<T>, #0.0
theEmitter->emitIns_R(INS_sve_fmov, EA_SCALABLE, REG_V1, INS_OPTS_SCALABLE_H); // FMOV <Zd>.<T>, #0.0
theEmitter->emitIns_R(INS_sve_fmov, EA_SCALABLE, REG_V2, INS_OPTS_SCALABLE_S); // FMOV <Zd>.<T>, #0.0
theEmitter->emitIns_R(INS_sve_fmov, EA_SCALABLE, REG_V3, INS_OPTS_SCALABLE_D); // FMOV <Zd>.<T>, #0.0

// IF_SVE_EC_1A
theEmitter->emitIns_R_I(INS_sve_add, EA_SCALABLE, REG_V0, 0,
INS_OPTS_SCALABLE_B); // ADD <Zdn>.<T>, <Zdn>.<T>, #<imm>{, <shift>}
Expand Down
145 changes: 137 additions & 8 deletions src/coreclr/jit/emitarm64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1484,6 +1484,18 @@ void emitter::emitInsSanityCheck(instrDesc* id)
assert(isValidVectorElemsize(optGetSveElemsize(id->idInsOpt()))); // xx
break;

case IF_SVE_DW_2B: // ........xx...... .......iNNN.DDDD -- SVE extract mask predicate from predicate-as-counter
assert(isValidImm1(emitGetInsSC(id))); // i

FALLTHROUGH;
case IF_SVE_DW_2A: // ........xx...... ......iiNNN.DDDD -- SVE extract mask predicate from predicate-as-counter
assert(insOptsScalableStandard(id->idInsOpt()));
assert(isPredicateRegister(id->idReg1())); // DDDD
assert(isHighPredicateRegister(id->idReg2())); // NNN
assert(isValidUimm2(emitGetInsSC(id))); // ii
assert(isValidVectorElemsize(optGetSveElemsize(id->idInsOpt()))); // xx
break;

case IF_SVE_DX_3A: // ........xx.mmmmm ......nnnnn.DDD. -- SVE integer compare scalar count and limit (predicate
// pair)
assert(insOptsScalableStandard(id->idInsOpt()));
Expand Down Expand Up @@ -1533,6 +1545,12 @@ void emitter::emitInsSanityCheck(instrDesc* id)
assert(isValidVectorElemsize(optGetSveElemsize(id->idInsOpt()))); // xx
break;

case IF_SVE_EB_1B: // ........xx...... ...........ddddd -- SVE broadcast integer immediate (unpredicated)
assert(insOptsScalableStandard(id->idInsOpt()));
assert(isVectorRegister(id->idReg1())); // ddddd
assert(isValidVectorElemsize(optGetSveElemsize(id->idInsOpt()))); // xx
break;

case IF_SVE_ED_1A: // ........xx...... ...iiiiiiiiddddd -- SVE integer min/max immediate (unpredicated)
assert(insOptsScalableStandard(id->idInsOpt()));
assert(isVectorRegister(id->idReg1())); // ddddd
Expand Down Expand Up @@ -2299,9 +2317,10 @@ const char* emitter::emitPredicateRegName(regNumber reg, PredicateType ptype)
{
assert((reg >= REG_P0) && (reg <= REG_P15));

int index = (int)reg - (int)REG_P0;
const int index = (int)reg - (int)REG_P0;
const bool usePnRegs = (ptype == PREDICATE_N) || (ptype == PREDICATE_N_SIZED);

return (ptype == PREDICATE_N_SIZED) ? pnRegNames[index] : pRegNames[index];
return usePnRegs ? pnRegNames[index] : pRegNames[index];
}

/*****************************************************************************
Expand Down Expand Up @@ -6248,6 +6267,19 @@ void emitter::emitIns_R(instruction ins, emitAttr attr, regNumber reg, insOpts o
fmt = IF_SVE_DZ_1A;
break;

case INS_sve_fmov:
assert(insOptsScalableStandard(opt));
assert(isVectorRegister(reg)); // ddddd
assert(isValidVectorElemsize(optGetSveElemsize(opt))); // xx
id->idReg1(reg);
id->idInsOpt(opt);
fmt = IF_SVE_EB_1B;

// FMOV is a pseudo-instruction for DUP, which is aliased by MOV;
// MOV is the preferred disassembly
ins = INS_sve_mov;
break;

default:
unreached();
}
Expand Down Expand Up @@ -7829,8 +7861,13 @@ void emitter::emitIns_R_I_I(instruction ins,
* Add an instruction referencing two registers and a constant.
*/

void emitter::emitIns_R_R_I(
instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, ssize_t imm, insOpts opt /* = INS_OPTS_NONE */)
void emitter::emitIns_R_R_I(instruction ins,
emitAttr attr,
regNumber reg1,
regNumber reg2,
ssize_t imm,
insOpts opt /* = INS_OPTS_NONE */,
insScalableOpts sopt /* = INS_SCALABLE_OPTS_NONE */)
{
emitAttr size = EA_SIZE(attr);
emitAttr elemsize = EA_UNKNOWN;
Expand Down Expand Up @@ -8423,6 +8460,25 @@ void emitter::emitIns_R_R_I(
fmt = IF_SVE_GA_2A;
break;

case INS_sve_pext:
assert(insOptsScalableStandard(opt));
assert(isPredicateRegister(reg1)); // DDDD
assert(isHighPredicateRegister(reg2)); // NNN
assert(isValidVectorElemsize(optGetSveElemsize(opt))); // xx

if (sopt == INS_SCALABLE_OPTS_WITH_PREDICATE_PAIR)
{
assert(isValidImm1(imm)); // i
fmt = IF_SVE_DW_2B;
}
else
{
assert(insScalableOptsNone(sopt));
assert(isValidUimm2(imm)); // ii
fmt = IF_SVE_DW_2A;
}
break;

default:
unreached();
break;
Expand Down Expand Up @@ -13795,7 +13851,7 @@ void emitter::emitIns_Call(EmitCallType callType,
assert(isPredicateRegister(reg));
emitter::code_t ureg = (emitter::code_t)reg - (emitter::code_t)REG_P0;
assert((ureg >= 0) && (ureg <= 15));
return ureg << 0;
return ureg;
}

/*****************************************************************************
Expand Down Expand Up @@ -13844,9 +13900,9 @@ void emitter::emitIns_Call(EmitCallType callType,

/*static*/ emitter::code_t emitter::insEncodeReg_P_7_to_5(regNumber reg)
{
assert(isLowPredicateRegister(reg));
emitter::code_t ureg = (emitter::code_t)reg - (emitter::code_t)REG_P0;
assert((ureg >= 0) && (ureg <= 15));
assert(isHighPredicateRegister(reg));
emitter::code_t ureg = (emitter::code_t)reg - (emitter::code_t)REG_P8;
assert((ureg >= 0) && (ureg <= 7));
return ureg << 5;
}

Expand Down Expand Up @@ -15843,6 +15899,17 @@ void emitter::emitIns_Call(EmitCallType callType,
return (code_t)imm << 16;
}

/*****************************************************************************
*
* Returns the encoding for the immediate value as 2-bits at bit locations '9-8'.
*/

/*static*/ emitter::code_t emitter::insEncodeUimm2_9_to_8(ssize_t imm)
{
assert(isValidUimm2(imm));
return (code_t)imm << 8;
}

/*****************************************************************************
*
* Returns the encoding for the immediate value as 7-bits at bit locations '20-14'.
Expand Down Expand Up @@ -18246,6 +18313,16 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp)
dst += emitOutput_Instr(dst, code);
break;

case IF_SVE_DW_2A: // ........xx...... ......iiNNN.DDDD -- SVE extract mask predicate from predicate-as-counter
case IF_SVE_DW_2B: // ........xx...... .......iNNN.DDDD -- SVE extract mask predicate from predicate-as-counter
code = emitInsCodeSve(ins, fmt);
code |= insEncodeReg_P_3_to_0(id->idReg1()); // DDDD
code |= insEncodeReg_P_7_to_5(id->idReg2()); // NNN
code |= insEncodeUimm2_9_to_8(emitGetInsSC(id)); // ii (or i)
code |= insEncodeElemsize(optGetSveElemsize(id->idInsOpt())); // xx
dst += emitOutput_Instr(dst, code);
break;

case IF_SVE_DX_3A: // ........xx.mmmmm ......nnnnn.DDD. -- SVE integer compare scalar count and limit (predicate
// pair)
code = emitInsCodeSve(ins, fmt);
Expand Down Expand Up @@ -18300,6 +18377,14 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp)
break;
}

case IF_SVE_EB_1B: // ........xx...... ...........ddddd -- SVE broadcast integer immediate (unpredicated)
// ins is MOV for this encoding, as it is the preferred disassembly, so pass FMOV to emitInsCodeSve
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is annoying, and slightly different to aliasing in other instructions.

Alternatively, keep it as FMOV in emitIns_R() and then covert to MOV in emitDispInsHelp().

I'm not sure either way is better. Happy to keep as is in here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, keep it as FMOV in emitIns_R() and then covert to MOV in emitDispInsHelp().

I'd like to keep the instruction as FMOV in emitIns_R, but in emitDispInsHelp, in order to print it as a MOV, we would have to add a check for this case at the beginning of the method so we don't print the instruction before modifying it. My current approach is hacky, but it allows us to contain this behavior to the relevant switch case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine to me. Comment describes why so it's all good.

code = emitInsCodeSve(INS_sve_fmov, fmt);
code |= insEncodeReg_V_4_to_0(id->idReg1()); // ddddd
code |= insEncodeElemsize(optGetSveElemsize(id->idInsOpt())); // xx
dst += emitOutput_Instr(dst, code);
break;

case IF_SVE_DU_3A: // ........xx.mmmmm ......nnnnn.DDDD -- SVE pointer conflict compare
code = emitInsCodeSve(ins, fmt);
code |= insEncodeReg_P_3_to_0(id->idReg1()); // DDDD
Expand Down Expand Up @@ -18822,6 +18907,15 @@ void emitter::emitDispImm(ssize_t imm, bool addComma, bool alwaysHex /* =false *
emitDispComma();
}

/*****************************************************************************
*
* Display an immediate value as an index operation
*/
void emitter::emitDispElementIndex(ssize_t imm)
{
printf("[%d]", imm);
}

/*****************************************************************************
*
* Display a float zero constant
Expand Down Expand Up @@ -19251,6 +19345,17 @@ void emitter::emitDispPredicateReg(regNumber reg, PredicateType ptype, insOpts o
emitDispComma();
}

//------------------------------------------------------------------------
// emitDispPredicateRegPair: Display a pair of predicate registers
//
void emitter::emitDispPredicateRegPair(regNumber reg, insOpts opt)
{
printf("{ ");
emitDispPredicateReg(reg, PREDICATE_SIZED, opt, true);
emitDispPredicateReg((regNumber)((unsigned)reg + 1), PREDICATE_SIZED, opt, false);
printf(" }, ");
}

//------------------------------------------------------------------------
// emitDispLowPredicateReg: Display a low predicate register name with with an arrangement suffix
//
Expand Down Expand Up @@ -21335,6 +21440,20 @@ void emitter::emitDispInsHelp(
emitDispReg(id->idReg3(), id->idOpSize(), false); // mmmmm
break;

// <Pd>.<T>, <PNn>[<imm>]
case IF_SVE_DW_2A: // ........xx...... ......iiNNN.DDDD -- SVE extract mask predicate from predicate-as-counter
emitDispPredicateReg(id->idReg1(), PREDICATE_SIZED, id->idInsOpt(), true); // DDDD
emitDispPredicateReg(id->idReg2(), PREDICATE_N, id->idInsOpt(), false); // NNN
emitDispElementIndex(emitGetInsSC(id)); // ii
break;

// {<Pd1>.<T>, <Pd2>.<T>}, <PNn>[<imm>]
case IF_SVE_DW_2B: // ........xx...... .......iNNN.DDDD -- SVE extract mask predicate from predicate-as-counter
emitDispPredicateRegPair(id->idReg1(), id->idInsOpt()); // DDDD
emitDispPredicateReg(id->idReg2(), PREDICATE_N, id->idInsOpt(), false); // NNN
emitDispElementIndex(emitGetInsSC(id)); // i
break;

// {<Pd1>.<T>, <Pd2>.<T>}, <Xn>, <Xm>
case IF_SVE_DX_3A: // ........xx.mmmmm ......nnnnn.DDD. -- SVE integer compare scalar count and limit (predicate
// pair)
Expand Down Expand Up @@ -21382,6 +21501,13 @@ void emitter::emitDispInsHelp(
emitDispImmOptsLSL(emitGetInsSC(id), id->idOptionalShift(), 8); // iiiiiiii, h
break;

// FMOV <Zd>.<T>, #0.0
// (Preferred disassembly: FMOV <Zd>.<T>, #0)
case IF_SVE_EB_1B: // ........xx...... ...........ddddd -- SVE broadcast integer immediate (unpredicated)
emitDispSveReg(id->idReg1(), id->idInsOpt(), true); // ddddd
emitDispImm(0, false);
break;

// SMAX <Zdn>.<T>, <Zdn>.<T>, #<imm>
// SMIN <Zdn>.<T>, <Zdn>.<T>, #<imm>
// UMAX <Zdn>.<T>, <Zdn>.<T>, #<imm>
Expand Down Expand Up @@ -24315,6 +24441,8 @@ emitter::insExecutionCharacteristics emitter::getInsExecutionCharacteristics(ins
result.insLatency = PERFSCORE_LATENCY_2C;
break;

case IF_SVE_DW_2A: // ........xx...... ......iiNNN.DDDD -- SVE extract mask predicate from predicate-as-counter
case IF_SVE_DW_2B: // ........xx...... .......iNNN.DDDD -- SVE extract mask predicate from predicate-as-counter
case IF_SVE_DS_2A: // .........x.mmmmm ......nnnnn..... -- SVE conditionally terminate scalars
result.insThroughput = PERFSCORE_THROUGHPUT_1C;
result.insLatency = PERFSCORE_LATENCY_1C;
Expand Down Expand Up @@ -24360,6 +24488,7 @@ emitter::insExecutionCharacteristics emitter::getInsExecutionCharacteristics(ins
case IF_SVE_EA_1A: // ........xx...... ...iiiiiiiiddddd -- SVE broadcast floating-point immediate (unpredicated)
case IF_SVE_EB_1A: // ........xx...... ..hiiiiiiiiddddd -- SVE broadcast integer immediate (unpredicated)
case IF_SVE_EC_1A: // ........xx...... ..hiiiiiiiiddddd -- SVE integer add/subtract immediate (unpredicated)
case IF_SVE_EB_1B: // ........xx...... ...........ddddd -- SVE broadcast integer immediate (unpredicated)
result.insThroughput = PERFSCORE_THROUGHPUT_2C;
result.insLatency = PERFSCORE_LATENCY_2C;
break;
Expand Down
29 changes: 26 additions & 3 deletions src/coreclr/jit/emitarm64.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ enum PredicateType
PREDICATE_MERGE, // Predicate printed with /m
PREDICATE_ZERO, // Predicate printed with /z
PREDICATE_SIZED, // Predicate printed with element size
PREDICATE_N_SIZED, // Predicate printed printed as counter with element size
PREDICATE_N, // Predicate printed as counter
PREDICATE_N_SIZED, // Predicate printed as counter with element size
};

const char* emitSveRegName(regNumber reg);
Expand All @@ -39,6 +40,7 @@ void emitDispLargeJmp(
void emitDispComma();
void emitDispInst(instruction ins);
void emitDispImm(ssize_t imm, bool addComma, bool alwaysHex = false, bool isAddrOffset = false);
void emitDispElementIndex(ssize_t imm);
void emitDispFloatZero();
void emitDispFloatImm(ssize_t imm8);
void emitDispImmOptsLSL(ssize_t imm, bool hasShift, unsigned shiftAmount);
Expand All @@ -59,6 +61,7 @@ void emitDispVectorRegList(regNumber firstReg, unsigned listSize, insOpts opt, b
void emitDispVectorElemList(regNumber firstReg, unsigned listSize, emitAttr elemsize, unsigned index, bool addComma);
void emitDispSveConsecutiveRegList(regNumber firstReg, unsigned listSize, insOpts opt, bool addComma);
void emitDispPredicateReg(regNumber reg, PredicateType ptype, insOpts opt, bool addComma);
void emitDispPredicateRegPair(regNumber reg, insOpts opt);
void emitDispLowPredicateReg(regNumber reg, PredicateType ptype, insOpts opt, bool addComma);
void emitDispLowPredicateRegPair(regNumber reg, insOpts opt);
void emitDispVectorLengthSpecifier(instrDesc* id);
Expand Down Expand Up @@ -550,6 +553,9 @@ static code_t insEncodeSimm4_MultipleOf32_19_to_16(ssize_t imm);
// Returns the encoding for the immediate value as 5-bits at bit locations '20-16'.
static code_t insEncodeSimm5_20_to_16(ssize_t imm);

// Returns the encoding for the immediate value as 2-bits at bit locations '9-8'.
static code_t insEncodeUimm2_9_to_8(ssize_t imm);

// Returns the encoding for the immediate value as 7-bits at bit locations '20-14'.
static code_t insEncodeUimm7_20_to_14(ssize_t imm);

Expand Down Expand Up @@ -618,6 +624,18 @@ static bool isValidSimm4_MultipleOf32(ssize_t value)
return (-256 <= value) && (value <= 224) && (value % 32 == 0);
};

// Returns true if 'value' is a legal immediate 1 bit encoding (such as for PEXT).
static bool isValidImm1(ssize_t value)
{
return (value == 0) || (value == 1);
};

// Returns true if 'value' is a legal unsigned immediate 1 bit encoding (such as for PEXT).
static bool isValidUimm2(ssize_t value)
{
return (0 <= value) || (value <= 3);
};

// Returns true if 'value' is a legal unsigned immediate 4 bit encoding, starting from 1 (such as for CNTB).
static bool isValidUimm4From1(ssize_t value)
{
Expand Down Expand Up @@ -1184,8 +1202,13 @@ void emitIns_R_I_I(instruction ins,
insOpts opt = INS_OPTS_NONE DEBUGARG(size_t targetHandle = 0)
DEBUGARG(GenTreeFlags gtFlags = GTF_EMPTY));

void emitIns_R_R_I(
instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, ssize_t imm, insOpts opt = INS_OPTS_NONE);
void emitIns_R_R_I(instruction ins,
emitAttr attr,
regNumber reg1,
regNumber reg2,
ssize_t imm,
insOpts opt = INS_OPTS_NONE,
insScalableOpts sopt = INS_SCALABLE_OPTS_NONE);

// Checks for a large immediate that needs a second instruction
void emitIns_R_R_Imm(instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, ssize_t imm);
Expand Down
Loading