Skip to content

Commit 14422cd

Browse files
committed
libswift: Add the StackList data structure
StackList is a very efficient data structure for worklist type things. This is a port of the C++ utility with the same name. Compared to Array, it does not require any memory allocations.
1 parent d49108d commit 14422cd

File tree

8 files changed

+235
-5
lines changed

8 files changed

+235
-5
lines changed

include/swift/SIL/SILBridging.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ enum {
3434
BridgedOperandSize = 4 * sizeof(uintptr_t)
3535
};
3636

37+
typedef struct {
38+
void * _Nullable data;
39+
} BridgedSlab;
40+
41+
enum {
42+
BridgedSlabCapacity = 64 * sizeof(uintptr_t)
43+
};
44+
3745
enum ChangeNotificationKind {
3846
instructionsChanged,
3947
callsChanged,
@@ -121,6 +129,11 @@ void PassContext_notifyChanges(BridgedPassContext passContext,
121129
enum ChangeNotificationKind changeKind);
122130
void PassContext_eraseInstruction(BridgedPassContext passContext,
123131
BridgedInstruction inst);
132+
BridgedSlab PassContext_getNextSlab(BridgedSlab slab);
133+
BridgedSlab PassContext_allocSlab(BridgedPassContext passContext,
134+
BridgedSlab afterSlab);
135+
BridgedSlab PassContext_freeSlab(BridgedPassContext passContext,
136+
BridgedSlab slab);
124137

125138
BridgedStringRef SILFunction_getName(BridgedFunction function);
126139
BridgedStringRef SILFunction_debugDescription(BridgedFunction function);

include/swift/SIL/SILModule.h

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ class Output;
5757

5858
namespace swift {
5959

60-
/// A fixed size slab of memory, which can be allocated and freed by the
61-
/// SILModule at (basically) zero cost.
62-
class FixedSizeSlab : public llvm::ilist_node<FixedSizeSlab>,
63-
public SILAllocated<FixedSizeSlab> {
60+
/// The payload for the FixedSizeSlab.
61+
/// This is a super-class rather than a member of FixedSizeSlab to make bridging
62+
/// with libswift easier.
63+
class FixedSizeSlabPayload {
6464
public:
6565
/// The capacity of the payload.
6666
static constexpr size_t capacity = 64 * sizeof(uintptr_t);
@@ -78,7 +78,7 @@ class FixedSizeSlab : public llvm::ilist_node<FixedSizeSlab>,
7878
uintptr_t overflowGuard = magicNumber;
7979

8080
public:
81-
void operator=(const FixedSizeSlab &) = delete;
81+
void operator=(const FixedSizeSlabPayload &) = delete;
8282
void operator delete(void *Ptr, size_t) = delete;
8383

8484
/// Returns the payload pointing to \p T.
@@ -88,6 +88,17 @@ class FixedSizeSlab : public llvm::ilist_node<FixedSizeSlab>,
8888
template<typename T> const T *dataFor() const { return (const T *)(&data[0]); }
8989
};
9090

91+
/// A fixed size slab of memory, which can be allocated and freed by the
92+
/// SILModule at (basically) zero cost.
93+
/// See SILModule::allocSlab().
94+
class FixedSizeSlab : public llvm::ilist_node<FixedSizeSlab>,
95+
public SILAllocated<FixedSizeSlab>,
96+
public FixedSizeSlabPayload {
97+
public:
98+
void operator=(const FixedSizeSlab &) = delete;
99+
void operator delete(void *Ptr, size_t) = delete;
100+
};
101+
91102
class AnyFunctionType;
92103
class ASTContext;
93104
class FileUnit;

include/swift/SILOptimizer/PassManager/PassManager.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,17 @@ class LibswiftPassInvocation {
5252
/// Non-null if this is an instruction pass, invoked from SILCombine.
5353
SILCombiner *silCombiner;
5454

55+
/// All slabs, allocated by the pass.
56+
SILModule::SlabList allocatedSlabs;
57+
5558
public:
5659
LibswiftPassInvocation(SILPassManager *passManager, SILCombiner *silCombiner) :
5760
passManager(passManager), silCombiner(silCombiner) {}
5861

62+
FixedSizeSlab *allocSlab(FixedSizeSlab *afterSlab);
63+
64+
FixedSizeSlab *freeSlab(FixedSizeSlab *slab);
65+
5966
/// The top-level API to erase an instruction, called from the Swift pass.
6067
void eraseInstruction(SILInstruction *inst);
6168

lib/SILOptimizer/PassManager/PassManager.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,26 @@ void SILPassManager::viewCallGraph() {
10791079
// LibswiftPassInvocation
10801080
//===----------------------------------------------------------------------===//
10811081

1082+
static_assert(BridgedSlabCapacity == FixedSizeSlab::capacity,
1083+
"wrong bridged slab capacity");
1084+
1085+
FixedSizeSlab *LibswiftPassInvocation::allocSlab(FixedSizeSlab *afterSlab) {
1086+
FixedSizeSlab *slab = passManager->getModule()->allocSlab();
1087+
if (afterSlab) {
1088+
allocatedSlabs.insert(std::next(afterSlab->getIterator()), *slab);
1089+
} else {
1090+
allocatedSlabs.push_back(*slab);
1091+
}
1092+
return slab;
1093+
}
1094+
1095+
FixedSizeSlab *LibswiftPassInvocation::freeSlab(FixedSizeSlab *slab) {
1096+
FixedSizeSlab *prev = std::prev(&*slab->getIterator());
1097+
allocatedSlabs.remove(*slab);
1098+
passManager->getModule()->freeSlab(slab);
1099+
return prev;
1100+
}
1101+
10821102
void LibswiftPassInvocation::eraseInstruction(SILInstruction *inst) {
10831103
if (silCombiner) {
10841104
// TODO
@@ -1088,6 +1108,7 @@ void LibswiftPassInvocation::eraseInstruction(SILInstruction *inst) {
10881108
}
10891109

10901110
void LibswiftPassInvocation::finishedPassRun() {
1111+
assert(allocatedSlabs.empty() && "StackList is leaking slabs");
10911112
}
10921113

10931114
//===----------------------------------------------------------------------===//
@@ -1099,6 +1120,38 @@ inline LibswiftPassInvocation *castToPassInvocation(BridgedPassContext ctxt) {
10991120
static_cast<const LibswiftPassInvocation *>(ctxt.opaqueCtxt));
11001121
}
11011122

1123+
inline FixedSizeSlab *castToSlab(BridgedSlab slab) {
1124+
if (slab.data)
1125+
return static_cast<FixedSizeSlab *>((FixedSizeSlabPayload *)slab.data);
1126+
return nullptr;
1127+
}
1128+
1129+
inline BridgedSlab toBridgedSlab(FixedSizeSlab *slab) {
1130+
if (slab) {
1131+
FixedSizeSlabPayload *payload = slab;
1132+
assert((void *)payload == slab->dataFor<void>());
1133+
return {payload};
1134+
}
1135+
return {nullptr};
1136+
}
1137+
1138+
1139+
BridgedSlab PassContext_getNextSlab(BridgedSlab slab) {
1140+
return toBridgedSlab(&*std::next(castToSlab(slab)->getIterator()));
1141+
}
1142+
1143+
BridgedSlab PassContext_allocSlab(BridgedPassContext passContext,
1144+
BridgedSlab afterSlab) {
1145+
auto *inv = castToPassInvocation(passContext);
1146+
return toBridgedSlab(inv->allocSlab(castToSlab(afterSlab)));
1147+
}
1148+
1149+
BridgedSlab PassContext_freeSlab(BridgedPassContext passContext,
1150+
BridgedSlab slab) {
1151+
auto *inv = castToPassInvocation(passContext);
1152+
return toBridgedSlab(inv->freeSlab(castToSlab(slab)));
1153+
}
1154+
11021155
void PassContext_notifyChanges(BridgedPassContext passContext,
11031156
enum ChangeNotificationKind changeKind) {
11041157
LibswiftPassInvocation *inv = castToPassInvocation(passContext);

libswift/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,6 @@ Some performance considerations:
9090

9191
* 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.
9292

93+
* Minimizing memory allocations: _libswift_ provides 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`)
94+
9395
But most importantly, if there are performance issues with the current compiler, the design of _libswift_ should make it possible to fix performance deficiencies with future compiler improvements.

libswift/Sources/Optimizer/PassManager/PassUtils.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ struct FunctionPass {
4949
}
5050
}
5151

52+
extension StackList {
53+
init(_ context: FunctionPassContext) {
54+
self.init(context: context.passContext)
55+
}
56+
}
57+
5258
extension Builder {
5359
init(at insPnt: Instruction, location: Location,
5460
_ context: FunctionPassContext) {

libswift/Sources/SIL/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ add_libswift_module(SIL
1818
Registration.swift
1919
Type.swift
2020
Utils.swift
21+
StackList.swift
2122
Value.swift)
2223

libswift/Sources/SIL/StackList.swift

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//===--- StackList.swift - defines the StackList data structure -----------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SILBridging
14+
15+
/// A very efficient implementation of a stack, which can also be iterated over.
16+
///
17+
/// A StackList is the best choice for things like worklists, etc., if no random
18+
/// access is needed.
19+
/// Compared to Array, it does not require any memory allocations, because it
20+
/// uses the bump pointer allocator of the SILModule.
21+
/// All operations have (almost) zero cost.
22+
///
23+
/// Ideally this would be a move-only type. Until then, only pass StackLists as
24+
/// inout!
25+
/// Note: it is required to manually remove all elements - either by pop() or
26+
/// removeAll().
27+
public struct StackList<Element> : Sequence, CustomReflectable {
28+
29+
private let context: BridgedPassContext
30+
private var firstSlab = BridgedSlab(data: nil)
31+
private var lastSlab = BridgedSlab(data: nil)
32+
private var endIndex: Int = slabCapacity
33+
34+
private static var slabCapacity: Int {
35+
BridgedSlabCapacity / MemoryLayout<Element>.size
36+
}
37+
38+
private static func bind(_ slab: BridgedSlab) -> UnsafeMutablePointer<Element> {
39+
return slab.data!.bindMemory(to: Element.self, capacity: StackList.slabCapacity)
40+
}
41+
42+
public struct Iterator : IteratorProtocol {
43+
var slab: BridgedSlab
44+
var index: Int
45+
let lastSlab: BridgedSlab
46+
let endIndex: Int
47+
48+
public mutating func next() -> Element? {
49+
let end = (slab.data == lastSlab.data ? endIndex : slabCapacity)
50+
if index < end {
51+
let elem = StackList.bind(slab)[index]
52+
index += 1
53+
54+
if index >= end && slab.data != lastSlab.data {
55+
slab = PassContext_getNextSlab(slab)
56+
index = 0
57+
}
58+
return elem
59+
}
60+
return nil
61+
}
62+
}
63+
64+
public init(context: BridgedPassContext) { self.context = context }
65+
66+
public func makeIterator() -> Iterator {
67+
return Iterator(slab: firstSlab, index: 0, lastSlab: lastSlab, endIndex: endIndex)
68+
}
69+
70+
public var first: Element? {
71+
if isEmpty {
72+
return nil
73+
}
74+
return StackList.bind(firstSlab)[0]
75+
}
76+
77+
public var last: Element? {
78+
if isEmpty {
79+
return nil
80+
}
81+
return StackList.bind(lastSlab)[endIndex - 1]
82+
}
83+
84+
public mutating func push(_ element: Element) {
85+
if endIndex >= StackList.slabCapacity {
86+
lastSlab = PassContext_allocSlab(context, lastSlab)
87+
if firstSlab.data == nil {
88+
firstSlab = lastSlab
89+
}
90+
endIndex = 0
91+
}
92+
(StackList.bind(lastSlab) + endIndex).initialize(to: element)
93+
endIndex += 1
94+
}
95+
96+
public var isEmpty: Bool { return firstSlab.data == nil }
97+
98+
public mutating func pop() -> Element? {
99+
if isEmpty {
100+
return nil
101+
}
102+
assert(endIndex > 0)
103+
endIndex -= 1
104+
let elem = (StackList.bind(lastSlab) + endIndex).move()
105+
106+
if endIndex == 0 {
107+
if lastSlab.data == firstSlab.data {
108+
_ = PassContext_freeSlab(context, lastSlab)
109+
firstSlab.data = nil
110+
lastSlab.data = nil
111+
} else {
112+
lastSlab = PassContext_freeSlab(context, lastSlab)
113+
}
114+
endIndex = StackList.slabCapacity
115+
}
116+
117+
return elem
118+
}
119+
120+
public mutating func removeAll() {
121+
if isEmpty {
122+
return
123+
}
124+
while lastSlab.data != firstSlab.data {
125+
lastSlab = PassContext_freeSlab(context, lastSlab)
126+
}
127+
_ = PassContext_freeSlab(context, lastSlab)
128+
firstSlab.data = nil
129+
lastSlab.data = nil
130+
endIndex = StackList.slabCapacity
131+
}
132+
133+
public var customMirror: Mirror {
134+
let c: [Mirror.Child] = map { (label: nil, value: $0) }
135+
return Mirror(self, children: c)
136+
}
137+
}

0 commit comments

Comments
 (0)