diff --git a/eng/Subsets.props b/eng/Subsets.props
index d693e2a58f9ff..9088b48f285de 100644
--- a/eng/Subsets.props
+++ b/eng/Subsets.props
@@ -79,6 +79,8 @@
'$(BuildTargetFramework)' == '' or
'$(BuildAllConfigurations)' == 'true'">libs.native+
$(DefaultLibrariesSubsets)libs.sfx+libs.oob+libs.pretest
+
+ $(DefaultLibrariesSubsets)+libs.tests
tools.illink
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index ceb14e14b0603..33eace4f218ce 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -84,6 +84,7 @@
https://github.com/dotnet/command-line-api
a045dd54a4c44723c215d992288160eb1401bb7f
+
https://github.com/dotnet/command-line-api
a045dd54a4c44723c215d992288160eb1401bb7f
@@ -92,21 +93,33 @@
https://github.com/dotnet/cecil
b8c2293cd1cbd9d0fe6f32d7b5befbd526b5a175
+
+
+
+ https://github.com/dotnet/cecil
+ b8c2293cd1cbd9d0fe6f32d7b5befbd526b5a175
https://github.com/dotnet/emsdk
c7b4dbc857259968a0892cf94cfa9ae4f2ca53cd
+
+
+
+ https://github.com/dotnet/emsdk
+ c7b4dbc857259968a0892cf94cfa9ae4f2ca53cd
+
https://github.com/dotnet/source-build-reference-packages
e659f328bf255d3e17e81296117c3aed1d461f2f
-
+
+
https://github.com/dotnet/source-build-externals
- 9f553c88e8a6787c560ab3e7adec226311de7e2c
+ 949db2fd23b687c0d545e954943feada8b361ed6
@@ -114,6 +127,11 @@
https://github.com/dotnet/arcade
438a8e2488313fb3aa1b24a741a85c2669ee7e0d
+
+
+
+ https://github.com/dotnet/arcade
+ 438a8e2488313fb3aa1b24a741a85c2669ee7e0d
@@ -315,6 +333,11 @@
https://github.com/dotnet/runtime
205ef031e0fe5152dede0bd9f99d0f6f9e7f1e45
+
+
+
+ https://github.com/dotnet/runtime
+ 205ef031e0fe5152dede0bd9f99d0f6f9e7f1e45
@@ -368,7 +391,6 @@
https://github.com/dotnet/roslyn
2fe96bca1092f880e91eea6eb17ea3487d89309a
-
https://github.com/dotnet/roslyn
@@ -382,9 +404,20 @@
https://github.com/dotnet/roslyn-analyzers
e39798fc8357615ab319c81b20acfb036ef7b513
+
+
+ https://github.com/dotnet/roslyn
+ 2fe96bca1092f880e91eea6eb17ea3487d89309a
+
+
https://github.com/dotnet/sdk
de4f12b8ab6692b01776d362f4fa609fd3f1154a
+
+
+
+ https://github.com/dotnet/sdk
+ de4f12b8ab6692b01776d362f4fa609fd3f1154a
diff --git a/eng/Versions.props b/eng/Versions.props
index 5aa0451a132ab..a7085f29db6b5 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -24,6 +24,8 @@
false
false
true
+
+ true
true
diff --git a/src/coreclr/jit/block.cpp b/src/coreclr/jit/block.cpp
index 542edf3518737..0c21487300836 100644
--- a/src/coreclr/jit/block.cpp
+++ b/src/coreclr/jit/block.cpp
@@ -297,7 +297,7 @@ bool BasicBlock::IsFirstColdBlock(Compiler* compiler) const
bool BasicBlock::CanRemoveJumpToNext(Compiler* compiler) const
{
assert(KindIs(BBJ_ALWAYS));
- return JumpsToNext() && !hasAlign() && !compiler->fgInDifferentRegions(this, bbTarget);
+ return JumpsToNext() && (bbNext != compiler->fgFirstColdBlock);
}
//------------------------------------------------------------------------
diff --git a/src/coreclr/jit/codegenarm64test.cpp b/src/coreclr/jit/codegenarm64test.cpp
index 8fe561e2f64e5..53b35cc2011b3 100644
--- a/src/coreclr/jit/codegenarm64test.cpp
+++ b/src/coreclr/jit/codegenarm64test.cpp
@@ -5273,6 +5273,20 @@ void CodeGen::genArm64EmitterUnitTestsSve()
theEmitter->emitIns_R_PATTERN_I(INS_sve_cnth, EA_8BYTE, REG_R5, SVE_PATTERN_ALL,
13); // CNTH {, {, MUL #}}
+ // IF_SVE_CI_3A
+ theEmitter->emitIns_R_R_R(INS_sve_trn1, EA_SCALABLE, REG_P1, REG_P3, REG_P4,
+ INS_OPTS_SCALABLE_B); // TRN1 ., ., .
+ theEmitter->emitIns_R_R_R(INS_sve_trn2, EA_SCALABLE, REG_P5, REG_P2, REG_P7,
+ INS_OPTS_SCALABLE_H); // TRN2 ., ., .
+ theEmitter->emitIns_R_R_R(INS_sve_uzp1, EA_SCALABLE, REG_P0, REG_P0, REG_P0,
+ INS_OPTS_SCALABLE_S); // UZP1 ., ., .
+ theEmitter->emitIns_R_R_R(INS_sve_uzp2, EA_SCALABLE, REG_P0, REG_P0, REG_P0,
+ INS_OPTS_SCALABLE_D); // UZP2 ., ., .
+ theEmitter->emitIns_R_R_R(INS_sve_zip1, EA_SCALABLE, REG_P0, REG_P0, REG_P0,
+ INS_OPTS_SCALABLE_B); // ZIP1 ., ., .
+ theEmitter->emitIns_R_R_R(INS_sve_zip2, EA_SCALABLE, REG_P0, REG_P0, REG_P0,
+ INS_OPTS_SCALABLE_H); // ZIP2 ., ., .
+
// IF_SVE_CL_3A
theEmitter->emitIns_R_R_R(INS_sve_compact, EA_SCALABLE, REG_V16, REG_P7, REG_V13,
INS_OPTS_SCALABLE_S); // COMPACT ., , .
@@ -5665,6 +5679,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 ., ,
+ // IF_SVE_DW_2A
+ theEmitter->emitIns_R_R_I(INS_sve_pext, EA_SCALABLE, REG_P0, REG_P8, 0,
+ INS_OPTS_SCALABLE_B); // PEXT ., []
+ theEmitter->emitIns_R_R_I(INS_sve_pext, EA_SCALABLE, REG_P1, REG_P9, 1,
+ INS_OPTS_SCALABLE_H); // PEXT ., []
+ theEmitter->emitIns_R_R_I(INS_sve_pext, EA_SCALABLE, REG_P2, REG_P10, 2,
+ INS_OPTS_SCALABLE_S); // PEXT ., []
+ theEmitter->emitIns_R_R_I(INS_sve_pext, EA_SCALABLE, REG_P3, REG_P11, 3,
+ INS_OPTS_SCALABLE_D); // PEXT ., []
+
+ // 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 {., .}, []
+ 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 {., .}, []
+ 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 {., .}, []
+ 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 {., .}, []
+
// 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 {., .}, ,
@@ -5750,6 +5784,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 ., #{, }
+ // IF_SVE_EB_1B
+ theEmitter->emitIns_R(INS_sve_fmov, EA_SCALABLE, REG_V0, INS_OPTS_SCALABLE_B); // FMOV ., #0.0
+ theEmitter->emitIns_R(INS_sve_fmov, EA_SCALABLE, REG_V1, INS_OPTS_SCALABLE_H); // FMOV ., #0.0
+ theEmitter->emitIns_R(INS_sve_fmov, EA_SCALABLE, REG_V2, INS_OPTS_SCALABLE_S); // FMOV ., #0.0
+ theEmitter->emitIns_R(INS_sve_fmov, EA_SCALABLE, REG_V3, INS_OPTS_SCALABLE_D); // FMOV ., #0.0
+
// IF_SVE_EC_1A
theEmitter->emitIns_R_I(INS_sve_add, EA_SCALABLE, REG_V0, 0,
INS_OPTS_SCALABLE_B); // ADD ., ., #{, }
diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp
index 309b9d0a316b2..913f3a47002a5 100644
--- a/src/coreclr/jit/codegenlinear.cpp
+++ b/src/coreclr/jit/codegenlinear.cpp
@@ -677,6 +677,7 @@ void CodeGen::genCodeForBBlist()
/* Do we need to generate a jump or return? */
+ bool removedJmp = false;
switch (block->GetKind())
{
case BBJ_RETURN:
@@ -765,6 +766,7 @@ void CodeGen::genCodeForBBlist()
}
#endif // TARGET_AMD64
+ removedJmp = true;
break;
}
#ifdef TARGET_XARCH
@@ -814,7 +816,7 @@ void CodeGen::genCodeForBBlist()
assert(!block->KindIs(BBJ_CALLFINALLY));
#endif // FEATURE_EH_CALLFINALLY_THUNKS
- GetEmitter()->emitLoopAlignment(DEBUG_ARG1(block->KindIs(BBJ_ALWAYS)));
+ GetEmitter()->emitLoopAlignment(DEBUG_ARG1(block->KindIs(BBJ_ALWAYS) && !removedJmp));
}
if (!block->IsLast() && block->Next()->isLoopAlign())
diff --git a/src/coreclr/jit/emitarm64.cpp b/src/coreclr/jit/emitarm64.cpp
index 1d7b9f7e95974..ebe22ad5515b4 100644
--- a/src/coreclr/jit/emitarm64.cpp
+++ b/src/coreclr/jit/emitarm64.cpp
@@ -1089,6 +1089,14 @@ void emitter::emitInsSanityCheck(instrDesc* id)
assert(isValidUimm4From1(emitGetInsSC(id)));
break;
+ case IF_SVE_CI_3A: // ........xx..MMMM .......NNNN.DDDD -- SVE permute predicate elements
+ elemsize = id->idOpSize();
+ assert(insOptsScalableStandard(id->idInsOpt()));
+ assert(isPredicateRegister(id->idReg1())); // DDDD
+ assert(isPredicateRegister(id->idReg2())); // NNNN
+ assert(isPredicateRegister(id->idReg3())); // MMMM
+ break;
+
// Scalable, 4 regs, to predicate register.
case IF_SVE_CX_4A: // ........xx.mmmmm ...gggnnnnn.DDDD -- SVE integer compare vectors
elemsize = id->idOpSize();
@@ -1484,6 +1492,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()));
@@ -1533,6 +1553,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
@@ -2299,9 +2325,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];
}
/*****************************************************************************
@@ -6248,6 +6275,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();
}
@@ -7829,8 +7869,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;
@@ -8423,6 +8468,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;
@@ -9479,6 +9543,19 @@ void emitter::emitIns_R_R_R(instruction ins,
}
break;
+ case INS_sve_uzp1:
+ case INS_sve_trn1:
+ case INS_sve_zip1:
+ case INS_sve_uzp2:
+ case INS_sve_trn2:
+ case INS_sve_zip2:
+ assert(insOptsScalableStandard(opt));
+ assert(isPredicateRegister(reg1)); // DDDD
+ assert(isPredicateRegister(reg2)); // NNNN
+ assert(isPredicateRegister(reg3)); // MMMM
+ fmt = IF_SVE_CI_3A;
+ break;
+
case INS_sve_clz:
case INS_sve_cls:
case INS_sve_cnt:
@@ -13795,7 +13872,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;
}
/*****************************************************************************
@@ -13844,9 +13921,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;
}
@@ -15843,6 +15920,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'.
@@ -17978,6 +18066,15 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp)
dst += emitOutput_Instr(dst, code);
break;
+ case IF_SVE_CI_3A: // ........xx..MMMM .......NNNN.DDDD -- SVE permute predicate elements
+ code = emitInsCodeSve(ins, fmt);
+ code |= insEncodeReg_P_3_to_0(id->idReg1()); // DDDD
+ code |= insEncodeReg_P_8_to_5(id->idReg2()); // NNNN
+ code |= insEncodeReg_P_19_to_16(id->idReg3()); // MMMM
+ code |= insEncodeElemsize(optGetSveElemsize(id->idInsOpt())); // xx
+ dst += emitOutput_Instr(dst, code);
+ break;
+
// Scalable to general register.
case IF_SVE_CO_3A: // ........xx...... ...gggmmmmmddddd -- SVE conditionally extract element to general register
case IF_SVE_CS_3A: // ........xx...... ...gggnnnnnddddd -- SVE extract element to general register
@@ -18246,6 +18343,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);
@@ -18300,6 +18407,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
+ 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
@@ -18822,6 +18937,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
@@ -19251,6 +19375,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
//
@@ -21077,6 +21212,13 @@ void emitter::emitDispInsHelp(
emitDispSveReg(id->idReg3(), id->idInsOpt(), false); // mmmmm
break;
+ // ., ., .
+ case IF_SVE_CI_3A: // ........xx..MMMM .......NNNN.DDDD -- SVE permute predicate elements
+ emitDispPredicateReg(id->idReg1(), insGetPredicateType(fmt, 1), id->idInsOpt(), true); // DDDD
+ emitDispPredicateReg(id->idReg2(), insGetPredicateType(fmt, 2), id->idInsOpt(), true); // NNNN
+ emitDispPredicateReg(id->idReg3(), insGetPredicateType(fmt, 3), id->idInsOpt(), false); // MMMM
+ break;
+
// ., , .
case IF_SVE_CL_3A: // ........xx...... ...gggnnnnnddddd -- SVE compress active elements
emitDispSveReg(id->idReg1(), id->idInsOpt(), true); // ddddd
@@ -21335,6 +21477,20 @@ void emitter::emitDispInsHelp(
emitDispReg(id->idReg3(), id->idOpSize(), false); // mmmmm
break;
+ // ., []
+ 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;
+
+ // {., .}, []
+ 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;
+
// {., .}, ,
case IF_SVE_DX_3A: // ........xx.mmmmm ......nnnnn.DDD. -- SVE integer compare scalar count and limit (predicate
// pair)
@@ -21382,6 +21538,13 @@ void emitter::emitDispInsHelp(
emitDispImmOptsLSL(emitGetInsSC(id), id->idOptionalShift(), 8); // iiiiiiii, h
break;
+ // FMOV ., #0.0
+ // (Preferred disassembly: FMOV ., #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 ., ., #
// SMIN ., ., #
// UMAX ., ., #
@@ -23935,6 +24098,11 @@ emitter::insExecutionCharacteristics emitter::getInsExecutionCharacteristics(ins
result.insLatency = PERFSCORE_LATENCY_2C;
break;
+ case IF_SVE_CI_3A: // ........xx..MMMM .......NNNN.DDDD -- SVE permute predicate elements
+ result.insThroughput = PERFSCORE_THROUGHPUT_2C;
+ result.insLatency = PERFSCORE_LATENCY_2C;
+ break;
+
// Conditional extract operations, SIMD&FP scalar and vector forms
case IF_SVE_CL_3A: // ........xx...... ...gggnnnnnddddd -- SVE compress active elements
case IF_SVE_CM_3A: // ........xx...... ...gggmmmmmddddd -- SVE conditionally broadcast element to vector
@@ -24315,6 +24483,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;
@@ -24360,6 +24530,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;
diff --git a/src/coreclr/jit/emitarm64.h b/src/coreclr/jit/emitarm64.h
index 5755b9515762c..2dcdf13bb9379 100644
--- a/src/coreclr/jit/emitarm64.h
+++ b/src/coreclr/jit/emitarm64.h
@@ -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);
@@ -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);
@@ -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);
@@ -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);
@@ -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 2 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)
{
@@ -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);
diff --git a/src/coreclr/jit/redundantbranchopts.cpp b/src/coreclr/jit/redundantbranchopts.cpp
index bd136cbf4d94f..edcbf34152644 100644
--- a/src/coreclr/jit/redundantbranchopts.cpp
+++ b/src/coreclr/jit/redundantbranchopts.cpp
@@ -47,43 +47,43 @@ PhaseStatus Compiler::optRedundantBranches()
{
bool madeChangesThisBlock = m_compiler->optRedundantRelop(block);
- BasicBlock* const bbNext = block->GetFalseTarget();
- BasicBlock* const bbJump = block->GetTrueTarget();
+ BasicBlock* const bbFalse = block->GetFalseTarget();
+ BasicBlock* const bbTrue = block->GetTrueTarget();
madeChangesThisBlock |= m_compiler->optRedundantBranch(block);
- // If we modified some flow out of block but it's still
+ // If we modified some flow out of block but it's still referenced and
// a BBJ_COND, retry; perhaps one of the later optimizations
// we can do has enabled one of the earlier optimizations.
//
- if (madeChangesThisBlock && block->KindIs(BBJ_COND))
+ if (madeChangesThisBlock && block->KindIs(BBJ_COND) && (block->countOfInEdges() > 0))
{
JITDUMP("Will retry RBO in " FMT_BB " after partial optimization\n", block->bbNum);
madeChangesThisBlock |= m_compiler->optRedundantBranch(block);
}
- // It's possible that the changed flow into bbNext or bbJump may unblock
+ // It's possible that the changed flow into bbFalse or bbTrue may unblock
// further optimizations there.
//
// Note this misses cascading retries, consider reworking the overall
// strategy here to iterate until closure.
//
- if (madeChangesThisBlock && (bbNext->countOfInEdges() == 0))
+ if (madeChangesThisBlock && (bbFalse->countOfInEdges() == 0))
{
- for (BasicBlock* succ : bbNext->Succs())
+ for (BasicBlock* succ : bbFalse->Succs())
{
JITDUMP("Will retry RBO in " FMT_BB "; pred " FMT_BB " now unreachable\n", succ->bbNum,
- bbNext->bbNum);
+ bbFalse->bbNum);
m_compiler->optRedundantBranch(succ);
}
}
- if (madeChangesThisBlock && (bbJump->countOfInEdges() == 0))
+ if (madeChangesThisBlock && (bbTrue->countOfInEdges() == 0))
{
- for (BasicBlock* succ : bbJump->Succs())
+ for (BasicBlock* succ : bbTrue->Succs())
{
JITDUMP("Will retry RBO in " FMT_BB "; pred " FMT_BB " now unreachable\n", succ->bbNum,
- bbNext->bbNum);
+ bbFalse->bbNum);
m_compiler->optRedundantBranch(succ);
}
}
@@ -828,10 +828,10 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
}
else if (trueReaches && !falseReaches && rii.canInferFromTrue)
{
- // Taken jump in dominator reaches, fall through doesn't; relop must be true/false.
+ // True path in dominator reaches, false path doesn't; relop must be true/false.
//
const bool relopIsTrue = rii.reverseSense ^ (domIsSameRelop | domIsInferredRelop);
- JITDUMP("Jump successor " FMT_BB " of " FMT_BB " reaches, relop [%06u] must be %s\n",
+ JITDUMP("True successor " FMT_BB " of " FMT_BB " reaches, relop [%06u] must be %s\n",
domBlock->GetTrueTarget()->bbNum, domBlock->bbNum, dspTreeID(tree),
relopIsTrue ? "true" : "false");
relopValue = relopIsTrue ? 1 : 0;
@@ -839,10 +839,10 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
}
else if (falseReaches && !trueReaches && rii.canInferFromFalse)
{
- // Fall through from dominator reaches, taken jump doesn't; relop must be false/true.
+ // False path from dominator reaches, true path doesn't; relop must be false/true.
//
const bool relopIsFalse = rii.reverseSense ^ (domIsSameRelop | domIsInferredRelop);
- JITDUMP("Fall through successor " FMT_BB " of " FMT_BB " reaches, relop [%06u] must be %s\n",
+ JITDUMP("False successor " FMT_BB " of " FMT_BB " reaches, relop [%06u] must be %s\n",
domBlock->GetFalseTarget()->bbNum, domBlock->bbNum, dspTreeID(tree),
relopIsFalse ? "false" : "true");
relopValue = relopIsFalse ? 0 : 1;
@@ -942,7 +942,6 @@ struct JumpThreadInfo
: m_block(block)
, m_trueTarget(block->GetTrueTarget())
, m_falseTarget(block->GetFalseTarget())
- , m_fallThroughPred(nullptr)
, m_ambiguousVNBlock(nullptr)
, m_truePreds(BlockSetOps::MakeEmpty(comp))
, m_ambiguousPreds(BlockSetOps::MakeEmpty(comp))
@@ -961,8 +960,6 @@ struct JumpThreadInfo
BasicBlock* const m_trueTarget;
// Block successor if predicate is false
BasicBlock* const m_falseTarget;
- // Unique pred that falls through to block, if any
- BasicBlock* m_fallThroughPred;
// Block that brings in the ambiguous VN
BasicBlock* m_ambiguousVNBlock;
// Pred blocks for which the predicate will be true
@@ -1239,35 +1236,9 @@ bool Compiler::optJumpThreadDom(BasicBlock* const block, BasicBlock* const domBl
// * It's also possible that the pred is a switch; we will treat switch
// preds as ambiguous as well.
//
- // * We note if there is an un-ambiguous pred that falls through to block.
- // This is the "fall through pred", and the (true/false) pred set it belongs to
- // is the "fall through set".
- //
- // Now for some case analysis.
- //
- // (1) If we have both an ambiguous pred and a fall through pred, we treat
- // the fall through pred as an ambiguous pred (we can't reroute its flow to
- // avoid block, and we need to keep block intact), and jump thread the other
- // preds per (2) below.
- //
- // (2) If we have an ambiguous pred and no fall through, we reroute the true and
- // false preds to branch to the true and false successors, respectively.
- //
- // (3) If we don't have an ambiguous pred and don't have a fall through pred,
- // we choose one of the pred sets to be treated as if it was the fall through set.
- // For now the choice is arbitrary, so we chose the true preds, and proceed
- // per (4) below.
- //
- // (4) If we don't have an ambiguous pred, and we have a fall through, we leave
- // all preds in the fall through set alone -- they continue branching to block.
- // We modify block to branch to the appropriate successor for the fall through set.
- // Note block will be empty other than phis and the branch, so this is ok.
- // The preds in the other set target the other successor.
- //
- // The goal of the above is to maximize the number of cases where we jump thread,
- // and to maximize the number of jump threads that reuse the original block. This
- // latter should prove useful in subsequent work, where we aim to enable jump
- // threading in cases where block has side effects.
+ // If there are ambiguous preds they will continue to flow into the
+ // unaltered block, while true and false preds will flow to the appropriate
+ // successors directly.
//
BasicBlock* const domTrueSuccessor = domIsSameRelop ? domBlock->GetTrueTarget() : domBlock->GetFalseTarget();
BasicBlock* const domFalseSuccessor = domIsSameRelop ? domBlock->GetFalseTarget() : domBlock->GetTrueTarget();
@@ -1337,15 +1308,6 @@ bool Compiler::optJumpThreadDom(BasicBlock* const block, BasicBlock* const domBl
jti.m_numFalsePreds++;
JITDUMP(FMT_BB " is a false pred\n", predBlock->bbNum);
}
-
- // Note if the true or false pred is the fall through pred.
- //
- if (predBlock->NextIs(block))
- {
- JITDUMP(FMT_BB " is the fall-through pred\n", predBlock->bbNum);
- assert(jti.m_fallThroughPred == nullptr);
- jti.m_fallThroughPred = predBlock;
- }
}
// Do the optimization.
@@ -1597,15 +1559,6 @@ bool Compiler::optJumpThreadPhi(BasicBlock* block, GenTree* tree, ValueNum treeN
continue;
}
-
- // Note if the true or false pred is the fall through pred.
- //
- if (predBlock->NextIs(block))
- {
- JITDUMP(FMT_BB " is the fall-through pred\n", predBlock->bbNum);
- assert(jti.m_fallThroughPred == nullptr);
- jti.m_fallThroughPred = predBlock;
- }
}
// Do the optimization.
@@ -1638,102 +1591,10 @@ bool Compiler::optJumpThreadCore(JumpThreadInfo& jti)
return false;
}
- if ((jti.m_numAmbiguousPreds > 0) && (jti.m_fallThroughPred != nullptr))
- {
- // TODO: Simplify jti.m_fallThroughPred logic, now that implicit fallthrough is disallowed.
- const bool fallThroughIsTruePred = BlockSetOps::IsMember(this, jti.m_truePreds, jti.m_fallThroughPred->bbNum);
- const bool predJumpsToNext = jti.m_fallThroughPred->KindIs(BBJ_ALWAYS) && jti.m_fallThroughPred->JumpsToNext();
-
- if (predJumpsToNext && ((fallThroughIsTruePred && (jti.m_numFalsePreds == 0)) ||
- (!fallThroughIsTruePred && (jti.m_numTruePreds == 0))))
- {
- JITDUMP(FMT_BB " has ambiguous preds and a (%s) fall through pred and no (%s) preds.\n"
- "Fall through pred " FMT_BB " is BBJ_ALWAYS\n",
- jti.m_block->bbNum, fallThroughIsTruePred ? "true" : "false",
- fallThroughIsTruePred ? "false" : "true", jti.m_fallThroughPred->bbNum);
-
- assert(jti.m_fallThroughPred->TargetIs(jti.m_block));
- }
- else
- {
- // Treat the fall through pred as an ambiguous pred.
- JITDUMP(FMT_BB " has both ambiguous preds and a fall through pred\n", jti.m_block->bbNum);
- JITDUMP("Treating fall through pred " FMT_BB " as an ambiguous pred\n", jti.m_fallThroughPred->bbNum);
-
- if (fallThroughIsTruePred)
- {
- BlockSetOps::RemoveElemD(this, jti.m_truePreds, jti.m_fallThroughPred->bbNum);
- assert(jti.m_numTruePreds > 0);
- jti.m_numTruePreds--;
- }
- else
- {
- assert(jti.m_numFalsePreds > 0);
- jti.m_numFalsePreds--;
- }
-
- assert(!(BlockSetOps::IsMember(this, jti.m_ambiguousPreds, jti.m_fallThroughPred->bbNum)));
- BlockSetOps::AddElemD(this, jti.m_ambiguousPreds, jti.m_fallThroughPred->bbNum);
- jti.m_numAmbiguousPreds++;
- }
-
- jti.m_fallThroughPred = nullptr;
- }
-
- // There still should be at least one pred that can bypass block.
- //
- if ((jti.m_numTruePreds == 0) && (jti.m_numFalsePreds == 0))
- {
- // This is possible, but also should be rare.
- //
- JITDUMP(FMT_BB " now only has ambiguous preds, not jump threading\n", jti.m_block->bbNum);
- return false;
- }
-
- // Determine if either set of preds will route via block.
- //
- bool truePredsWillReuseBlock = false;
- bool falsePredsWillReuseBlock = false;
-
- if (jti.m_fallThroughPred != nullptr)
- {
- assert(jti.m_numAmbiguousPreds == 0);
- truePredsWillReuseBlock = BlockSetOps::IsMember(this, jti.m_truePreds, jti.m_fallThroughPred->bbNum);
- falsePredsWillReuseBlock = !truePredsWillReuseBlock;
- }
- else if (jti.m_numAmbiguousPreds == 0)
- {
- truePredsWillReuseBlock = true;
- falsePredsWillReuseBlock = !truePredsWillReuseBlock;
- }
-
- assert(!(truePredsWillReuseBlock && falsePredsWillReuseBlock));
-
// We should be good to go
//
JITDUMP("Optimizing via jump threading\n");
- // Fix block, if we're reusing it.
- //
- if (truePredsWillReuseBlock)
- {
- Statement* const lastStmt = jti.m_block->lastStmt();
- fgRemoveStmt(jti.m_block, lastStmt);
- JITDUMP(" repurposing " FMT_BB " to always jump to " FMT_BB "\n", jti.m_block->bbNum, jti.m_trueTarget->bbNum);
- fgRemoveRefPred(jti.m_falseTarget, jti.m_block);
- jti.m_block->SetKind(BBJ_ALWAYS);
- }
- else if (falsePredsWillReuseBlock)
- {
- Statement* const lastStmt = jti.m_block->lastStmt();
- fgRemoveStmt(jti.m_block, lastStmt);
- JITDUMP(" repurposing " FMT_BB " to always jump to " FMT_BB "\n", jti.m_block->bbNum,
- jti.m_falseTarget->bbNum);
- fgRemoveRefPred(jti.m_trueTarget, jti.m_block);
- jti.m_block->SetKindAndTarget(BBJ_ALWAYS, jti.m_falseTarget);
- jti.m_block->SetFlags(BBF_NONE_QUIRK);
- }
-
// Now reroute the flow from the predecessors.
// If this pred is in the set that will reuse block, do nothing.
// Else revise pred to branch directly to the appropriate successor of block.
@@ -1749,22 +1610,8 @@ bool Compiler::optJumpThreadCore(JumpThreadInfo& jti)
const bool isTruePred = BlockSetOps::IsMember(this, jti.m_truePreds, predBlock->bbNum);
- // Do we need to alter flow from this pred?
+ // Jump to the appropriate successor.
//
- if ((isTruePred && truePredsWillReuseBlock) || (!isTruePred && falsePredsWillReuseBlock))
- {
- // No, we can leave as is.
- //
- JITDUMP("%s pred " FMT_BB " will continue to target " FMT_BB "\n", isTruePred ? "true" : "false",
- predBlock->bbNum, jti.m_block->bbNum);
- continue;
- }
-
- // Yes, we need to jump to the appropriate successor.
- // Note we should not be altering flow for the fall-through pred.
- //
- assert(predBlock != jti.m_fallThroughPred);
-
if (isTruePred)
{
JITDUMP("Jump flow from pred " FMT_BB " -> " FMT_BB
diff --git a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp
index 117c31fb99a99..bd83b0ac3dbf1 100644
--- a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp
+++ b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp
@@ -186,7 +186,6 @@ void StackFrameIterator::InternalInit(Thread * pThreadToWalk, PInvokeTransitionF
#ifdef TARGET_ARM
m_RegDisplay.pLR = (PTR_UIntNative)PTR_HOST_MEMBER(PInvokeTransitionFrame, pFrame, m_RIP);
- m_RegDisplay.pR11 = (PTR_UIntNative)PTR_HOST_MEMBER(PInvokeTransitionFrame, pFrame, m_ChainPointer);
if (pFrame->m_Flags & PTFF_SAVE_R4) { m_RegDisplay.pR4 = pPreservedRegsCursor++; }
if (pFrame->m_Flags & PTFF_SAVE_R5) { m_RegDisplay.pR5 = pPreservedRegsCursor++; }
@@ -632,6 +631,7 @@ void StackFrameIterator::InternalInit(Thread * pThreadToWalk, NATIVE_CONTEXT* pC
m_RegDisplay.pR9 = (PTR_UIntNative)PTR_TO_REG(pCtx, R9);
m_RegDisplay.pR10 = (PTR_UIntNative)PTR_TO_REG(pCtx, R10);
m_RegDisplay.pR11 = (PTR_UIntNative)PTR_TO_REG(pCtx, R11);
+ m_RegDisplay.pR12 = (PTR_UIntNative)PTR_TO_REG(pCtx, R12);
m_RegDisplay.pLR = (PTR_UIntNative)PTR_TO_REG(pCtx, Lr);
#else
PORTABILITY_ASSERT("StackFrameIterator::InternalInit");
diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixContext.cpp b/src/coreclr/nativeaot/Runtime/unix/UnixContext.cpp
index 4087e2ae6ac4f..8e587901f60a3 100644
--- a/src/coreclr/nativeaot/Runtime/unix/UnixContext.cpp
+++ b/src/coreclr/nativeaot/Runtime/unix/UnixContext.cpp
@@ -266,6 +266,7 @@
#define MCREG_R9(mc) ((mc).arm_r9)
#define MCREG_R10(mc) ((mc).arm_r10)
#define MCREG_R11(mc) ((mc).arm_fp)
+#define MCREG_R12(mc) ((mc).arm_ip)
#elif defined(HOST_X86)
@@ -526,6 +527,7 @@ uint64_t GetPC(void* context)
uint64_t& UNIX_CONTEXT::R9(){ return (uint64_t&)MCREG_R9(ctx.uc_mcontext); }
uint64_t& UNIX_CONTEXT::R10(){ return (uint64_t&)MCREG_R10(ctx.uc_mcontext); }
uint64_t& UNIX_CONTEXT::R11(){ return (uint64_t&)MCREG_R11(ctx.uc_mcontext); }
+ uint64_t& UNIX_CONTEXT::R12(){ return (uint64_t&)MCREG_R12(ctx.uc_mcontext); }
#else
PORTABILITY_ASSERT("UNIX_CONTEXT");
diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixContext.h b/src/coreclr/nativeaot/Runtime/unix/UnixContext.h
index 471f395bef1f5..caddff419d373 100644
--- a/src/coreclr/nativeaot/Runtime/unix/UnixContext.h
+++ b/src/coreclr/nativeaot/Runtime/unix/UnixContext.h
@@ -136,6 +136,7 @@ struct UNIX_CONTEXT
uint64_t& R9();
uint64_t& R10();
uint64_t& R11();
+ uint64_t& R12();
uintptr_t GetIp() { return (uintptr_t)Pc(); }
uintptr_t GetSp() { return (uintptr_t)Sp(); }
@@ -155,6 +156,7 @@ struct UNIX_CONTEXT
lambda((size_t*)&R9());
lambda((size_t*)&R10());
lambda((size_t*)&R11());
+ lambda((size_t*)&R12());
}
#else
PORTABILITY_ASSERT("UNIX_CONTEXT");
diff --git a/src/coreclr/nativeaot/Runtime/unix/UnwindHelpers.cpp b/src/coreclr/nativeaot/Runtime/unix/UnwindHelpers.cpp
index 9e6ec0af36e16..3bd963a7cd0e8 100644
--- a/src/coreclr/nativeaot/Runtime/unix/UnwindHelpers.cpp
+++ b/src/coreclr/nativeaot/Runtime/unix/UnwindHelpers.cpp
@@ -335,14 +335,14 @@ struct Registers_REGDISPLAY : REGDISPLAY
inline static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM; }
bool validRegister(int num) const;
- bool validFloatRegister(int num) { return false; };
+ bool validFloatRegister(int num) const;
bool validVectorRegister(int num) const { return false; };
uint32_t getRegister(int num) const;
void setRegister(int num, uint32_t value, uint32_t location);
- double getFloatRegister(int num) const {abort();}
- void setFloatRegister(int num, double value) {abort();}
+ double getFloatRegister(int num) const;
+ void setFloatRegister(int num, double value);
libunwind::v128 getVectorRegister(int num) const {abort();}
void setVectorRegister(int num, libunwind::v128 value) {abort();}
@@ -393,6 +393,10 @@ inline bool Registers_REGDISPLAY::validRegister(int num) const {
return false;
}
+inline bool Registers_REGDISPLAY::validFloatRegister(int num) const {
+ return num >= UNW_ARM_D0 && num <= UNW_ARM_D31;
+}
+
inline uint32_t Registers_REGDISPLAY::getRegister(int regNum) const {
if (regNum == UNW_REG_SP || regNum == UNW_ARM_SP)
return SP;
@@ -499,6 +503,24 @@ void Registers_REGDISPLAY::setRegister(int num, uint32_t value, uint32_t locatio
}
}
+double Registers_REGDISPLAY::getFloatRegister(int num) const
+{
+ if (num >= UNW_ARM_D8 && num <= UNW_ARM_D15)
+ {
+ return D[num - UNW_ARM_D8];
+ }
+
+ PORTABILITY_ASSERT("unsupported arm register");
+}
+
+void Registers_REGDISPLAY::setFloatRegister(int num, double value)
+{
+ if (num >= UNW_ARM_D8 && num <= UNW_ARM_D15)
+ {
+ D[num - UNW_ARM_D8] = value;
+ }
+}
+
#endif // TARGET_ARM
#if defined(TARGET_ARM64)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
index e4d7121c6023b..5355c4be9ff12 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
@@ -419,6 +419,7 @@ private sealed class ScannedDevirtualizationManager : DevirtualizationManager
private Dictionary> _implementators = new();
private HashSet _disqualifiedTypes = new();
private HashSet _overridenMethods = new();
+ private HashSet _generatedVirtualMethods = new();
public ScannedDevirtualizationManager(NodeFactory factory, ImmutableArray> markedNodes)
{
@@ -426,6 +427,12 @@ public ScannedDevirtualizationManager(NodeFactory factory, ImmutableArray eetypeNode.Type,
@@ -564,7 +571,6 @@ static List BuildVTable(NodeFactory factory, TypeDesc currentType, T
_overridenMethods.Add(baseVtable[i]);
}
}
-
}
}
}
@@ -642,10 +648,25 @@ public override bool IsEffectivelySealed(TypeDesc type)
public override bool IsEffectivelySealed(MethodDesc method)
{
- if (method.IsFinal || IsEffectivelySealed(method.OwningType))
+ // First try to answer using metadata
+ if (method.IsFinal || method.OwningType.IsSealed())
return true;
- return !_overridenMethods.Contains(method.GetCanonMethodTarget(CanonicalFormKind.Specific));
+ // Now let's see if we can seal through whole program view
+
+ // Sealing abstract methods or methods on interface can't lead to anything good
+ if (method.IsAbstract || method.OwningType.IsInterface)
+ return false;
+
+ // If we want to make something final, we better have a method body for it.
+ // Sometimes we might have optimized it away so don't let codegen make direct calls.
+ // NOTE: this check naturally also rejects generic virtual methods since we don't track them.
+ MethodDesc canonMethod = method.GetCanonMethodTarget(CanonicalFormKind.Specific);
+ if (!_generatedVirtualMethods.Contains(canonMethod))
+ return false;
+
+ // If we haven't seen any other method override this, this method is sealed
+ return !_overridenMethods.Contains(canonMethod);
}
protected override MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType implType, out CORINFO_DEVIRTUALIZATION_DETAIL devirtualizationDetail)
diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs
index 39a03aafa7e28..0e6dd63b49fc1 100644
--- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs
+++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs
@@ -1329,9 +1329,27 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO
}
else
{
- if (!targetMethod.IsVirtual ||
- // Final/sealed has no meaning for interfaces, but lets us devirtualize otherwise
- !targetMethod.OwningType.IsInterface && (targetMethod.IsFinal || targetMethod.OwningType.IsSealed()))
+ // We can devirtualize the callvirt if the method is not virtual to begin with
+ bool canDevirt = !targetMethod.IsVirtual;
+
+ // Final/sealed has no meaning for interfaces, but might let us devirtualize otherwise
+ if (!canDevirt && !targetMethod.OwningType.IsInterface)
+ {
+ // Check if we can devirt per metadata
+ canDevirt = targetMethod.IsFinal || targetMethod.OwningType.IsSealed();
+
+ // We might be able to devirt based on whole program view
+ if (!canDevirt
+ // Do not devirt if devirtualization would need a generic dictionary entry that we didn't predict
+ // during scanning (i.e. compiling a shared method body and we need to call another shared body
+ // with a method generic dictionary argument).
+ && (!pResult->exactContextNeedsRuntimeLookup || !targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).RequiresInstMethodDescArg()))
+ {
+ canDevirt = _compilation.IsEffectivelySealed(targetMethod);
+ }
+ }
+
+ if (canDevirt)
{
resolvedCallVirt = true;
directCall = true;
diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs
index 41540f8b6da59..8dd1e4cb13673 100644
--- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs
@@ -80,6 +80,16 @@ public AssemblyChecker (
}
public void Verify ()
+ {
+ var errors = VerifyImpl().ToList();
+ if (errors.Any())
+ {
+ Assert.Fail(string.Join(Environment.NewLine, errors));
+ }
+
+ }
+
+ IEnumerable VerifyImpl()
{
// There are no type forwarders left after compilation in Native AOT
// VerifyExportedTypes (originalAssembly, linkedAssembly);
@@ -114,7 +124,8 @@ public void Verify ()
token,
out LinkedEntity? linkedMember);
- VerifyTypeDefinition (td, linkedMember);
+ foreach(var err in VerifyTypeDefinition (td, linkedMember))
+ yield return err;
linkedMembers.Remove (token);
continue;
@@ -123,22 +134,22 @@ public void Verify ()
throw new NotImplementedException ($"Don't know how to check member of type {originalMember.GetType ()}");
}
- // Verify anything not in the main assembly
- VerifyLinkingOfOtherAssemblies(this.originalAssembly);
+ // Verify anything not in the main assembly
+ foreach(var err in VerifyLinkingOfOtherAssemblies(this.originalAssembly))
+ yield return err;
- // Filter out all members which are not from the main assembly
- // The Kept attributes are "optional" for non-main assemblies
- string mainModuleName = originalAssembly.Name.Name;
+ // Filter out all members which are not from the main assembly
+ // The Kept attributes are "optional" for non-main assemblies
+ string mainModuleName = originalAssembly.Name.Name;
List externalMembers = linkedMembers.Where (m => GetModuleName (m.Value.Entity) != mainModuleName).Select (m => m.Key).ToList ();
foreach (var externalMember in externalMembers) {
linkedMembers.Remove (externalMember);
}
if (linkedMembers.Count != 0)
- Assert.Fail(
- "Linked output includes unexpected member:\n " +
- string.Join ("\n ", linkedMembers.Values.Select (e => e.Entity.GetDisplayName ())));
- }
+ yield return "Linked output includes unexpected member:\n " +
+ string.Join ("\n ", linkedMembers.Values.Select (e => e.Entity.GetDisplayName ()));
+ }
private void PopulateLinkedMembers ()
{
@@ -278,32 +289,32 @@ static bool ShouldIncludeType (TypeDesc type)
static bool ShouldIncludeMethod (MethodDesc method) => ShouldIncludeType (method.OwningType) && ShouldIncludeEntityByDisplayName (method);
}
- private static MetadataType? GetOwningType (TypeSystemEntity? entity)
- {
- return entity switch
- {
- MetadataType type => type.ContainingType as MetadataType,
- MethodDesc method => method.OwningType as MetadataType,
- PropertyPseudoDesc prop => prop.OwningType,
- EventPseudoDesc e => e.OwningType,
- _ => null
- };
- }
+ private static MetadataType? GetOwningType (TypeSystemEntity? entity)
+ {
+ return entity switch
+ {
+ MetadataType type => type.ContainingType as MetadataType,
+ MethodDesc method => method.OwningType as MetadataType,
+ PropertyPseudoDesc prop => prop.OwningType,
+ EventPseudoDesc e => e.OwningType,
+ _ => null
+ };
+ }
private static string? GetModuleName (TypeSystemEntity entity)
{
return entity switch {
MetadataType type => type.Module.ToString (),
- _ => GetOwningType(entity)?.Module.ToString()
+ _ => GetOwningType(entity)?.Module.ToString()
};
}
- protected virtual void VerifyModule (ModuleDefinition original, ModuleDefinition? linked)
+ protected virtual IEnumerable VerifyModule (ModuleDefinition original, ModuleDefinition? linked)
{
// We never link away a module today so let's make sure the linked one isn't null
if (linked == null) {
- Assert.Fail($"Linked assembly `{original.Assembly.Name.Name}` is missing module `{original.Name}`");
- return;
+ yield return $"Linked assembly `{original.Assembly.Name.Name}` is missing module `{original.Name}`";
+ yield break;
}
var expected = original.Assembly.MainModule.AllDefinedTypes ()
@@ -314,16 +325,18 @@ protected virtual void VerifyModule (ModuleDefinition original, ModuleDefinition
.Select (name => name.Name)
.ToArray ();
- Assert.Equal (expected, actual);
+ if (!expected.Equals(actual))
+ yield return "Module references do not match";
- VerifyCustomAttributes (original, linked);
+ foreach(var err in VerifyCustomAttributes (original, linked))
+ yield return err;
}
- void VerifyTypeDefinition (TypeDefinition original, LinkedEntity? linkedEntity)
+ IEnumerable VerifyTypeDefinition (TypeDefinition original, LinkedEntity? linkedEntity)
{
TypeDesc? linked = linkedEntity?.Entity as TypeDesc;
if (linked != null && NameUtils.GetActualOriginDisplayName (linked) is string linkedDisplayName && verifiedGeneratedTypes.Contains (linkedDisplayName))
- return;
+ yield break;
EcmaModule? linkedModule = (linked as MetadataType)?.Module as EcmaModule;
@@ -340,7 +353,7 @@ void VerifyTypeDefinition (TypeDefinition original, LinkedEntity? linkedEntity)
if (!expectedKept) {
if (linked == null)
- return;
+ yield break;
// Compiler generated members can't be annotated with `Kept` attributes directly
// For some of them we have special attributes (backing fields for example), but it's impractical to define
@@ -351,13 +364,14 @@ void VerifyTypeDefinition (TypeDefinition original, LinkedEntity? linkedEntity)
// we do want to validate. There's no specific use case right now, but I can easily imagine one
// for more detailed testing of for example custom attributes on local functions, or similar.
if (!IsCompilerGeneratedMember (original))
- Assert.Fail($"Type `{original}' should have been removed");
+ yield return $"Type `{original}' should have been removed";
}
bool prev = checkNames;
checkNames |= original.HasAttribute (nameof (VerifyMetadataNamesAttribute));
- VerifyTypeDefinitionKept (original, linked);
+ foreach(var err in VerifyTypeDefinitionKept (original, linked))
+ yield return err;
checkNames = prev;
@@ -370,7 +384,7 @@ void VerifyTypeDefinition (TypeDefinition original, LinkedEntity? linkedEntity)
var linkedMemberName = linkedMembers.Keys.FirstOrDefault (l => l.Contains (newName));
if (linkedMemberName == null)
- Assert.Fail($"Newly created member '{newName}' was not found");
+ yield return $"Newly created member '{newName}' was not found";
linkedMembers.Remove (linkedMemberName);
}
@@ -378,33 +392,43 @@ void VerifyTypeDefinition (TypeDefinition original, LinkedEntity? linkedEntity)
}
}
- protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDesc? linked)
+ protected virtual IEnumerable VerifyTypeDefinitionKept (TypeDefinition original, TypeDesc? linked)
{
// NativeAOT will not keep delegate backing field type information, it's compiled down to a set of static fields
// this infra currently doesn't track fields in any way.
// Same goes for private implementation detail type.
if (IsDelegateBackingFieldsType (original) || IsPrivateImplementationDetailsType(original))
- return;
+ yield break;
if (linked == null) {
- Assert.Fail($"Type `{original}' should have been kept");
- return;
+ yield return $"Type `{original}' should have been kept";
+ yield break;
}
#if false
// Skip verification of type metadata for compiler generated types (we don't currently need it yet)
if (!IsCompilerGeneratedMember (original)) {
- VerifyKeptByAttributes (original, linked);
+ foreach(var err in VerifyKeptByAttributes (original, linked))
+ yield return err;
if (!original.IsInterface)
- VerifyBaseType (original, linked);
+ {
+ foreach(var err in VerifyBaseType (original, linked))
+ yield return err;
+ }
- VerifyInterfaces (original, linked);
- VerifyPseudoAttributes (original, linked);
- VerifyGenericParameters (original, linked);
- VerifyCustomAttributes (original, linked);
- VerifySecurityAttributes (original, linked);
+ foreach(var err in VerifyInterfaces (original, linked))
+ yield return err;
+ foreach(var err in VerifyPseudoAttributes (original, linked))
+ yield return err;
+ foreach(var err in VerifyGenericParameters (original, linked))
+ yield return err;
+ foreach(var err in VerifyCustomAttributes (original, linked))
+ yield return err;
+ foreach(var err in VerifySecurityAttributes (original, linked))
+ yield return err;
- VerifyFixedBufferFields (original, linked);
+ foreach(var err in VerifyFixedBufferFields (original, linked))
+ yield return err;
}
#endif
@@ -414,7 +438,8 @@ protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDe
token,
out LinkedEntity? linkedMember);
- VerifyTypeDefinition (td, linkedMember);
+ foreach(var err in VerifyTypeDefinition (td, linkedMember))
+ yield return err;
linkedMembers.Remove (token);
}
@@ -425,7 +450,8 @@ protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDe
linkedMembers.TryGetValue (
token,
out LinkedEntity? linkedMember);
- VerifyProperty (p, linkedMember, linked);
+ foreach(var err in VerifyProperty (p, linkedMember, linked))
+ yield return err;
linkedMembers.Remove (token);
}
// Need to check events before fields so that the KeptBackingFieldAttribute is handled correctly
@@ -435,7 +461,8 @@ protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDe
linkedMembers.TryGetValue (
token,
out LinkedEntity? linkedMember);
- VerifyEvent (e, linkedMember, linked);
+ foreach(var err in VerifyEvent (e, linkedMember, linked))
+ yield return err;
linkedMembers.Remove (token);
}
@@ -460,12 +487,13 @@ protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDe
token,
out LinkedEntity? linkedMember);
- VerifyMethod (m, linkedMember);
+ foreach(var err in VerifyMethod (m, linkedMember))
+ yield return err;
linkedMembers.Remove (token);
}
}
- private void VerifyBaseType (TypeDefinition src, TypeDefinition linked)
+ private IEnumerable VerifyBaseType (TypeDefinition src, TypeDefinition linked)
{
string expectedBaseName;
var expectedBaseGenericAttr = src.CustomAttributes.FirstOrDefault (w => w.AttributeType.Name == nameof (KeptBaseTypeAttribute) && w.ConstructorArguments.Count > 1);
@@ -477,26 +505,28 @@ private void VerifyBaseType (TypeDefinition src, TypeDefinition linked)
}
if (expectedBaseName != linked.BaseType?.FullName) {
- Assert.Fail($"Incorrect base type on : {linked.Name}. Expected {expectedBaseName}, actual {linked.BaseType?.FullName}");
+ yield return $"Incorrect base type on : {linked.Name}. Expected {expectedBaseName}, actual {linked.BaseType?.FullName}";
}
}
- private void VerifyInterfaces (TypeDefinition src, TypeDefinition linked)
+ private IEnumerable VerifyInterfaces (TypeDefinition src, TypeDefinition linked)
{
var expectedInterfaces = new HashSet (src.CustomAttributes
.Where (w => w.AttributeType.Name == nameof (KeptInterfaceAttribute))
.Select (FormatBaseOrInterfaceAttributeValue));
if (expectedInterfaces.Count == 0) {
- Assert.False (linked.HasInterfaces, $"Type `{src}' has unexpected interfaces");
+ if (linked.HasInterfaces)
+ yield return $"Type `{src}' has unexpected interfaces";
} else {
foreach (var iface in linked.Interfaces) {
if (!expectedInterfaces.Remove (iface.InterfaceType.FullName)) {
- Assert.True (expectedInterfaces.Remove (iface.InterfaceType.Resolve ().FullName), $"Type `{src}' interface `{iface.InterfaceType.Resolve ().FullName}' should have been removed");
+ if (true != expectedInterfaces.Remove (iface.InterfaceType.Resolve ().FullName))
+ yield return $"Type `{src}' interface `{iface.InterfaceType.Resolve ().FullName}' should have been removed";
}
}
if (expectedInterfaces.Count != 0)
- Assert.Fail($"Expected interfaces were not found on {src}");
+ yield return $"Expected interfaces were not found on {src}";
}
}
@@ -523,26 +553,27 @@ private static string FormatBaseOrInterfaceAttributeValue (CustomAttribute attr)
return builder.ToString ();
}
- private void VerifyField (FieldDefinition src, FieldDesc? linked)
+ private IEnumerable VerifyField (FieldDefinition src, FieldDesc? linked)
{
bool compilerGenerated = IsCompilerGeneratedMember (src);
bool expectedKept = ShouldBeKept (src) | compilerGenerated;
if (!expectedKept) {
if (linked != null)
- Assert.Fail($"Field `{src}' should have been removed");
+ yield return $"Field `{src}' should have been removed";
- return;
+ yield break;
}
- VerifyFieldKept (src, linked, compilerGenerated);
+ foreach(var err in VerifyFieldKept (src, linked, compilerGenerated))
+ yield return err;
}
- private static void VerifyFieldKept (FieldDefinition src, FieldDesc? linked, bool compilerGenerated)
+ private static IEnumerable VerifyFieldKept (FieldDefinition src, FieldDesc? linked, bool compilerGenerated)
{
if (linked == null) {
- Assert.Fail($"Field `{src}' should have been kept");
- return;
+ yield return $"Field `{src}' should have been kept";
+ yield break;
}
@@ -550,18 +581,20 @@ private static void VerifyFieldKept (FieldDefinition src, FieldDesc? linked, boo
throw new NotImplementedException ("Constant value for a field is not yet supported by the test infra.");
#if false
if (!Equals (src.Constant, linked.Constant)) {
- Assert.Fail($"Field '{src}' value doesn's match. Expected {src.Constant}, actual {linked.Constant}");
+ yield return $"Field '{src}' value doesn's match. Expected {src.Constant}, actual {linked.Constant}";
}
#endif
#if false
- VerifyPseudoAttributes (src, linked);
+ foreach(var err in VerifyPseudoAttributes (src, linked))
+ yield return err;
if (!compilerGenerated)
- VerifyCustomAttributes (src, linked);
+ foreach(var err in VerifyCustomAttributes (src, linked))
+ yield return err;
#endif
}
- private void VerifyProperty (PropertyDefinition src, LinkedEntity? linkedEntity, TypeDesc linkedType)
+ private IEnumerable VerifyProperty (PropertyDefinition src, LinkedEntity? linkedEntity, TypeDesc linkedType)
{
PropertyPseudoDesc? linked = linkedEntity?.Entity as PropertyPseudoDesc;
VerifyMemberBackingField (src, linkedType);
@@ -571,55 +604,61 @@ private void VerifyProperty (PropertyDefinition src, LinkedEntity? linkedEntity,
if (!expectedKept) {
if (linked is not null)
- Assert.Fail($"Property `{src}' should have been removed");
+ yield return $"Property `{src}' should have been removed";
- return;
+ yield break;
}
if (linked is null) {
- Assert.Fail($"Property `{src}' should have been kept");
- return;
+ yield return $"Property `{src}' should have been kept";
+ yield break;
}
if (src.HasConstant)
throw new NotSupportedException ("Constant value for a property is not yet supported by the test infra.");
#if false
if (src.Constant != linked.Constant) {
- Assert.Fail($"Property '{src}' value doesn's match. Expected {src.Constant}, actual {linked.Constant}");
+ yield return $"Property '{src}' value doesn's match. Expected {src.Constant}, actual {linked.Constant}";
}
#endif
#if false
- VerifyPseudoAttributes (src, linked);
+ foreach(var err in VerifyPseudoAttributes (src, linked))
+ yield return err;
if (!compilerGenerated)
- VerifyCustomAttributes (src, linked);
+ {
+ foreach(var err in VerifyCustomAttributes (src, linked))
+ yield return err;
+ }
#endif
}
- private void VerifyEvent (EventDefinition src, LinkedEntity? linkedEntity, TypeDesc linkedType)
+ private IEnumerable VerifyEvent (EventDefinition src, LinkedEntity? linkedEntity, TypeDesc linkedType)
{
EventPseudoDesc? linked = linkedEntity?.Entity as EventPseudoDesc;
- VerifyMemberBackingField (src, linkedType);
+ foreach(var err in VerifyMemberBackingField (src, linkedType))
+ yield return err;
bool compilerGenerated = IsCompilerGeneratedMember (src);
bool expectedKept = ShouldBeKept (src) | compilerGenerated;
if (!expectedKept) {
if (linked is not null)
- Assert.Fail($"Event `{src}' should have been removed");
+ yield return $"Event `{src}' should have been removed";
- return;
+ yield break;
}
if (linked is null) {
- Assert.Fail($"Event `{src}' should have been kept");
- return;
+ yield return $"Event `{src}' should have been kept";
+ yield break;
}
if (src.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (KeptEventAddMethodAttribute))) {
// TODO: This is wrong - we can't validate that the method is present by looking at linked (as that is not actually linked)
// we need to look into linkedMembers to see if the method was actually preserved by the compiler (and has an entry point)
- VerifyMethodInternal (src.AddMethod, new LinkedMethodEntity(linked.AddMethod, false), true, compilerGenerated);
+ foreach(var err in VerifyMethodInternal (src.AddMethod, new LinkedMethodEntity(linked.AddMethod, false), true, compilerGenerated))
+ yield return err;
verifiedEventMethods.Add (src.AddMethod.FullName);
linkedMembers.Remove (new AssemblyQualifiedToken (src.AddMethod));
}
@@ -627,47 +666,54 @@ private void VerifyEvent (EventDefinition src, LinkedEntity? linkedEntity, TypeD
if (src.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (KeptEventRemoveMethodAttribute))) {
// TODO: This is wrong - we can't validate that the method is present by looking at linked (as that is not actually linked)
// we need to look into linkedMembers to see if the method was actually preserved by the compiler (and has an entry point)
- VerifyMethodInternal (src.RemoveMethod, new LinkedMethodEntity(linked.RemoveMethod, false), true, compilerGenerated);
+ foreach(var err in VerifyMethodInternal (src.RemoveMethod, new LinkedMethodEntity(linked.RemoveMethod, false), true, compilerGenerated))
+ yield return err;
verifiedEventMethods.Add (src.RemoveMethod.FullName);
linkedMembers.Remove (new AssemblyQualifiedToken (src.RemoveMethod));
}
#if false
- VerifyPseudoAttributes (src, linked);
+ foreach(var err in VerifyPseudoAttributes (src, linked))
+ yield return err;
if (!compilerGenerated)
- VerifyCustomAttributes (src, linked);
+ {
+ foreach(var err in VerifyCustomAttributes (src, linned))
+ yield return err;
+ }
#endif
}
- private void VerifyMethod (MethodDefinition src, LinkedEntity? linkedEntity)
+ private IEnumerable VerifyMethod (MethodDefinition src, LinkedEntity? linkedEntity)
{
LinkedMethodEntity? linked = linkedEntity as LinkedMethodEntity;
bool compilerGenerated = IsCompilerGeneratedMember (src);
bool expectedKept = ShouldMethodBeKept (src);
- VerifyMethodInternal (src, linked, expectedKept, compilerGenerated);
+ foreach(var err in VerifyMethodInternal (src, linked, expectedKept, compilerGenerated))
+ yield return err;
}
- private void VerifyMethodInternal (MethodDefinition src, LinkedMethodEntity? linked, bool expectedKept, bool compilerGenerated)
+ private IEnumerable VerifyMethodInternal (MethodDefinition src, LinkedMethodEntity? linked, bool expectedKept, bool compilerGenerated)
{
if (!expectedKept) {
if (linked == null)
- return;
+ yield break;
// Similar to comment on types, compiler-generated methods can't be annotated with Kept attribute directly
// so we're not going to validate kept/remove on them. Note that we're still going to go validate "into" them
// to check for other properties (like parameter name presence/removal for example)
if (!compilerGenerated)
- Assert.Fail($"Method `{NameUtils.GetExpectedOriginDisplayName (src)}' should have been removed");
+ yield return $"Method `{NameUtils.GetExpectedOriginDisplayName (src)}' should have been removed";
}
- VerifyMethodKept (src, linked, compilerGenerated);
+ foreach(var err in VerifyMethodKept (src, linked, compilerGenerated))
+ yield return err;
}
- private void VerifyMemberBackingField (IMemberDefinition src, TypeDesc? linkedType)
+ private IEnumerable VerifyMemberBackingField (IMemberDefinition src, TypeDesc? linkedType)
{
var keptBackingFieldAttribute = src.CustomAttributes.FirstOrDefault (attr => attr.AttributeType.Name == nameof (KeptBackingFieldAttribute));
if (keptBackingFieldAttribute == null)
- return;
+ yield break;
var backingFieldName = src.MetadataToken.TokenType == TokenType.Property
? $"<{src.Name}>k__BackingField" : src.Name;
@@ -683,60 +729,75 @@ private void VerifyMemberBackingField (IMemberDefinition src, TypeDesc? linkedTy
}
if (srcField == null) {
- Assert.Fail($"{src.MetadataToken.TokenType} `{src}', could not locate the expected backing field {backingFieldName}");
- return;
+ yield return $"{src.MetadataToken.TokenType} `{src}', could not locate the expected backing field {backingFieldName}";
+ yield break;
}
- VerifyFieldKept (srcField, linkedType?.GetFields ()?.FirstOrDefault (l => srcField.Name == l.Name), compilerGenerated: true);
+ foreach(var err in VerifyFieldKept (srcField, linkedType?.GetFields ()?.FirstOrDefault (l => srcField.Name == l.Name), compilerGenerated: true))
+ yield return err;
verifiedGeneratedFields.Add (srcField.FullName);
linkedMembers.Remove (new AssemblyQualifiedToken (srcField));
}
- void VerifyMethodKept (MethodDefinition src, LinkedMethodEntity? linked, bool compilerGenerated)
+ IEnumerable VerifyMethodKept (MethodDefinition src, LinkedMethodEntity? linked, bool compilerGenerated)
{
if (linked == null) {
- Assert.Fail($"Method `{NameUtils.GetExpectedOriginDisplayName (src)}' should have been kept");
- return;
+ yield return $"Method `{NameUtils.GetExpectedOriginDisplayName (src)}' should have been kept";
+ yield break;
}
#if false
- VerifyPseudoAttributes (src, linked);
- VerifyGenericParameters (src, linked);
+ foreach(var err in VerifyPseudoAttributes (src, linked))
+ yield return err;
+ foreach(var err in VerifyGenericParameters (src, linked))
+ yield return err;
if (!compilerGenerated) {
- VerifyCustomAttributes (src, linked);
- VerifyCustomAttributes (src.MethodReturnType, linked.MethodReturnType);
+ foreach(var err in VerifyCustomAttributes (src, linked))
+ yield return err;
+ foreach(var err in VerifyCustomAttributes (src.MethodReturnType, linked.MethodReturnType))
+ yield return err;
}
#endif
- VerifyParameters (src, linked);
+ foreach(var err in VerifyParameters (src, linked))
+ yield return err;
#if false
- VerifySecurityAttributes (src, linked);
- VerifyArrayInitializers (src, linked);
+ foreach(var err in VerifySecurityAttributes (src, linked))
+ yield return err;
+ foreach(var err in VerifyArrayInitializers (src, linked))
+ yield return err;
// Method bodies are not very different in Native AOT
- VerifyMethodBody (src, linked);
- VerifyKeptByAttributes (src, linked);
+ foreach(var err in VerifyMethodBody (src, linked))
+ yield return err;
+ foreach(var err in VerifyKeptByAttributes (src, linked))
+ yield return err;
#endif
}
- protected virtual void VerifyMethodBody (MethodDefinition src, MethodDefinition linked)
+ protected virtual IEnumerable VerifyMethodBody (MethodDefinition src, MethodDefinition linked)
{
if (!src.HasBody)
- return;
+ yield break;
- VerifyInstructions (src, linked);
- VerifyLocals (src, linked);
+ foreach(var err in VerifyInstructions (src, linked))
+ yield return err;
+ foreach(var err in VerifyLocals (src, linked))
+ yield return err;
}
- protected static void VerifyInstructions (MethodDefinition src, MethodDefinition linked)
+ protected static IEnumerable VerifyInstructions (MethodDefinition src, MethodDefinition linked)
{
- VerifyBodyProperties (
+ foreach (var err in VerifyBodyProperties (
src,
linked,
nameof (ExpectedInstructionSequenceAttribute),
nameof (ExpectBodyModifiedAttribute),
"instructions",
m => FormatMethodBody (m.Body),
- attr => GetStringArrayAttributeValue (attr)!.ToArray ());
+ attr => GetStringArrayAttributeValue (attr)!.ToArray ()))
+ {
+ yield return err;
+ }
}
public static string[] FormatMethodBody (MethodBody body)
@@ -848,19 +909,22 @@ private static string FormatInstruction (Instruction instr)
}
}
- private static void VerifyLocals (MethodDefinition src, MethodDefinition linked)
+ private static IEnumerable VerifyLocals (MethodDefinition src, MethodDefinition linked)
{
- VerifyBodyProperties (
+ foreach(var err in VerifyBodyProperties (
src,
linked,
nameof (ExpectedLocalsSequenceAttribute),
nameof (ExpectLocalsModifiedAttribute),
"locals",
m => m.Body.Variables.Select (v => v.VariableType.ToString ()).ToArray (),
- attr => GetStringOrTypeArrayAttributeValue (attr).ToArray ());
+ attr => GetStringOrTypeArrayAttributeValue (attr).ToArray ()))
+ {
+ yield return err;
+ }
}
- public static void VerifyBodyProperties (MethodDefinition src, MethodDefinition linked, string sequenceAttributeName, string expectModifiedAttributeName,
+ public static IEnumerable VerifyBodyProperties (MethodDefinition src, MethodDefinition linked, string sequenceAttributeName, string expectModifiedAttributeName,
string propertyDescription,
Func valueCollector,
Func getExpectFromSequenceAttribute)
@@ -870,16 +934,25 @@ public static void VerifyBodyProperties (MethodDefinition src, MethodDefinition
var srcValues = valueCollector (src);
if (src.CustomAttributes.Any (attr => attr.AttributeType.Name == expectModifiedAttributeName)) {
- linkedValues.Should ().BeEquivalentTo (srcValues, $"Expected method `{src} to have {propertyDescription} modified, however, the {propertyDescription} were the same as the original\n{FormattingUtils.FormatSequenceCompareFailureMessage (linkedValues, srcValues)}");
+ if (linkedValues.SequenceEqual(srcValues))
+ {
+ yield return $"Expected method `{src} to have {propertyDescription} modified, however, the {propertyDescription} were the same as the original\n{FormattingUtils.FormatSequenceCompareFailureMessage (linkedValues, srcValues)}";
+ }
} else if (expectedSequenceAttribute != null) {
var expected = getExpectFromSequenceAttribute (expectedSequenceAttribute).ToArray ();
- linkedValues.Should ().BeEquivalentTo (expected, $"Expected method `{src} to have it's {propertyDescription} modified, however, the sequence of {propertyDescription} does not match the expected value\n{FormattingUtils.FormatSequenceCompareFailureMessage2 (linkedValues, expected, srcValues)}");
+ if (!linkedValues.SequenceEqual(expected))
+ {
+ yield return $"Expected method `{src} to have it's {propertyDescription} modified, however, the sequence of {propertyDescription} does not match the expected value\n{FormattingUtils.FormatSequenceCompareFailureMessage2 (linkedValues, expected, srcValues)}";
+ }
} else {
- linkedValues.Should ().BeEquivalentTo (srcValues, $"Expected method `{src} to have it's {propertyDescription} unchanged, however, the {propertyDescription} differ from the original\n{FormattingUtils.FormatSequenceCompareFailureMessage (linkedValues, srcValues)}");
+ if (!linkedValues.SequenceEqual(srcValues))
+ {
+ yield return $"Expected method `{src} to have it's {propertyDescription} unchanged, however, the {propertyDescription} differ from the original\n{FormattingUtils.FormatSequenceCompareFailureMessage (linkedValues, srcValues)}";
+ }
}
}
- private void VerifyReferences (AssemblyDefinition original, AssemblyDefinition linked)
+ private IEnumerable VerifyReferences (AssemblyDefinition original, AssemblyDefinition linked)
{
var expected = original.MainModule.AllDefinedTypes ()
.SelectMany (t => GetCustomAttributeCtorValues (t, nameof (KeptReferenceAttribute)))
@@ -896,13 +969,14 @@ private void VerifyReferences (AssemblyDefinition original, AssemblyDefinition l
Once 1 kept reference attribute is used, the test will need to define all of of it's expected references
*/
if (expected.Length == 0)
- return;
+ yield break;
var actual = linked.MainModule.AssemblyReferences
.Select (name => name.Name)
.ToArray ();
- actual.Should ().BeEquivalentTo (expected);
+ if (!actual.SequenceEqual(expected))
+ yield return $"Expected references do not match actual references:\n\tExpected: {string.Join(", ", expected)}\n\tActual: {string.Join(", ", actual)}";
}
private string? ReduceAssemblyFileNameOrNameToNameOnly (string? fileNameOrAssemblyName)
@@ -917,75 +991,97 @@ private void VerifyReferences (AssemblyDefinition original, AssemblyDefinition l
return fileNameOrAssemblyName;
}
- private void VerifyResources (AssemblyDefinition original, AssemblyDefinition linked)
+ private IEnumerable VerifyResources (AssemblyDefinition original, AssemblyDefinition linked)
{
- var expectedResourceNames = original.MainModule.AllDefinedTypes ()
+ List expectedResourceNames = original.MainModule.AllDefinedTypes ()
.SelectMany (t => GetCustomAttributeCtorValues (t, nameof (KeptResourceAttribute)))
.ToList ();
foreach (var resource in linked.MainModule.Resources) {
if (!expectedResourceNames.Remove (resource.Name))
- Assert.Fail($"Resource '{resource.Name}' should be removed.");
+ yield return $"Resource '{resource.Name}' should be removed.";
EmbeddedResource embeddedResource = (EmbeddedResource) resource;
var expectedResource = (EmbeddedResource) original.MainModule.Resources.First (r => r.Name == resource.Name);
- embeddedResource.GetResourceData ().Should ().BeEquivalentTo (expectedResource.GetResourceData (), $"Resource '{resource.Name}' data doesn't match.");
+ if (!embeddedResource.GetResourceData().SequenceEqual(expectedResource.GetResourceData()))
+ yield return $"Resource '{resource.Name}' data doesn't match.";
}
if (expectedResourceNames.Count > 0) {
- Assert.Fail($"Resource '{expectedResourceNames.First ()}' should be kept.");
+ yield return $"Resource '{expectedResourceNames.First ()}' should be kept.";
}
}
- private void VerifyExportedTypes (AssemblyDefinition original, AssemblyDefinition linked)
+ private IEnumerable VerifyExportedTypes (AssemblyDefinition original, AssemblyDefinition linked)
{
var expectedTypes = original.MainModule.AllDefinedTypes ()
.SelectMany (t => GetCustomAttributeCtorValues (t, nameof (KeptExportedTypeAttribute)).Select (l => l?.FullName ?? "")).ToArray ();
- linked.MainModule.ExportedTypes.Select (l => l.FullName).Should ().BeEquivalentTo (expectedTypes);
+ if (!linked.MainModule.ExportedTypes.Select (l => l.FullName).SequenceEqual(expectedTypes))
+ yield return $"Exported types do not match expected values";
}
- protected virtual void VerifyPseudoAttributes (MethodDefinition src, MethodDefinition linked)
+ protected virtual IEnumerable VerifyPseudoAttributes (MethodDefinition src, MethodDefinition linked)
{
var expected = (MethodAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes);
- linked.Attributes.Should ().Be (expected, $"Method `{src}' pseudo attributes did not match expected");
+ if(!linked.Attributes.Equals(expected))
+ {
+ yield return $"Method `{src}' pseudo attributes did not match expected";
+ }
}
- protected virtual void VerifyPseudoAttributes (TypeDefinition src, TypeDefinition linked)
+ protected virtual IEnumerable VerifyPseudoAttributes (TypeDefinition src, TypeDefinition linked)
{
var expected = (TypeAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes);
- linked.Attributes.Should ().Be (expected, $"Type `{src}' pseudo attributes did not match expected");
+
+ if(!linked.Attributes.Equals(expected))
+ {
+ yield return $"Type `{src}' pseudo attributes did not match expected";
+ }
}
- protected virtual void VerifyPseudoAttributes (FieldDefinition src, FieldDefinition linked)
+ protected virtual IEnumerable VerifyPseudoAttributes (FieldDefinition src, FieldDefinition linked)
{
var expected = (FieldAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes);
- linked.Attributes.Should ().Be (expected, $"Field `{src}' pseudo attributes did not match expected");
+ if(!linked.Attributes.Equals(expected))
+ {
+ yield return $"Field `{src}' pseudo attributes did not match expected";
+ }
}
- protected virtual void VerifyPseudoAttributes (PropertyDefinition src, PropertyDefinition linked)
+ protected virtual IEnumerable VerifyPseudoAttributes (PropertyDefinition src, PropertyDefinition linked)
{
var expected = (PropertyAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes);
- linked.Attributes.Should ().Be (expected, $"Property `{src}' pseudo attributes did not match expected");
+ if(!linked.Attributes.Equals(expected))
+ {
+ yield return $"Property `{src}' pseudo attributes did not match expected";
+ }
+
}
- protected virtual void VerifyPseudoAttributes (EventDefinition src, EventDefinition linked)
+ protected virtual IEnumerable VerifyPseudoAttributes (EventDefinition src, EventDefinition linked)
{
var expected = (EventAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes);
- linked.Attributes.Should ().Be (expected, $"Event `{src}' pseudo attributes did not match expected");
+ if(!linked.Attributes.Equals(expected))
+ {
+ yield return $"Event `{src}' pseudo attributes did not match expected";
+ }
}
- protected virtual void VerifyCustomAttributes (ICustomAttributeProvider src, ICustomAttributeProvider linked)
+ protected virtual IEnumerable VerifyCustomAttributes (ICustomAttributeProvider src, ICustomAttributeProvider linked)
{
var expectedAttrs = GetExpectedAttributes (src).ToList ();
var linkedAttrs = FilterLinkedAttributes (linked).ToList ();
- linkedAttrs.Should ().BeEquivalentTo (expectedAttrs, $"Custom attributes on `{src}' are not matching");
+ if(!linkedAttrs.SequenceEqual(expectedAttrs))
+ {
+ yield return $"Custom attributes on `{src}' are not matching";
+ }
}
- protected virtual void VerifySecurityAttributes (ICustomAttributeProvider src, ISecurityDeclarationProvider linked)
+ protected virtual IEnumerable VerifySecurityAttributes (ICustomAttributeProvider src, ISecurityDeclarationProvider linked)
{
var expectedAttrs = GetCustomAttributeCtorValues