Skip to content

Commit bfeafb6

Browse files
authored
Merge pull request #33376 from meg-gupta/fixallocboxtostack
Fix edgecase in AllocBoxToStack handling of local apply
2 parents ccd82a0 + 4d4af13 commit bfeafb6

File tree

3 files changed

+236
-15
lines changed

3 files changed

+236
-15
lines changed

lib/SILOptimizer/Transforms/AllocBoxToStack.cpp

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#define DEBUG_TYPE "allocbox-to-stack"
1414
#include "swift/AST/DiagnosticsSIL.h"
15+
#include "swift/Basic/BlotMapVector.h"
1516
#include "swift/SIL/ApplySite.h"
1617
#include "swift/SIL/Dominance.h"
1718
#include "swift/SIL/SILArgument.h"
@@ -43,6 +44,10 @@ static llvm::cl::opt<unsigned> MaxLocalApplyRecurDepth(
4344
"max-local-apply-recur-depth", llvm::cl::init(4),
4445
llvm::cl::desc("Max recursive depth for analyzing local functions"));
4546

47+
static llvm::cl::opt<bool> AllocBoxToStackAnalyzeApply(
48+
"allocbox-to-stack-analyze-apply", llvm::cl::init(true),
49+
llvm::cl::desc("Analyze functions into while alloc_box is passed"));
50+
4651
//===-----------------------------------------------------------------------===//
4752
// SIL Utilities for alloc_box Promotion
4853
//===----------------------------------------------------------------------===//
@@ -319,6 +324,10 @@ static bool checkLocalApplyBody(Operand *O,
319324
// AllocBoxToStack opt. We don't want to increase code size, so this is
320325
// restricted only for private local functions currently.
321326
static bool isOptimizableApplySite(ApplySite Apply) {
327+
if (!AllocBoxToStackAnalyzeApply) {
328+
// turned off explicitly
329+
return false;
330+
}
322331
auto callee = Apply.getReferencedFunctionOrNull();
323332
if (!callee) {
324333
return false;
@@ -978,8 +987,7 @@ specializeApplySite(SILOptFunctionBuilder &FuncBuilder, ApplySite Apply,
978987
}
979988

980989
static void rewriteApplySites(AllocBoxToStackState &pass) {
981-
llvm::DenseMap<ApplySite, ArgIndexList> IndexMap;
982-
llvm::SmallVector<ApplySite, 8> AppliesToSpecialize;
990+
swift::SmallBlotMapVector<ApplySite, ArgIndexList, 8> AppliesToSpecialize;
983991
ArgIndexList Indices;
984992

985993
// Build a map from the ApplySite to the indices of the operands
@@ -993,29 +1001,39 @@ static void rewriteApplySites(AllocBoxToStackState &pass) {
9931001
Indices.clear();
9941002
Indices.push_back(CalleeArgIndexNumber);
9951003

996-
auto iterAndSuccess = IndexMap.try_emplace(Apply, Indices);
9971004
// AllocBoxStack opt promotes boxes passed to a chain of applies when it is
9981005
// safe to do so. All such applies have to be specialized to take pointer
9991006
// arguments instead of box arguments. This has to be done in dfs order.
1000-
// Since PromotedOperands is already populated in dfs order by
1001-
// `recursivelyFindBoxOperandsPromotableToAddress`. Build
1002-
// `AppliesToSpecialize` in the same order.
1003-
if (iterAndSuccess.second) {
1004-
AppliesToSpecialize.push_back(Apply);
1005-
} else {
1006-
iterAndSuccess.first->second.push_back(CalleeArgIndexNumber);
1007+
1008+
// PromotedOperands is already populated in dfs order by
1009+
// `recursivelyFindBoxOperandsPromotableToAddress` w.r.t a single alloc_box.
1010+
// AppliesToSpecialize is then populated in the order of PromotedOperands.
1011+
// If multiple alloc_boxes are passed to the same apply instruction, then
1012+
// the apply instruction can appear multiple times in AppliesToSpecialize.
1013+
// Only its last appearance is maintained and previous appearances are
1014+
// blotted.
1015+
auto iterAndSuccess =
1016+
AppliesToSpecialize.insert(std::make_pair(Apply, Indices));
1017+
if (!iterAndSuccess.second) {
1018+
// Blot the previously inserted apply and insert at the end with updated
1019+
// indices
1020+
auto OldIndices = iterAndSuccess.first->getValue().second;
1021+
OldIndices.push_back(CalleeArgIndexNumber);
1022+
AppliesToSpecialize.erase(iterAndSuccess.first);
1023+
AppliesToSpecialize.insert(std::make_pair(Apply, OldIndices));
10071024
}
10081025
}
10091026

10101027
// Clone the referenced function of each ApplySite, removing the
10111028
// operands that we will not need, and remove the existing
10121029
// ApplySite.
10131030
SILOptFunctionBuilder FuncBuilder(*pass.T);
1014-
for (auto &Apply : AppliesToSpecialize) {
1015-
auto It = IndexMap.find(Apply);
1016-
assert(It != IndexMap.end());
1017-
auto &Indices = It->second;
1018-
1031+
for (auto &It : AppliesToSpecialize) {
1032+
if (!It.hasValue()) {
1033+
continue;
1034+
}
1035+
auto Apply = It.getValue().first;
1036+
auto Indices = It.getValue().second;
10191037
// Sort the indices and unique them.
10201038
sortUnique(Indices);
10211039

test/SILOptimizer/allocboxtostack_localapply.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,67 @@ public func testrecur() -> Int {
118118
}
119119
return bas() + bar()
120120
}
121+
122+
// Test to make sure AppliesToSpecialize in AllocBoxToStack is populated correctly when there are common function calls for mutiple allox_boxes.
123+
// Order of function calls constructed in PromotedOperands: bar common bas common.
124+
// AppliesToSpecialize should have the order: bar bas common.
125+
// Only then, the functions get specialized correctly, and we won't see an assert in checkNoPromotedBoxInApply.
126+
// CHECK-LABEL: sil [noinline] @$s26allocboxtostack_localapply8testdfs1SiyF :
127+
// CHECK-NOT : alloc_box ${ var Int }, var, name "x"
128+
// CHECK-NOT : alloc_box ${ var Int }, var, name "y"
129+
// CHECK-LABEL:} // end sil function '$s26allocboxtostack_localapply8testdfs1SiyF'
130+
@inline(never)
131+
public func testdfs1() -> Int {
132+
var x = 0
133+
var y = 0
134+
@inline(never)
135+
func bar() -> Int {
136+
return x
137+
}
138+
@inline(never)
139+
func bas() -> Int {
140+
return y
141+
}
142+
@inline(never)
143+
func common() -> Int {
144+
return bar() + bas()
145+
}
146+
return common()
147+
}
148+
149+
// Test to make sure we don't optimize the case when we have an inner common function call for multiple boxes.
150+
// We don't optimize this case now, because we don't have additional logic to correctly construct AppliesToSpecialize
151+
// Order of function calls constructed in PromotedOperands: bar innercommon local1 bas innercommon local2
152+
// AppliesToSpecialize should have the order: bar bas innercommon local1 local2
153+
// Since we don't maintain any tree like data structure with more info on the call tree, this is not possible to contruct today
154+
// CHECK-LABEL: sil [noinline] @$s26allocboxtostack_localapply8testdfs2SiyF :
155+
// CHECK: alloc_box ${ var Int }, var, name "x"
156+
// CHECK: alloc_box ${ var Int }, var, name "y"
157+
// CHECK-LABEL:} // end sil function '$s26allocboxtostack_localapply8testdfs2SiyF'
158+
@inline(never)
159+
public func testdfs2() -> Int {
160+
var x = 0
161+
var y = 0
162+
@inline(never)
163+
func bar() -> Int {
164+
return x
165+
}
166+
@inline(never)
167+
func bas() -> Int {
168+
return y
169+
}
170+
@inline(never)
171+
func innercommon() -> Int {
172+
return bar() + bas()
173+
}
174+
@inline(never)
175+
func local1() -> Int {
176+
return innercommon()
177+
}
178+
@inline(never)
179+
func local2() -> Int {
180+
return innercommon()
181+
}
182+
return local1() + local2()
183+
}
184+

test/SILOptimizer/allocboxtostack_localapply_ossa.sil

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,142 @@ bb2:
349349
unwind
350350
}
351351

352+
struct Int {
353+
var _value: Builtin.Int64
354+
}
355+
356+
// Test to make sure AppliesToSpecialize in AllocBoxToStack is populated correctly when there are common function calls for mutiple allox_boxes.
357+
// Order of function calls constructed in PromotedOperands: bar common bas common.
358+
// AppliesToSpecialize should have the order: bar bas common.
359+
// Only then, the functions get specialized correctly, and we won't see an assert in checkNoPromotedBoxInApply.
360+
// CHECK-LABEL: sil [noinline] [ossa] @$testdfs1 :
361+
// CHECK-NOT : alloc_box ${ var Int64 }, var, name "x"
362+
// CHECK-NOT : alloc_box ${ var Int64 }, var, name "y"
363+
// CHECK-LABEL:} // end sil function '$testdfs1'
364+
sil [noinline] [ossa] @$testdfs1 : $@convention(thin) () -> Int64 {
365+
bb0:
366+
%0 = alloc_box ${ var Int64 }, var, name "x"
367+
%1 = project_box %0 : ${ var Int64 }, 0
368+
%2 = integer_literal $Builtin.Int64, 0
369+
%3 = struct $Int64 (%2 : $Builtin.Int64)
370+
store %3 to [trivial] %1 : $*Int64
371+
%5 = alloc_box ${ var Int64 }, var, name "y"
372+
%6 = project_box %5 : ${ var Int64 }, 0
373+
%7 = integer_literal $Builtin.Int64, 0
374+
%8 = struct $Int64 (%7 : $Builtin.Int64)
375+
store %8 to [trivial] %6 : $*Int64
376+
%10 = function_ref @$testdfs1common : $@convention(thin) (@guaranteed { var Int64 }, @guaranteed { var Int64 }) -> Int64
377+
%11 = apply %10(%0, %5) : $@convention(thin) (@guaranteed { var Int64 }, @guaranteed { var Int64 }) -> Int64
378+
destroy_value %5 : ${ var Int64 }
379+
destroy_value %0 : ${ var Int64 }
380+
return %11 : $Int64
381+
}
382+
383+
sil private [noinline] [ossa] @$testdfs1common : $@convention(thin) (@guaranteed { var Int64 }, @guaranteed { var Int64 }) -> Int64 {
384+
bb0(%0 : @guaranteed ${ var Int64 }, %1 : @guaranteed ${ var Int64 }):
385+
%proj1 = project_box %0 : ${ var Int64 }, 0
386+
%proj2 = project_box %1 : ${ var Int64 }, 0
387+
%barfunc = function_ref @$testdfs1bar : $@convention(thin) (@guaranteed { var Int64 }) -> Int64
388+
%res1 = apply %barfunc(%0) : $@convention(thin) (@guaranteed { var Int64 }) -> Int64
389+
%basfunc = function_ref @$testdfs1bas : $@convention(thin) (@guaranteed { var Int64 }) -> Int64
390+
%res2 = apply %basfunc(%1) : $@convention(thin) (@guaranteed { var Int64 }) -> Int64
391+
%func = function_ref @$blackhole : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
392+
%tmp1 = apply %func<Int64>(%proj1) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
393+
%tmp2 = apply %func<Int64>(%proj2) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
394+
%res = load [trivial] %proj1 : $*Int64
395+
return %res : $Int64
396+
}
397+
398+
sil private [noinline] [ossa] @$testdfs1bar : $@convention(thin) (@guaranteed { var Int64 }) -> Int64 {
399+
bb0(%0 : @guaranteed ${ var Int64 }):
400+
%1 = project_box %0 : ${ var Int64 }, 0
401+
%4 = load [trivial] %1 : $*Int64
402+
return %4 : $Int64
403+
}
404+
405+
sil private [noinline] [ossa] @$testdfs1bas : $@convention(thin) (@guaranteed { var Int64 }) -> Int64 {
406+
bb0(%0 : @guaranteed ${ var Int64 }):
407+
%1 = project_box %0 : ${ var Int64 }, 0
408+
%4 = load [trivial] %1 : $*Int64
409+
return %4 : $Int64
410+
}
411+
412+
// Test to make sure we don't optimize the case when we have an inner common function call for multiple boxes.
413+
// We don't optimize this case now, because we don't have additional logic to correctly construct AppliesToSpecialize
414+
// Order of function calls constructed in PromotedOperands: bar innercommon local1 bas innercommon local2
415+
// AppliesToSpecialize should have the order: bar bas innercommon local1 local2
416+
// Since we don't maintain any tree like data structure with more info on the call tree, this is not possible to contruct today
417+
// CHECK-LABEL: sil [noinline] [ossa] @$testdfs2 :
418+
// CHECK: alloc_box ${ var Int }, var, name "x"
419+
// CHECK: alloc_box ${ var Int }, var, name "y"
420+
// CHECK-LABEL:} // end sil function '$testdfs2'
421+
sil [noinline] [ossa] @$testdfs2 : $@convention(thin) () -> Int {
422+
bb0:
423+
%0 = alloc_box ${ var Int }, var, name "x"
424+
%1 = project_box %0 : ${ var Int }, 0
425+
%2 = integer_literal $Builtin.Int64, 0
426+
%3 = struct $Int (%2 : $Builtin.Int64)
427+
store %3 to [trivial] %1 : $*Int
428+
%5 = alloc_box ${ var Int }, var, name "y"
429+
%6 = project_box %5 : ${ var Int }, 0
430+
%7 = integer_literal $Builtin.Int64, 0
431+
%8 = struct $Int (%7 : $Builtin.Int64)
432+
store %8 to [trivial] %6 : $*Int
433+
%10 = function_ref @$testdfs2local1 : $@convention(thin) (@guaranteed { var Int }, @guaranteed { var Int }) -> Int
434+
%11 = apply %10(%0, %5) : $@convention(thin) (@guaranteed { var Int }, @guaranteed { var Int }) -> Int
435+
%12 = function_ref @$testdfs2local2 : $@convention(thin) (@guaranteed { var Int }, @guaranteed { var Int }) -> Int
436+
%13 = apply %12(%0, %5) : $@convention(thin) (@guaranteed { var Int }, @guaranteed { var Int }) -> Int
437+
%14 = struct_extract %11 : $Int, #Int._value
438+
%15 = struct_extract %13 : $Int, #Int._value
439+
%16 = integer_literal $Builtin.Int1, -1
440+
%17 = builtin "sadd_with_overflow_Int64"(%14 : $Builtin.Int64, %15 : $Builtin.Int64, %16 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1)
441+
(%18, %19) = destructure_tuple %17 : $(Builtin.Int64, Builtin.Int1)
442+
cond_fail %19 : $Builtin.Int1, "arithmetic overflow"
443+
%21 = struct $Int (%18 : $Builtin.Int64)
444+
destroy_value %5 : ${ var Int }
445+
destroy_value %0 : ${ var Int }
446+
return %21 : $Int
447+
}
448+
449+
sil private [noinline] [ossa] @$testdfs2bar : $@convention(thin) (@guaranteed { var Int }) -> Int {
450+
bb0(%0 : @guaranteed ${ var Int }):
451+
%1 = project_box %0 : ${ var Int }, 0
452+
%4 = load [trivial] %1 : $*Int
453+
return %4 : $Int
454+
}
455+
456+
sil private [noinline] [ossa] @$testdfs2bas : $@convention(thin) (@guaranteed { var Int }) -> Int {
457+
bb0(%0 : @guaranteed ${ var Int }):
458+
%1 = project_box %0 : ${ var Int }, 0
459+
%4 = load [trivial] %1 : $*Int
460+
return %4 : $Int
461+
}
462+
463+
sil private [noinline] [ossa] @$testdfs2innercommon : $@convention(thin) (@guaranteed { var Int }, @guaranteed { var Int }) -> Int {
464+
bb0(%0 : @guaranteed ${ var Int }, %1 : @guaranteed ${ var Int }):
465+
%2 = project_box %0 : ${ var Int }, 0
466+
%4 = project_box %1 : ${ var Int }, 0
467+
%8 = function_ref @$testdfs2bar : $@convention(thin) (@guaranteed { var Int }) -> Int
468+
%9 = apply %8(%0) : $@convention(thin) (@guaranteed { var Int }) -> Int
469+
%11 = function_ref @$testdfs2bas : $@convention(thin) (@guaranteed { var Int }) -> Int
470+
%12 = apply %11(%1) : $@convention(thin) (@guaranteed { var Int }) -> Int
471+
return %12 : $Int
472+
}
473+
474+
sil private [noinline] [ossa] @$testdfs2local1 : $@convention(thin) (@guaranteed { var Int }, @guaranteed { var Int }) -> Int {
475+
bb0(%0 : @guaranteed ${ var Int }, %1 : @guaranteed ${ var Int }):
476+
%2 = project_box %0 : ${ var Int }, 0
477+
%4 = project_box %1 : ${ var Int }, 0
478+
%7 = function_ref @$testdfs2innercommon : $@convention(thin) (@guaranteed { var Int }, @guaranteed { var Int }) -> Int
479+
%8 = apply %7(%0, %1) : $@convention(thin) (@guaranteed { var Int }, @guaranteed { var Int }) -> Int
480+
return %8 : $Int
481+
}
482+
483+
sil private [noinline] [ossa] @$testdfs2local2 : $@convention(thin) (@guaranteed { var Int }, @guaranteed { var Int }) -> Int {
484+
bb0(%0 : @guaranteed ${ var Int }, %1 : @guaranteed ${ var Int }):
485+
%2 = project_box %0 : ${ var Int }, 0
486+
%4 = project_box %1 : ${ var Int }, 0
487+
%7 = function_ref @$testdfs2innercommon : $@convention(thin) (@guaranteed { var Int }, @guaranteed { var Int }) -> Int
488+
%8 = apply %7(%0, %1) : $@convention(thin) (@guaranteed { var Int }, @guaranteed { var Int }) -> Int
489+
return %8 : $Int
490+
}

0 commit comments

Comments
 (0)