Skip to content

Rework optimization of global variables #65764

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
85aa1c1
handle debug_step in static initializers of globals and in the const …
eeckstein Apr 5, 2023
3560360
Handle `builtin.once` in BottomUpFunctionOrder
eeckstein Apr 11, 2023
8a8a895
alias analysis: compute more precise memory effects of `builtin "once"`
eeckstein May 8, 2023
a8c9aae
Swift Optimizer: add simplification for `cond_fail`
eeckstein May 8, 2023
0a05124
Swift Optimizer: add simplification of `debug_step`
eeckstein May 8, 2023
5a3ab6e
Swift Optimizer: add simplifications for `destructure_struct` and `de…
eeckstein May 8, 2023
88973a3
Swift Optimizer: add simplification for `tuple_extract`
eeckstein May 8, 2023
9b51e69
Swift Optimizer: constant fold builtins in the simplification passes
eeckstein May 8, 2023
c4096bc
Swift Optimizer: simplify `builtin "once"`
eeckstein May 8, 2023
e95c642
Swift SIL: add some APIs for global variables
eeckstein May 8, 2023
3e04b86
make ModulePassContext conform to CustomStringConvertible
eeckstein May 8, 2023
260e68b
Passmanager: fix a problem with skipping the inliner pass
eeckstein May 8, 2023
47a7acd
LICM: hoist `builtin "once"` calls out of loops
eeckstein May 8, 2023
56c09c3
CSE: cse `builtin "once"` calls
eeckstein May 8, 2023
19b828b
StringOptimization: handle inlined global accessors.
eeckstein May 8, 2023
ee2924f
Inliner: don't distinguish between the "mid-level" and "late" inliner
eeckstein May 8, 2023
78ce13d
WalkUtils: Don't treat `end_access` as leaf-use in the AddressDefUseW…
eeckstein May 8, 2023
2b117fd
Swift Optimizer: add APIs to copy from or to a global static initializer
eeckstein May 8, 2023
6d6b94e
Swift Optimizer: add the InitializeStaticGlobals function pass
eeckstein May 8, 2023
88a4a97
Swift Optimizer: add simplification for `load`
eeckstein May 8, 2023
960ca70
Swift Optimizer: add the module pass ReadOnlyGlobalVariables
eeckstein May 8, 2023
1e6511e
Pass Pipeline: replace the old GlobalOpt with the new InitializeStati…
eeckstein May 8, 2023
df7c71b
Optimizer: remove the now obsolete GlobalOpt pass
eeckstein May 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,17 @@ struct AliasAnalysis {
let inst = bridgedInst.instruction
let val = bridgedVal.value
let path = AliasAnalysis.getPtrOrAddressPath(for: val)
if let apply = inst as? ApplySite {
let effect = getMemoryEffect(of: apply, for: val, path: path, context)
switch (effect.read, effect.write) {
case (false, false): return .None
case (true, false): return .MayRead
case (false, true): return .MayWrite
case (true, true): return .MayReadWrite
switch inst {
case let apply as ApplySite:
return getMemoryEffect(ofApply: apply, for: val, path: path, context).bridged
case let builtin as BuiltinInst:
return getMemoryEffect(ofBuiltin: builtin, for: val, path: path, context).bridged
default:
if val.at(path).isEscaping(using: EscapesToInstructionVisitor(target: inst, isAddress: true), context) {
return .MayReadWrite
}
return .None
}
if val.at(path).isEscaping(using: EscapesToInstructionVisitor(target: inst, isAddress: true), context) {
return .MayReadWrite
}
return .None
},

// isObjReleasedFn
Expand Down Expand Up @@ -121,7 +119,7 @@ struct AliasAnalysis {
}
}

private func getMemoryEffect(of apply: ApplySite, for address: Value, path: SmallProjectionPath, _ context: FunctionPassContext) -> SideEffects.Memory {
private func getMemoryEffect(ofApply apply: ApplySite, for address: Value, path: SmallProjectionPath, _ context: FunctionPassContext) -> SideEffects.Memory {
let calleeAnalysis = context.calleeAnalysis
let visitor = SideEffectsVisitor(apply: apply, calleeAnalysis: calleeAnalysis, isAddress: true)
let memoryEffects: SideEffects.Memory
Expand All @@ -132,7 +130,7 @@ private func getMemoryEffect(of apply: ApplySite, for address: Value, path: Smal
memoryEffects = result.memory
} else {
// `address` has unknown escapes. So we have to take the global effects of the called function(s).
memoryEffects = calleeAnalysis.getSideEffects(of: apply).memory
memoryEffects = calleeAnalysis.getSideEffects(ofApply: apply).memory
}
// Do some magic for `let` variables. Function calls cannot modify let variables.
// The only exception is that the let variable is directly passed to an indirect out of the
Expand All @@ -144,14 +142,28 @@ private func getMemoryEffect(of apply: ApplySite, for address: Value, path: Smal
return memoryEffects
}

private func getMemoryEffect(ofBuiltin builtin: BuiltinInst, for address: Value, path: SmallProjectionPath, _ context: FunctionPassContext) -> SideEffects.Memory {

switch builtin.id {
case .Once, .OnceWithContext:
if !address.at(path).isEscaping(using: AddressVisibleByBuiltinOnceVisitor(), context) {
return SideEffects.Memory()
}
let callee = builtin.operands[1].value
return context.calleeAnalysis.getSideEffects(ofCallee: callee).memory
default:
return builtin.memoryEffects
}
}

private func getOwnershipEffect(of apply: ApplySite, for value: Value, path: SmallProjectionPath, _ context: FunctionPassContext) -> SideEffects.Ownership {
let visitor = SideEffectsVisitor(apply: apply, calleeAnalysis: context.calleeAnalysis, isAddress: false)
if let result = value.at(path).visit(using: visitor, context) {
// The resulting effects are the argument effects to which `value` escapes to.
return result.ownership
} else {
// `value` has unknown escapes. So we have to take the global effects of the called function(s).
return visitor.calleeAnalysis.getSideEffects(of: apply).ownership
return visitor.calleeAnalysis.getSideEffects(ofApply: apply).ownership
}
}

Expand Down Expand Up @@ -180,6 +192,11 @@ private struct SideEffectsVisitor : EscapeVisitorWithResult {
var followLoads: Bool { !isAddress }
}

private struct AddressVisibleByBuiltinOnceVisitor : EscapeVisitor {
var followTrivialTypes: Bool { true }
var followLoads: Bool { false }
}

/// Lets `ProjectedValue.isEscaping` return true if the value is "escaping" to the `target` instruction.
private struct EscapesToInstructionVisitor : EscapeVisitor {
let target: Instruction
Expand Down Expand Up @@ -229,3 +246,13 @@ private struct IsIndirectResultWalker: AddressDefUseWalker {
}
}

private extension SideEffects.Memory {
var bridged: swift.MemoryBehavior {
switch (read, write) {
case (false, false): return .None
case (true, false): return .MayRead
case (false, true): return .MayWrite
case (true, true): return .MayReadWrite
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public struct CalleeAnalysis {
// getMemBehaviorFn
{ (bridgedApply: BridgedInstruction, observeRetains: Bool, bca: BridgedCalleeAnalysis) -> swift.MemoryBehavior in
let apply = bridgedApply.instruction as! ApplySite
let e = bca.analysis.getSideEffects(of: apply)
let e = bca.analysis.getSideEffects(ofApply: apply)
return e.getMemBehavior(observeRetains: observeRetains)
}
)
Expand Down Expand Up @@ -60,8 +60,12 @@ public struct CalleeAnalysis {
}

/// Returns the global (i.e. not argument specific) side effects of an apply.
public func getSideEffects(of apply: ApplySite) -> SideEffects.GlobalEffects {
guard let callees = getCallees(callee: apply.callee) else {
public func getSideEffects(ofApply apply: ApplySite) -> SideEffects.GlobalEffects {
return getSideEffects(ofCallee: apply.callee)
}

public func getSideEffects(ofCallee callee: Value) -> SideEffects.GlobalEffects {
guard let callees = getCallees(callee: callee) else {
return .worstEffects
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ swift_compiler_sources(Optimizer
CleanupDebugSteps.swift
ComputeEscapeEffects.swift
ComputeSideEffects.swift
InitializeStaticGlobals.swift
ObjCBridgingOptimization.swift
MergeCondFails.swift
ReleaseDevirtualizer.swift
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//===--- InitializeStaticGlobals.swift -------------------------------------==//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SIL

/// Converts a lazily initialized global to a statically initialized global variable.
///
/// When this pass runs on a global initializer `[global_init_once_fn]` it tries to
/// create a static initializer for the initialized global.
///
/// ```
/// sil [global_init_once_fn] @globalinit {
/// alloc_global @the_global
/// %a = global_addr @the_global
/// %i = some_const_initializer_insts
/// store %i to %a
/// }
/// ```
/// The pass creates a static initializer for the global:
/// ```
/// sil_global @the_global = {
/// %initval = some_const_initializer_insts
/// }
/// ```
/// and removes the allocation and store instructions from the initializer function:
/// ```
/// sil [global_init_once_fn] @globalinit {
/// %a = global_addr @the_global
/// %i = some_const_initializer_insts
/// }
/// ```
/// The initializer then becomes a side-effect free function which let's the builtin-
/// simplification remove the `builtin "once"` which calls the initializer.
///
let initializeStaticGlobalsPass = FunctionPass(name: "initialize-static-globals") {
(function: Function, context: FunctionPassContext) in

if !function.isGlobalInitOnceFunction {
return
}

guard let (allocInst, storeToGlobal) = function.getGlobalInitialization() else {
return
}

if !allocInst.global.canBeInitializedStatically {
return
}

context.createStaticInitializer(for: allocInst.global,
initValue: storeToGlobal.source as! SingleValueInstruction)
context.erase(instruction: allocInst)
context.erase(instruction: storeToGlobal)
}

private extension Function {
/// Analyses the global initializer function and returns the `alloc_global` and `store`
/// instructions which initialize the global.
///
/// The function's single basic block must contain following code pattern:
/// ```
/// alloc_global @the_global
/// %a = global_addr @the_global
/// %i = some_const_initializer_insts
/// store %i to %a
/// ```
func getGlobalInitialization() -> (allocInst: AllocGlobalInst, storeToGlobal: StoreInst)? {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm baffled as to why getGlobalInitialization is a member of Function as opposed to taking a Function as an argument.

As a rule, if it doesn't require access to Function's internal state, don't make it part of Function's interface.

The implicit self in the implementation also makes it very hard to read the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As a rule, if it doesn't require access to Function's internal state, don't make it part of Function's interface.

Note that in an extension you can't access internal state and a private extension doesn't add to the type's interface.


guard let block = singleBlock else {
return nil
}

var allocInst: AllocGlobalInst? = nil
var globalAddr: GlobalAddrInst? = nil
var store: StoreInst? = nil

for inst in block.instructions {
switch inst {
case is ReturnInst,
is DebugValueInst,
is DebugStepInst:
break
case let agi as AllocGlobalInst:
if allocInst != nil {
return nil
}
allocInst = agi
case let ga as GlobalAddrInst:
if globalAddr != nil {
return nil
}
guard let agi = allocInst, agi.global == ga.global else {
return nil
}
globalAddr = ga
case let si as StoreInst:
if store != nil {
return nil
}
guard let ga = globalAddr else {
return nil
}
if si.destination != ga {
return nil
}
store = si
default:
if !inst.isValidInStaticInitializerOfGlobal {
return nil
}
}
}
if let store = store {
return (allocInst: allocInst!, storeToGlobal: store)
}
return nil
}

var singleBlock: BasicBlock? {
let block = entryBlock
if block.next != nil {
return nil
}
return block
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ swift_compiler_sources(Optimizer
SimplifyBranch.swift
SimplifyBuiltin.swift
SimplifyCondBranch.swift
SimplifyCondFail.swift
SimplifyDebugStep.swift
SimplifyDestructure.swift
SimplifyGlobalValue.swift
SimplifyLoad.swift
SimplifyStrongRetainRelease.swift
SimplifyStructExtract.swift
SimplifyTupleExtract.swift
SimplifyUncheckedEnumData.swift)
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ extension BuiltinInst : OnoneSimplifyable {
optimizeIsConcrete(allowArchetypes: false, context)
case .IsSameMetatype:
optimizeIsSameMetatype(context)
case .Once:
optimizeBuiltinOnce(context)
default:
// TODO: handle other builtin types
break
if let literal = constantFold(context) {
uses.replaceAll(with: literal, context)
}
}
}
}
Expand Down Expand Up @@ -64,6 +67,29 @@ private extension BuiltinInst {

uses.replaceAll(with: result, context)
}

func optimizeBuiltinOnce(_ context: SimplifyContext) {
guard let callee = calleeOfOnce, callee.isDefinition else {
return
}
// If the callee is side effect-free we can remove the whole builtin "once".
// We don't use the callee's memory effects but instead look at all callee instructions
// because memory effects are not computed in the Onone pipeline, yet.
// This is no problem because the callee (usually a global init function )is mostly very small,
// or contains the side-effect instruction `alloc_global` right at the beginning.
if callee.instructions.contains(where: { $0.mayReadOrWriteMemory || $0.hasUnspecifiedSideEffects }) {
return
}
context.erase(instruction: self)
}

var calleeOfOnce: Function? {
let callee = operands[1].value
if let fri = callee as? FunctionRefInst {
return fri.referencedFunction
}
return nil
}
}

private func typesOfValuesAreEqual(_ lhs: Value, _ rhs: Value, in function: Function) -> Bool? {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//===--- SimplifyCondFail.swift -------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SIL

extension CondFailInst : OnoneSimplifyable {
func simplify(_ context: SimplifyContext) {

/// Eliminates
/// ```
/// %0 = integer_literal 0
/// cond_fail %0, "message"
/// ```
if let literal = condition as? IntegerLiteralInst,
literal.value.isZero() {

context.erase(instruction: self)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//===--- SimplifyDebugStep.swift ------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SIL

extension DebugStepInst : Simplifyable {
func simplify(_ context: SimplifyContext) {
// When compiling with optimizations (note: it's not a OnoneSimplifyable transformation),
// unconditionally remove debug_step instructions.
context.erase(instruction: self)
}
}

Loading