Skip to content

Add swift SIL/Optimizer infrastructure #40805

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 9 commits into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion SwiftCompilerSources/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,6 @@ Some performance considerations:

* Memory is managed on the C++ side. On the Swift side, SIL objects are treated as "immortal" objects, which avoids (most of) ARC overhead. ARC runtime functions are still being called, but no atomic reference counting operations are done. In future we could add a compiler feature to mark classes as immortal to avoid the runtime calls at all.

* Minimizing memory allocations by using data structures which are malloc-free. For example `StackList` can be used in optimizations to implement work lists without any memory allocations. (Not yet done: `BasicBlockSet`, `BasicBlockData`)
* Minimizing memory allocations by using data structures which are malloc-free, e.g. `Stack`

* The Swift modules are compiled with `-cross-module-optimization`. This enables the compiler to optimize the Swift code across module boundaries.
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@

swift_compiler_sources(Optimizer
AliasAnalysis.swift
CalleeAnalysis.swift)
CalleeAnalysis.swift
DeadEndBlocksAnalysis.swift
DominatorTree.swift
PostDominatorTree.swift)
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ public struct CalleeAnalysis {
public func getCallees(callee: Value) -> FunctionArray {
return FunctionArray(bridged: CalleeAnalysis_getCallees(bridged, callee.bridged))
}

public func getDestructors(destroyInst: Instruction) -> FunctionArray {
return FunctionArray(bridged:
CalleeAnalysis_getInstCallees(bridged, destroyInst.bridged))
}
}

public struct FunctionArray : RandomAccessCollection, CustomReflectable {
public struct FunctionArray : RandomAccessCollection, FormattedLikeArray {
fileprivate let bridged: BridgedCalleeList

public var startIndex: Int { 0 }
Expand All @@ -32,9 +37,4 @@ public struct FunctionArray : RandomAccessCollection, CustomReflectable {
}

public var allCalleesKnown: Bool { bridged.incomplete == 0 }

public var customMirror: Mirror {
let c: [Mirror.Child] = map { (label: nil, value: $0.name) }
return Mirror(self, children: c)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//===--- DeadEndBlocksAnalysis.swift - the dead-end blocks analysis -------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2021 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 OptimizerBridging
import SIL

public struct DeadEndBlocksAnalysis {
let bridged: BridgedDeadEndBlocksAnalysis

public func isDeadEnd(_ block: BasicBlock) -> Bool {
return DeadEndBlocksAnalysis_isDeadEnd(bridged, block.bridged) != 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//===--- DominatorTree.swift - the dominator tree -------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2022 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
import OptimizerBridging

public struct DominatorTree {
let bridged: BridgedDomTree
}

extension BasicBlock {
func dominates(_ other: BasicBlock, _ domTree: DominatorTree) -> Bool {
DominatorTree_dominates(domTree.bridged, self.bridged, other.bridged) != 0
}

func strictlyDominates(_ other: BasicBlock, _ domTree: DominatorTree) -> Bool {
dominates(other, domTree) && self != other
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//===--- PostDominatorTree.swift - the post dominator tree ----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2022 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
import OptimizerBridging

public struct PostDominatorTree {
let bridged: BridgedPostDomTree
}

extension BasicBlock {
func postDominates(_ other: BasicBlock, _ pdomTree: PostDominatorTree) -> Bool {
PostDominatorTree_postDominates(pdomTree.bridged, self.bridged, other.bridged) != 0
}

func strictlyPostDominates(_ other: BasicBlock, _ pdomTree: PostDominatorTree) -> Bool {
postDominates(other, pdomTree) && self != other
}
}
1 change: 1 addition & 0 deletions SwiftCompilerSources/Sources/Optimizer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ endif()
add_swift_compiler_module(Optimizer DEPENDS ${dependencies})

add_subdirectory(Analysis)
add_subdirectory(DataStructures)
add_subdirectory(InstructionPasses)
add_subdirectory(PassManager)
add_subdirectory(FunctionPasses)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//===--- BasicBlockRange.swift - a range of basic blocks ------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2022 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

/// A range of basic blocks.
///
/// The `BasicBlockRange` defines a range from a dominating "begin" block to one or more "end" blocks.
/// The range is "exclusive", which means that the "end" blocks are not part of the range.
///
/// The `BasicBlockRange` is in the same spirit as a linear range, but as the control flow is a graph
/// and not a linear list, there can be "exit" blocks from within the range.
///
/// One or more "potential" end blocks can be inserted.
/// Though, not all inserted blocks end up as "end" blocks.
///
/// There are several kind of blocks:
/// * begin: it is a single block which dominates all blocks of the range
/// * range: all blocks from which there is a path from the begin block to any of the end blocks
/// * ends: all inserted blocks which are at the end of the range
/// * exits: all successor blocks of range blocks which are not in the range themselves
/// * interiors: all inserted blocks which are not end blocks.
///
/// In the following example, let's assume `B` is the begin block and `I1`, `I2` and `I3`
/// were inserted as potential end blocks:
///
/// B
/// / \
/// I1 I2
/// / \
/// I3 X
///
/// Then `I1` and `I3` are "end" blocks. `I2` is an interior block and `X` is an exit block.
/// The range consists of `B` and `I2`. Note that the range does not include `I1` and `I3`
/// because it's an _exclusive_ range.
///
/// This type should be a move-only type, but unfortunately we don't have move-only
/// types yet. Therefore it's needed to call `deinitialize()` explicitly to
/// destruct this data structure, e.g. in a `defer {}` block.
struct BasicBlockRange : CustomStringConvertible, CustomReflectable {

/// The dominating begin block.
let begin: BasicBlock

/// The exclusive range, i.e. not containing the end blocks.
private(set) var range: Stack<BasicBlock>

/// All inserted blocks.
private(set) var inserted: Stack<BasicBlock>

private var insertedSet: BasicBlockSet
private var worklist: BasicBlockWorklist

init(begin: BasicBlock, _ context: PassContext) {
self.begin = begin
self.range = Stack(context)
self.inserted = Stack(context)
self.insertedSet = BasicBlockSet(context)
self.worklist = BasicBlockWorklist(context)
}

/// Insert a potential end block.
mutating func insert(_ block: BasicBlock) {
if !insertedSet.contains(block) {
insertedSet.insert(block)
inserted.append(block)
}
if block != begin {
worklist.pushIfNotVisited(contentsOf: block.predecessors)

while let b = worklist.pop() {
range.append(b)
if b != begin {
worklist.pushIfNotVisited(contentsOf: b.predecessors)
}
}
}
}

/// Returns true if the exclusive range contains `block`.
func contains(_ block: BasicBlock) -> Bool { worklist.hasBeenPushed(block) }

/// Returns true if the inclusive range contains `block`.
func inclusiveRangeContains (_ block: BasicBlock) -> Bool {
contains(block) || insertedSet.contains(block)
}

/// Returns true if the range is valid and that's iff the begin block dominates all blocks of the range.
var isValid: Bool {
let entry = begin.function.entryBlock
return begin == entry ||
// If any block in the range is not dominated by `begin`, the range propagates back to the entry block.
!inclusiveRangeContains(entry)
}

/// Returns the end blocks.
var ends: LazyFilterSequence<Stack<BasicBlock>> {
inserted.lazy.filter { !worklist.hasBeenPushed($0) }
}

/// Returns the exit blocks.
var exits: LazySequence<FlattenSequence<
LazyMapSequence<Stack<BasicBlock>,
LazyFilterSequence<SuccessorArray>>>> {
range.lazy.flatMap {
$0.successors.lazy.filter {
!inclusiveRangeContains($0) || $0 == begin
}
}
}

/// Returns the interior blocks.
var interiors: LazyFilterSequence<Stack<BasicBlock>> {
inserted.lazy.filter { contains($0) && $0 != begin }
}

var description: String {
"""
begin: \(begin.name)
range: \(range)
ends: \(ends)
exits: \(exits)
interiors: \(interiors)
"""
}

var customMirror: Mirror { Mirror(self, children: []) }

/// TODO: once we have move-only types, make this a real deinit.
mutating func deinitialize() {
worklist.deinitialize()
inserted.deinitialize()
insertedSet.deinitialize()
range.deinitialize()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//===--- BasicBlockSet.swift - a set of basic blocks ----------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2022 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
import OptimizerBridging

/// A set of basic blocks.
///
/// This is an extremely efficient implementation which does not need memory
/// allocations or hash lookups.
///
/// This type should be a move-only type, but unfortunately we don't have move-only
/// types yet. Therefore it's needed to call `deinitialize()` explicitly to
/// destruct this data structure, e.g. in a `defer {}` block.
struct BasicBlockSet : CustomStringConvertible, CustomReflectable {

private let context: PassContext
private let bridged: BridgedBasicBlockSet

init(_ context: PassContext) {
self.context = context
self.bridged = PassContext_allocBasicBlockSet(context._bridged)
}

func contains(_ block: BasicBlock) -> Bool {
BasicBlockSet_contains(bridged, block.bridged) != 0
}

mutating func insert(_ block: BasicBlock) {
BasicBlockSet_insert(bridged, block.bridged)
}

mutating func erase(_ block: BasicBlock) {
BasicBlockSet_erase(bridged, block.bridged)
}

var description: String {
let function = BasicBlockSet_getFunction(bridged).function
let blockNames = function.blocks.enumerated().filter { contains($0.1) }
.map { "bb\($0.0)"}
return "{" + blockNames.joined(separator: ", ") + "}"
}

var customMirror: Mirror { Mirror(self, children: []) }

/// TODO: once we have move-only types, make this a real deinit.
mutating func deinitialize() {
PassContext_freeBasicBlockSet(context._bridged, bridged)
}
}
Loading