Files
swift-mirror/test/SILOptimizer/allocbox_to_stack_noncopyable.sil
Erik Eckstein 6714a72256 Optimizer: re-implement and improve the AllocBoxToStack pass
This pass replaces `alloc_box` with `alloc_stack` if the box is not escaping.
The original implementation had some limitations. It could not handle cases of local functions which are called multiple times or even recursively, e.g.

```
public func foo() -> Int {
  var i = 1

  func localFunction() { i += 1 }

  localFunction()
  localFunction()
  return i
}

```

The new implementation (done in Swift) fixes this problem with a new algorithm.
It's not only more powerful, but also simpler: the new pass has less than half lines of code than the old pass.

The pass is invoked in the mandatory pipeline and later in the optimizer pipeline.
The new implementation provides a module-pass for the mandatory pipeline (whereas the "regular" pass is a function pass).
This is required because the mandatory pass needs to remove originals of specialized closures, which cannot be done from a function-pass.
In the old implementation this was done with a hack by adding a semantic attribute and deleting the function later in the pipeline.

I still kept the sources of the old pass for being able to bootstrap the compiler without a host compiler.

rdar://142756547
2025-06-20 08:15:04 +02:00

195 lines
9.8 KiB
Plaintext

// RUN: %target-sil-opt -enable-sil-verify-all %s -allocbox-to-stack -enable-copy-propagation=requested-passes-only | %FileCheck %s
sil_stage raw
import Builtin
import Swift
struct NonTrivialStruct: ~Copyable {
var i: Builtin.Int32
}
sil @use_nontrivialstruct : $@convention(thin) (@guaranteed NonTrivialStruct) -> ()
sil @consume_nontrivialstruct : $@convention(thin) (@owned NonTrivialStruct) -> ()
sil @get_nontrivialstruct : $@convention(thin) () -> @owned NonTrivialStruct
sil [ossa] @host_markmustcheck_closure : $@convention(thin) (@guaranteed { var NonTrivialStruct }) -> () {
bb0(%0 : @closureCapture @guaranteed ${ var NonTrivialStruct }):
%9999 = tuple()
return %9999 : $()
}
// CHECK-LABEL: sil [ossa] @hoist_markmustcheck : $@convention(thin) (@owned NonTrivialStruct) -> () {
// CHECK: bb0([[ARG:%.*]] : @owned
// CHECK-NEXT: [[STACK1:%.*]] = alloc_stack $NonTrivialStruct
// CHECK-NEXT: [[MARKED1:%.*]] = mark_unresolved_non_copyable_value [consumable_and_assignable] [[STACK1]]
// CHECK-NEXT: store [[ARG]] to [init] [[MARKED1]]
// CHECK-NEXT: [[LOADED_VALUE:%.*]] = load [take] [[MARKED1]]
// CHECK-NEXT: [[STACK2:%.*]] = alloc_stack $NonTrivialStruct
// CHECK-NEXT: [[MARKED2:%.*]] = mark_unresolved_non_copyable_value [consumable_and_assignable] [[STACK2]]
// CHECK-NEXT: store [[LOADED_VALUE]] to [init] [[MARKED2]]
// CHECK: [[FUNC:%.*]] = function_ref @$s26host_markmustcheck_closureTf0s_n : $@convention(thin) (@inout_aliasable NonTrivialStruct) -> ()
// CHECK-NEXT: [[PA:%.*]] = partial_apply [callee_guaranteed] [[FUNC]]([[MARKED2]]) : $@convention(thin) (@inout_aliasable NonTrivialStruct) -> ()
// CHECK-NEXT: apply [[PA]]()
// CHECK-NEXT: destroy_value [[PA]]
// CHECK-NEXT: destroy_addr [[MARKED2]]
// CHECK-NEXT: dealloc_stack [[STACK2]]
// CHECK-NEXT: dealloc_stack [[STACK1]]
// CHECK-NEXT: tuple ()
// CHECK-NEXT: return
// CHECK: } // end sil function 'hoist_markmustcheck'
sil [ossa] @hoist_markmustcheck : $@convention(thin) (@owned NonTrivialStruct) -> () {
bb0(%0 : @owned $NonTrivialStruct):
%1 = alloc_box ${ var NonTrivialStruct }
%2 = project_box %1 : ${ var NonTrivialStruct }, 0
store %0 to [init] %2 : $*NonTrivialStruct
%3 = project_box %1 : ${ var NonTrivialStruct }, 0
%3a = mark_unresolved_non_copyable_value [assignable_but_not_consumable] %3 : $*NonTrivialStruct
%3b = load [take] %3a : $*NonTrivialStruct
%4 = alloc_box ${ var NonTrivialStruct }
%4a = project_box %4 : ${ var NonTrivialStruct }, 0
%4b = mark_unresolved_non_copyable_value [assignable_but_not_consumable] %4a : $*NonTrivialStruct
store %3b to [init] %4b : $*NonTrivialStruct
%f = function_ref @host_markmustcheck_closure : $@convention(thin) (@guaranteed { var NonTrivialStruct }) -> ()
%5c = copy_value %4 : ${ var NonTrivialStruct }
%g = partial_apply [callee_guaranteed] %f(%5c) : $@convention(thin) (@guaranteed { var NonTrivialStruct }) -> ()
apply %g() : $@callee_guaranteed () -> ()
destroy_value %g : $@callee_guaranteed () -> ()
destroy_value %4 : ${ var NonTrivialStruct }
dealloc_box %1 : ${ var NonTrivialStruct }
%9999 = tuple()
return %9999 : $()
}
// MARK: Test that we properly handle let alloc_box.
//
// CHECK-LABEL: sil hidden [ossa] @captured_let_allocstack_caller : $@convention(thin) () -> () {
// CHECK: bb0:
// CHECK: [[STACK:%.*]] = alloc_stack
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [consumable_and_assignable] [[STACK]]
// CHECK: partial_apply [callee_guaranteed] {{%.*}}([[MARK]]) : $@convention(thin) (@inout_aliasable NonTrivialStruct) -> ()
// CHECK: } // end sil function 'captured_let_allocstack_caller'
// CHECK-LABEL: sil private [ossa] @$s31captured_let_allocstack_closureTf0s_n : $@convention(thin) (@inout_aliasable NonTrivialStruct) -> () {
// CHECK: // [[ARG:.*]]{{ .*}}// user: [[MARK:%[0-9][0-9]*]]
// CHECK: bb0([[ARG]] : @closureCapture $*NonTrivialStruct):
// CHECK: [[MARK]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[ARG]]
// CHECK: [[LOAD:%.*]] = load [copy] [[MARK]]
// CHECK: [[VALUE:%.*]] = move_value [[LOAD]]
// CHECK: destroy_value [[VALUE]]
// CHECK: [[LOAD:%.*]] = load [copy] [[MARK]]
// CHECK: [[VALUE:%.*]] = move_value [[LOAD]]
// CHECK: destroy_value [[VALUE]]
// CHECK: cond_br undef, [[LHS:bb[0-9]+]], [[RHS:bb[0-9]+]]
//
// CHECK: [[LHS]]:
// CHECK: [[VALUE:%.*]] = load [copy] [[MARK]]
// CHECK: apply {{%.*}}([[VALUE]]) : $@convention(thin) (@owned NonTrivialStruct) -> ()
// CHECK: [[VALUE:%.*]] = load_borrow [[MARK]]
// CHECK: apply {{%.*}}([[VALUE]]) : $@convention(thin) (@guaranteed NonTrivialStruct) -> ()
// CHECK: } // end sil function 'captured_let_allocstack_closure'
sil hidden [ossa] @captured_let_allocstack_caller : $@convention(thin) () -> () {
bb0:
%0 = alloc_box ${ let NonTrivialStruct }, let, name "x"
%2 = function_ref @get_nontrivialstruct : $@convention(thin) () -> @owned NonTrivialStruct
%3 = apply %2() : $@convention(thin) () -> @owned NonTrivialStruct
%4 = project_box %0 : ${ let NonTrivialStruct }, 0
store %3 to [init] %4 : $*NonTrivialStruct
%6 = function_ref @captured_let_allocstack_closure : $@convention(thin) (@guaranteed { let NonTrivialStruct }) -> ()
%7 = project_box %0 : ${ let NonTrivialStruct }, 0
%8 = copy_value %0 : ${ let NonTrivialStruct }
mark_function_escape %7 : $*NonTrivialStruct
%10 = partial_apply [callee_guaranteed] %6(%8) : $@convention(thin) (@guaranteed { let NonTrivialStruct }) -> ()
%11 = begin_borrow [lexical] %10 : $@callee_guaranteed () -> ()
debug_value %11 : $@callee_guaranteed () -> (), let, name "f"
%13 = project_box %0 : ${ let NonTrivialStruct }, 0
%13a = mark_unresolved_non_copyable_value [assignable_but_not_consumable] %13 : $*NonTrivialStruct
%14 = load_borrow %13a : $*NonTrivialStruct
%15 = function_ref @use_nontrivialstruct : $@convention(thin) (@guaranteed NonTrivialStruct) -> ()
%16 = apply %15(%14) : $@convention(thin) (@guaranteed NonTrivialStruct) -> ()
end_borrow %14 : $NonTrivialStruct
%18 = project_box %0 : ${ let NonTrivialStruct }, 0
%18a = mark_unresolved_non_copyable_value [assignable_but_not_consumable] %18 : $*NonTrivialStruct
%19 = load [copy] %18a : $*NonTrivialStruct
%20 = function_ref @consume_nontrivialstruct : $@convention(thin) (@owned NonTrivialStruct) -> ()
%21 = apply %20(%19) : $@convention(thin) (@owned NonTrivialStruct) -> ()
end_borrow %11 : $@callee_guaranteed () -> ()
destroy_value %10 : $@callee_guaranteed () -> ()
destroy_value %0 : ${ let NonTrivialStruct }
%25 = tuple ()
return %25 : $()
}
sil private [ossa] @captured_let_allocstack_closure : $@convention(thin) (@guaranteed { let NonTrivialStruct }) -> () {
bb0(%0 : @closureCapture @guaranteed ${ let NonTrivialStruct }):
%1 = project_box %0 : ${ let NonTrivialStruct }, 0
%2 = mark_unresolved_non_copyable_value [assignable_but_not_consumable] %1 : $*NonTrivialStruct
%3 = load [copy] %2 : $*NonTrivialStruct
%4 = move_value %3 : $NonTrivialStruct
destroy_value %4 : $NonTrivialStruct
%6 = project_box %0 : ${ let NonTrivialStruct }, 0
%7 = mark_unresolved_non_copyable_value [assignable_but_not_consumable] %6 : $*NonTrivialStruct
%8 = load [copy] %7 : $*NonTrivialStruct
%9 = move_value %8 : $NonTrivialStruct
destroy_value %9 : $NonTrivialStruct
cond_br undef, bb1, bb2
bb1:
%17 = project_box %0 : ${ let NonTrivialStruct }, 0
%18 = mark_unresolved_non_copyable_value [assignable_but_not_consumable] %17 : $*NonTrivialStruct
%19 = load [copy] %18 : $*NonTrivialStruct
%20 = function_ref @consume_nontrivialstruct : $@convention(thin) (@owned NonTrivialStruct) -> ()
%21 = apply %20(%19) : $@convention(thin) (@owned NonTrivialStruct) -> ()
%22 = project_box %0 : ${ let NonTrivialStruct }, 0
%23 = mark_unresolved_non_copyable_value [assignable_but_not_consumable] %22 : $*NonTrivialStruct
%24 = load_borrow %23 : $*NonTrivialStruct
%25 = function_ref @use_nontrivialstruct : $@convention(thin) (@guaranteed NonTrivialStruct) -> ()
%26 = apply %25(%24) : $@convention(thin) (@guaranteed NonTrivialStruct) -> ()
end_borrow %24 : $NonTrivialStruct
br bb3
bb2:
br bb3
bb3:
%30 = tuple ()
return %30 : $()
}
// CHECK: sil hidden [ossa] @earlyallocbox_to_stack_partial_apply_test_caller : $@convention(thin) (@owned NonTrivialStruct) -> () {
// CHECK: alloc_stack [lexical] $NonTrivialStruct, var, name "x"
// CHECK: } // end sil function 'earlyallocbox_to_stack_partial_apply_test_caller'
sil hidden [ossa] @earlyallocbox_to_stack_partial_apply_test_caller : $@convention(thin) (@owned NonTrivialStruct) -> () {
bb0(%arg : @owned $NonTrivialStruct):
%0 = alloc_box ${ var NonTrivialStruct }, var, name "x"
%1 = begin_borrow [lexical] %0 : ${ var NonTrivialStruct }
%2 = project_box %1 : ${ var NonTrivialStruct }, 0
store %arg to [init] %2 : $*NonTrivialStruct
%15 = function_ref @earlyallocbox_to_stack_partial_apply_test_callee : $@convention(thin) (@guaranteed { var NonTrivialStruct }) -> ()
%16 = copy_value %1 : ${ var NonTrivialStruct }
%18 = partial_apply [callee_guaranteed] %15(%16) : $@convention(thin) (@guaranteed { var NonTrivialStruct }) -> ()
destroy_value %18 : $@callee_guaranteed () -> ()
end_borrow %1 : ${ var NonTrivialStruct }
destroy_value %0 : ${ var NonTrivialStruct }
%24 = tuple ()
return %24 : $()
}
sil private [ossa] @earlyallocbox_to_stack_partial_apply_test_callee : $@convention(thin) (@guaranteed { var NonTrivialStruct }) -> () {
bb0(%0 : @closureCapture @guaranteed ${ var NonTrivialStruct }):
%1 = project_box %0 : ${ var NonTrivialStruct }, 0
debug_value %1 : $*NonTrivialStruct, var, name "x", argno 1, expr op_deref
%3 = begin_access [read] [unknown] %1 : $*NonTrivialStruct
%4 = mark_unresolved_non_copyable_value [no_consume_or_assign] %3 : $*NonTrivialStruct
%5 = load [copy] %4 : $*NonTrivialStruct
end_access %3 : $*NonTrivialStruct
%7 = move_value %5 : $NonTrivialStruct
destroy_value %7 : $NonTrivialStruct
%9 = tuple ()
return %9 : $()
}