Skip to content

Commit

Permalink
Merge pull request #65764 from eeckstein/globalopt
Browse files Browse the repository at this point in the history
Rework optimization of global variables
  • Loading branch information
eeckstein authored May 9, 2023
2 parents e12e16d + df7c71b commit af933b0
Show file tree
Hide file tree
Showing 89 changed files with 1,745 additions and 1,609 deletions.
55 changes: 41 additions & 14 deletions SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift
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)? {

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

0 comments on commit af933b0

Please sign in to comment.