Skip to content

Commit feff67d

Browse files
authored
JIT: Add some limits in physical promotion (#87729)
Add limits on how many fields we promote in each struct and how many total fields we promote. These limits are put in place to avoid pathological cases; we do not hit any of them over all of our SPMI collections (at least on win-x64). The limits were selected based on histograms of physical promotion stats over our collections. Here they are for some of the important collections: JitEnablePhysicalPromotion=1: benchmarks.run_pgo: ``` (Per context) How many fields are promoted: <= 0 ===> 1654 count ( 66% of total) 1 .. 1 ===> 206 count ( 75% of total) 2 .. 2 ===> 259 count ( 85% of total) 3 .. 3 ===> 127 count ( 90% of total) 4 .. 4 ===> 46 count ( 92% of total) 5 .. 5 ===> 30 count ( 93% of total) 6 .. 10 ===> 99 count ( 97% of total) 11 .. 15 ===> 17 count ( 98% of total) 16 .. 20 ===> 21 count ( 99% of total) 21 .. 30 ===> 16 count ( 99% of total) 31 .. 40 ===> 3 count (100% of total) 41 .. 50 ===> 0 count (100% of total) 51 .. 100 ===> 0 count (100% of total) 101 .. 150 ===> 0 count (100% of total) 151 .. 200 ===> 0 count (100% of total) 201 .. 300 ===> 0 count (100% of total) 301 .. 400 ===> 0 count (100% of total) 401 .. 500 ===> 0 count (100% of total) 501 .. 700 ===> 0 count (100% of total) 701 .. 1000 ===> 0 count (100% of total) 1001 .. 2000 ===> 0 count (100% of total) 2001 .. 3000 ===> 0 count (100% of total) 3001 .. 4000 ===> 0 count (100% of total) 4001 .. 5000 ===> 0 count (100% of total) (Per struct) How many fields are promoted: <= 0 ===> 980 count ( 39% of total) 1 .. 1 ===> 760 count ( 69% of total) 2 .. 2 ===> 423 count ( 86% of total) 3 .. 3 ===> 73 count ( 89% of total) 4 .. 4 ===> 30 count ( 90% of total) 5 .. 5 ===> 46 count ( 92% of total) 6 .. 6 ===> 181 count ( 99% of total) 7 .. 8 ===> 2 count (100% of total) 9 .. 10 ===> 0 count (100% of total) 11 .. 15 ===> 0 count (100% of total) 16 .. 20 ===> 0 count (100% of total) 21 .. 30 ===> 0 count (100% of total) 31 .. 50 ===> 0 count (100% of total) 51 .. 100 ===> 0 count (100% of total) 101 .. 200 ===> 0 count (100% of total) 201 .. 400 ===> 0 count (100% of total) ``` libraries.pmi: ``` (Per context) How many fields are promoted: <= 0 ===> 19677 count ( 76% of total) 1 .. 1 ===> 1717 count ( 83% of total) 2 .. 2 ===> 899 count ( 86% of total) 3 .. 3 ===> 616 count ( 89% of total) 4 .. 4 ===> 660 count ( 91% of total) 5 .. 5 ===> 285 count ( 92% of total) 6 .. 10 ===> 932 count ( 96% of total) 11 .. 15 ===> 377 count ( 98% of total) 16 .. 20 ===> 164 count ( 98% of total) 21 .. 30 ===> 165 count ( 99% of total) 31 .. 40 ===> 68 count ( 99% of total) 41 .. 50 ===> 28 count ( 99% of total) 51 .. 100 ===> 51 count ( 99% of total) 101 .. 150 ===> 14 count ( 99% of total) 151 .. 200 ===> 2 count ( 99% of total) 201 .. 300 ===> 0 count ( 99% of total) 301 .. 400 ===> 1 count (100% of total) 401 .. 500 ===> 0 count (100% of total) 501 .. 700 ===> 0 count (100% of total) 701 .. 1000 ===> 0 count (100% of total) 1001 .. 2000 ===> 0 count (100% of total) 2001 .. 3000 ===> 0 count (100% of total) 3001 .. 4000 ===> 0 count (100% of total) 4001 .. 5000 ===> 0 count (100% of total) (Per struct) How many fields are promoted: <= 0 ===> 6218 count ( 31% of total) 1 .. 1 ===> 5075 count ( 57% of total) 2 .. 2 ===> 1440 count ( 65% of total) 3 .. 3 ===> 1859 count ( 74% of total) 4 .. 4 ===> 2682 count ( 88% of total) 5 .. 5 ===> 974 count ( 93% of total) 6 .. 6 ===> 624 count ( 96% of total) 7 .. 8 ===> 533 count ( 99% of total) 9 .. 10 ===> 37 count ( 99% of total) 11 .. 15 ===> 65 count ( 99% of total) 16 .. 20 ===> 2 count ( 99% of total) 21 .. 30 ===> 0 count ( 99% of total) 31 .. 50 ===> 1 count (100% of total) 51 .. 100 ===> 0 count (100% of total) 101 .. 200 ===> 0 count (100% of total) 201 .. 400 ===> 0 count (100% of total) ``` realworld: ``` (Per context) How many fields are promoted: <= 0 ===> 1807 count ( 54% of total) 1 .. 1 ===> 407 count ( 66% of total) 2 .. 2 ===> 230 count ( 73% of total) 3 .. 3 ===> 146 count ( 77% of total) 4 .. 4 ===> 122 count ( 81% of total) 5 .. 5 ===> 86 count ( 83% of total) 6 .. 10 ===> 218 count ( 90% of total) 11 .. 15 ===> 147 count ( 94% of total) 16 .. 20 ===> 59 count ( 96% of total) 21 .. 30 ===> 66 count ( 98% of total) 31 .. 40 ===> 19 count ( 98% of total) 41 .. 50 ===> 12 count ( 99% of total) 51 .. 100 ===> 14 count ( 99% of total) 101 .. 150 ===> 3 count ( 99% of total) 151 .. 200 ===> 2 count ( 99% of total) 201 .. 300 ===> 4 count ( 99% of total) 301 .. 400 ===> 0 count ( 99% of total) 401 .. 500 ===> 0 count ( 99% of total) 501 .. 700 ===> 2 count (100% of total) 701 .. 1000 ===> 0 count (100% of total) 1001 .. 2000 ===> 0 count (100% of total) 2001 .. 3000 ===> 0 count (100% of total) 3001 .. 4000 ===> 0 count (100% of total) 4001 .. 5000 ===> 0 count (100% of total) (Per struct) How many fields are promoted: <= 0 ===> 2012 count ( 32% of total) 1 .. 1 ===> 1421 count ( 55% of total) 2 .. 2 ===> 499 count ( 63% of total) 3 .. 3 ===> 521 count ( 71% of total) 4 .. 4 ===> 565 count ( 80% of total) 5 .. 5 ===> 446 count ( 87% of total) 6 .. 6 ===> 503 count ( 96% of total) 7 .. 8 ===> 139 count ( 98% of total) 9 .. 10 ===> 71 count ( 99% of total) 11 .. 15 ===> 34 count ( 99% of total) 16 .. 20 ===> 3 count (100% of total) 21 .. 30 ===> 0 count (100% of total) 31 .. 50 ===> 0 count (100% of total) 51 .. 100 ===> 0 count (100% of total) 101 .. 200 ===> 0 count (100% of total) 201 .. 400 ===> 0 count (100% of total) ``` JitEnablePhysicalPromotion=1;JitStressModeNames=STRESS_NO_OLD_PROMOTION: benchmarks.run_pgo: ``` (Per context) How many fields are promoted: <= 0 ===> 1654 count ( 66% of total) 1 .. 1 ===> 206 count ( 75% of total) 2 .. 2 ===> 259 count ( 85% of total) 3 .. 3 ===> 127 count ( 90% of total) 4 .. 4 ===> 46 count ( 92% of total) 5 .. 5 ===> 30 count ( 93% of total) 6 .. 10 ===> 99 count ( 97% of total) 11 .. 15 ===> 17 count ( 98% of total) 16 .. 20 ===> 21 count ( 99% of total) 21 .. 30 ===> 16 count ( 99% of total) 31 .. 40 ===> 3 count (100% of total) 41 .. 50 ===> 0 count (100% of total) 51 .. 100 ===> 0 count (100% of total) 101 .. 150 ===> 0 count (100% of total) 151 .. 200 ===> 0 count (100% of total) 201 .. 300 ===> 0 count (100% of total) 301 .. 400 ===> 0 count (100% of total) 401 .. 500 ===> 0 count (100% of total) 501 .. 700 ===> 0 count (100% of total) 701 .. 1000 ===> 0 count (100% of total) 1001 .. 2000 ===> 0 count (100% of total) 2001 .. 3000 ===> 0 count (100% of total) 3001 .. 4000 ===> 0 count (100% of total) 4001 .. 5000 ===> 0 count (100% of total) (Per struct) How many fields are promoted: <= 0 ===> 980 count ( 39% of total) 1 .. 1 ===> 760 count ( 69% of total) 2 .. 2 ===> 423 count ( 86% of total) 3 .. 3 ===> 73 count ( 89% of total) 4 .. 4 ===> 30 count ( 90% of total) 5 .. 5 ===> 46 count ( 92% of total) 6 .. 6 ===> 181 count ( 99% of total) 7 .. 8 ===> 2 count (100% of total) 9 .. 10 ===> 0 count (100% of total) 11 .. 15 ===> 0 count (100% of total) 16 .. 20 ===> 0 count (100% of total) 21 .. 30 ===> 0 count (100% of total) 31 .. 50 ===> 0 count (100% of total) 51 .. 100 ===> 0 count (100% of total) 101 .. 200 ===> 0 count (100% of total) 201 .. 400 ===> 0 count (100% of total) ``` libraries.pmi: ``` (Per context) How many fields are promoted: <= 0 ===> 37477 count ( 48% of total) 1 .. 1 ===> 7987 count ( 58% of total) 2 .. 2 ===> 7292 count ( 68% of total) 3 .. 3 ===> 2988 count ( 72% of total) 4 .. 4 ===> 4927 count ( 78% of total) 5 .. 5 ===> 1835 count ( 81% of total) 6 .. 10 ===> 7147 count ( 90% of total) 11 .. 15 ===> 2694 count ( 93% of total) 16 .. 20 ===> 1667 count ( 95% of total) 21 .. 30 ===> 1346 count ( 97% of total) 31 .. 40 ===> 758 count ( 98% of total) 41 .. 50 ===> 335 count ( 99% of total) 51 .. 100 ===> 510 count ( 99% of total) 101 .. 150 ===> 107 count ( 99% of total) 151 .. 200 ===> 34 count ( 99% of total) 201 .. 300 ===> 12 count ( 99% of total) 301 .. 400 ===> 2 count ( 99% of total) 401 .. 500 ===> 2 count (100% of total) 501 .. 700 ===> 0 count (100% of total) 701 .. 1000 ===> 0 count (100% of total) 1001 .. 2000 ===> 0 count (100% of total) 2001 .. 3000 ===> 0 count (100% of total) 3001 .. 4000 ===> 0 count (100% of total) 4001 .. 5000 ===> 0 count (100% of total) (Per struct) How many fields are promoted: <= 0 ===> 102669 count ( 39% of total) 1 .. 1 ===> 57467 count ( 61% of total) 2 .. 2 ===> 69010 count ( 88% of total) 3 .. 3 ===> 11494 count ( 92% of total) 4 .. 4 ===> 16887 count ( 99% of total) 5 .. 5 ===> 1043 count ( 99% of total) 6 .. 6 ===> 618 count ( 99% of total) 7 .. 8 ===> 579 count ( 99% of total) 9 .. 10 ===> 57 count ( 99% of total) 11 .. 15 ===> 71 count ( 99% of total) 16 .. 20 ===> 5 count ( 99% of total) 21 .. 30 ===> 0 count ( 99% of total) 31 .. 50 ===> 1 count (100% of total) 51 .. 100 ===> 0 count (100% of total) 101 .. 200 ===> 0 count (100% of total) 201 .. 400 ===> 0 count (100% of total) ``` realworld: ``` (Per context) How many fields are promoted: <= 0 ===> 5617 count ( 41% of total) 1 .. 1 ===> 1657 count ( 53% of total) 2 .. 2 ===> 1164 count ( 62% of total) 3 .. 3 ===> 522 count ( 66% of total) 4 .. 4 ===> 878 count ( 72% of total) 5 .. 5 ===> 472 count ( 76% of total) 6 .. 10 ===> 1383 count ( 86% of total) 11 .. 15 ===> 619 count ( 91% of total) 16 .. 20 ===> 335 count ( 93% of total) 21 .. 30 ===> 361 count ( 96% of total) 31 .. 40 ===> 165 count ( 97% of total) 41 .. 50 ===> 101 count ( 98% of total) 51 .. 100 ===> 177 count ( 99% of total) 101 .. 150 ===> 38 count ( 99% of total) 151 .. 200 ===> 18 count ( 99% of total) 201 .. 300 ===> 12 count ( 99% of total) 301 .. 400 ===> 1 count ( 99% of total) 401 .. 500 ===> 1 count ( 99% of total) 501 .. 700 ===> 2 count (100% of total) 701 .. 1000 ===> 0 count (100% of total) 1001 .. 2000 ===> 0 count (100% of total) 2001 .. 3000 ===> 0 count (100% of total) 3001 .. 4000 ===> 0 count (100% of total) 4001 .. 5000 ===> 0 count (100% of total) (Per struct) How many fields are promoted: <= 0 ===> 23630 count ( 37% of total) 1 .. 1 ===> 13267 count ( 59% of total) 2 .. 2 ===> 16731 count ( 86% of total) 3 .. 3 ===> 4677 count ( 93% of total) 4 .. 4 ===> 2727 count ( 97% of total) 5 .. 5 ===> 492 count ( 98% of total) 6 .. 6 ===> 496 count ( 99% of total) 7 .. 8 ===> 150 count ( 99% of total) 9 .. 10 ===> 92 count ( 99% of total) 11 .. 15 ===> 41 count ( 99% of total) 16 .. 20 ===> 3 count ( 99% of total) 21 .. 30 ===> 3 count ( 99% of total) 31 .. 50 ===> 3 count (100% of total) 51 .. 100 ===> 0 count (100% of total) 101 .. 200 ===> 0 count (100% of total) 201 .. 400 ===> 0 count (100% of total) ```
1 parent e3127c6 commit feff67d

File tree

2 files changed

+95
-33
lines changed

2 files changed

+95
-33
lines changed

src/coreclr/jit/promotion.cpp

Lines changed: 88 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -382,16 +382,22 @@ class LocalUses
382382
// lclNum - Local num for this struct local
383383
// aggregateInfo - [out] Pointer to aggregate info to create and insert replacements into.
384384
//
385-
void PickPromotions(Compiler* comp, unsigned lclNum, AggregateInfo** aggregateInfo)
385+
// Returns:
386+
// Number of promotions picked.
387+
//
388+
int PickPromotions(Compiler* comp, unsigned lclNum, AggregateInfo** aggregateInfo)
386389
{
387390
if (m_accesses.size() <= 0)
388391
{
389-
return;
392+
return 0;
390393
}
391394

395+
AggregateInfo*& agg = *aggregateInfo;
396+
392397
JITDUMP("Picking promotions for V%02u\n", lclNum);
393398

394-
assert(*aggregateInfo == nullptr);
399+
assert(agg == nullptr);
400+
int numReps = 0;
395401
for (size_t i = 0; i < m_accesses.size(); i++)
396402
{
397403
const Access& access = m_accesses[i];
@@ -406,15 +412,24 @@ class LocalUses
406412
continue;
407413
}
408414

409-
if (*aggregateInfo == nullptr)
415+
if (agg == nullptr)
410416
{
411-
*aggregateInfo = new (comp, CMK_Promotion) AggregateInfo(comp->getAllocator(CMK_Promotion), lclNum);
417+
agg = new (comp, CMK_Promotion) AggregateInfo(comp->getAllocator(CMK_Promotion), lclNum);
412418
}
413419

414-
(*aggregateInfo)->Replacements.push_back(Replacement(access.Offset, access.AccessType));
420+
agg->Replacements.push_back(Replacement(access.Offset, access.AccessType));
421+
numReps++;
422+
423+
if (agg->Replacements.size() >= PHYSICAL_PROMOTION_MAX_PROMOTIONS_PER_STRUCT)
424+
{
425+
JITDUMP(" Promoted %zu fields in V%02u; will not promote more\n", agg->Replacements.size(),
426+
agg->LclNum);
427+
break;
428+
}
415429
}
416430

417431
JITDUMP("\n");
432+
return numReps;
418433
}
419434

420435
//------------------------------------------------------------------------
@@ -427,14 +442,24 @@ class LocalUses
427442
// lclNum - Local num for this struct local
428443
// aggregateInfo - [out] Pointer to aggregate info to create and insert replacements into.
429444
//
430-
bool PickInducedPromotions(Compiler* comp, unsigned lclNum, AggregateInfo** aggregateInfo)
445+
// Returns:
446+
// Number of new promotions.
447+
//
448+
int PickInducedPromotions(Compiler* comp, unsigned lclNum, AggregateInfo** aggregateInfo)
431449
{
432450
if (m_inducedAccesses.size() <= 0)
433451
{
434-
return false;
452+
return 0;
453+
}
454+
455+
AggregateInfo*& agg = *aggregateInfo;
456+
457+
if ((agg != nullptr) && (agg->Replacements.size() >= PHYSICAL_PROMOTION_MAX_PROMOTIONS_PER_STRUCT))
458+
{
459+
return 0;
435460
}
436461

437-
bool any = false;
462+
int numReps = 0;
438463
JITDUMP("Picking induced promotions for V%02u\n", lclNum);
439464
for (PrimitiveAccess& inducedAccess : m_inducedAccesses)
440465
{
@@ -483,24 +508,22 @@ class LocalUses
483508
}
484509
}
485510

486-
if (*aggregateInfo == nullptr)
511+
if (agg == nullptr)
487512
{
488-
*aggregateInfo = new (comp, CMK_Promotion) AggregateInfo(comp->getAllocator(CMK_Promotion), lclNum);
513+
agg = new (comp, CMK_Promotion) AggregateInfo(comp->getAllocator(CMK_Promotion), lclNum);
489514
}
490515

491516
size_t insertionIndex;
492-
if ((*aggregateInfo)->Replacements.size() > 0)
517+
if (agg->Replacements.size() > 0)
493518
{
494519
#ifdef DEBUG
495520
Replacement* overlapRep;
496-
assert(!(*aggregateInfo)
497-
->OverlappingReplacements(inducedAccess.Offset, genTypeSize(inducedAccess.AccessType),
498-
&overlapRep, nullptr));
521+
assert(!agg->OverlappingReplacements(inducedAccess.Offset, genTypeSize(inducedAccess.AccessType),
522+
&overlapRep, nullptr));
499523
#endif
500524

501525
insertionIndex =
502-
Promotion::BinarySearch<Replacement, &Replacement::Offset>((*aggregateInfo)->Replacements,
503-
inducedAccess.Offset);
526+
Promotion::BinarySearch<Replacement, &Replacement::Offset>(agg->Replacements, inducedAccess.Offset);
504527
assert((ssize_t)insertionIndex < 0);
505528
insertionIndex = ~insertionIndex;
506529
}
@@ -509,13 +532,18 @@ class LocalUses
509532
insertionIndex = 0;
510533
}
511534

512-
(*aggregateInfo)
513-
->Replacements.insert((*aggregateInfo)->Replacements.begin() + insertionIndex,
514-
Replacement(inducedAccess.Offset, inducedAccess.AccessType));
515-
any = true;
535+
agg->Replacements.insert(agg->Replacements.begin() + insertionIndex,
536+
Replacement(inducedAccess.Offset, inducedAccess.AccessType));
537+
numReps++;
538+
539+
if (agg->Replacements.size() >= PHYSICAL_PROMOTION_MAX_PROMOTIONS_PER_STRUCT)
540+
{
541+
JITDUMP(" Promoted %zu fields in V%02u; will not promote more\n", agg->Replacements.size());
542+
break;
543+
}
516544
}
517545

518-
return any;
546+
return numReps;
519547
}
520548

521549
//------------------------------------------------------------------------
@@ -897,7 +925,21 @@ class LocalsUseVisitor : public GenTreeVisitor<LocalsUseVisitor>
897925
unsigned numLocals = (unsigned)aggregates.size();
898926
JITDUMP("Picking promotions\n");
899927

900-
bool any = false;
928+
int totalNumPromotions = 0;
929+
// We limit the total number of promotions picked based on the tracking
930+
// limit to avoid blowup in the superlinear liveness computation in
931+
// pathological cases, and also because once we stop tracking the fields there is no benefit anymore.
932+
//
933+
// This logic could be improved by the use of ref counting to pick the
934+
// smart fields to compute liveness for, but as of writing this there
935+
// is no example in the built-in SPMI collections that hits this limit.
936+
//
937+
// Note that we may go slightly over this as once we start picking
938+
// replacement locals for a single struct we do not stop until we get
939+
// to the next struct, but PHYSICAL_PROMOTION_MAX_PROMOTIONS_PER_STRUCT
940+
// puts a limit on the number of promotions in each struct so this is
941+
// fine to avoid the pathological cases.
942+
const int maxTotalNumPromotions = JitConfig.JitMaxLocalsToTrack();
901943

902944
for (unsigned lclNum = 0; lclNum < numLocals; lclNum++)
903945
{
@@ -914,17 +956,22 @@ class LocalsUseVisitor : public GenTreeVisitor<LocalsUseVisitor>
914956
}
915957
#endif
916958

917-
uses->PickPromotions(m_compiler, lclNum, &aggregates[lclNum]);
959+
totalNumPromotions += uses->PickPromotions(m_compiler, lclNum, &aggregates[lclNum]);
918960

919-
any |= aggregates[lclNum] != nullptr;
961+
if (totalNumPromotions >= maxTotalNumPromotions)
962+
{
963+
JITDUMP("Promoted %d fields which is over our limit of %d; will not promote more\n", totalNumPromotions,
964+
maxTotalNumPromotions);
965+
break;
966+
}
920967
}
921968

922-
if (!any)
969+
if (totalNumPromotions <= 0)
923970
{
924971
return false;
925972
}
926973

927-
if (m_candidateStores.Height() > 0)
974+
if ((m_candidateStores.Height() > 0) && (totalNumPromotions < maxTotalNumPromotions))
928975
{
929976
// Now look for induced accesses due to assignment decomposition.
930977

@@ -974,7 +1021,7 @@ class LocalsUseVisitor : public GenTreeVisitor<LocalsUseVisitor>
9741021
}
9751022
}
9761023

977-
bool any = false;
1024+
bool again = false;
9781025
for (unsigned lclNum = 0; lclNum < numLocals; lclNum++)
9791026
{
9801027
LocalUses* uses = m_uses[lclNum];
@@ -989,10 +1036,20 @@ class LocalsUseVisitor : public GenTreeVisitor<LocalsUseVisitor>
9891036
}
9901037
#endif
9911038

992-
any |= uses->PickInducedPromotions(m_compiler, lclNum, &aggregates[lclNum]);
1039+
int numInducedProms = uses->PickInducedPromotions(m_compiler, lclNum, &aggregates[lclNum]);
1040+
again |= numInducedProms > 0;
1041+
1042+
totalNumPromotions += numInducedProms;
1043+
if (totalNumPromotions >= maxTotalNumPromotions)
1044+
{
1045+
JITDUMP("Promoted %d fields and our limit is %d; will not promote more\n", totalNumPromotions,
1046+
maxTotalNumPromotions);
1047+
again = false;
1048+
break;
1049+
}
9931050
}
9941051

995-
if (!any)
1052+
if (!again)
9961053
{
9971054
break;
9981055
}
@@ -1067,11 +1124,9 @@ class LocalsUseVisitor : public GenTreeVisitor<LocalsUseVisitor>
10671124
{
10681125
// Aggregate is fully promoted, leave UnpromotedMin == UnpromotedMax to indicate this.
10691126
}
1070-
1071-
any = true;
10721127
}
10731128

1074-
return any;
1129+
return totalNumPromotions > 0;
10751130
}
10761131

10771132
private:

src/coreclr/jit/promotion.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
#include "compiler.h"
88
#include "vector.h"
99

10+
// We limit the max number of fields that can be promoted in a single struct to
11+
// avoid pathological cases (e.g. machine generated code). Furthermore,
12+
// writebacks before struct uses introduce commas with nested trees for each
13+
// field written back, so without a limit we could create arbitrarily deep
14+
// trees.
15+
const int PHYSICAL_PROMOTION_MAX_PROMOTIONS_PER_STRUCT = 64;
16+
1017
// Represents a single replacement of a (field) access into a struct local.
1118
struct Replacement
1219
{

0 commit comments

Comments
 (0)