Skip to content

Commit 0dfaf80

Browse files
committed
SIL: add a StackList data structure with zero cost operations.
A StackList is the best choice for things like worklists, etc., if no random access is needed. Regardless of how large a Stack gets, there is no memory allocation needed (except maybe for the first few uses in the compiler run). All operations have (almost) zero cost. The needed memory is managed by the SILModule. Initially, the memory slabs are allocated with the module's bump pointer allocator. In contrast to bump pointer allocated memory, those slabs can be freed again (at zero cost) and then recycled. StackList is meant to be a replacement for llvm::SmallVector, which needs to malloc after the small size is exceeded. This is more a usability than a compile time improvement. Usually we think hard about how to correctly use an llvm::SmallVector to avoid memory allocations: we chose the small size wisely and in many cases we keep a shared instance of a SmallVector to reuse its allocated capacity. All this is not necessary by using a StackList: no need to select a small size and to share it across usages.
1 parent ae2a4cc commit 0dfaf80

File tree

3 files changed

+246
-0
lines changed

3 files changed

+246
-0
lines changed

include/swift/SIL/SILModule.h

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,52 @@
4949
#include "llvm/Support/raw_ostream.h"
5050
#include <functional>
5151

52+
namespace swift {
53+
54+
/// A fixed size slab of memory, which can be allocated and freed by the
55+
/// SILModule at (basically) zero cost.
56+
class FixedSizeSlab : public llvm::ilist_node<FixedSizeSlab>,
57+
public SILAllocated<FixedSizeSlab> {
58+
public:
59+
/// The capacity of the payload.
60+
static constexpr size_t capacity = 64 * sizeof(uintptr_t);
61+
62+
private:
63+
friend class SILModule;
64+
65+
/// The magic number which is stored in overflowGuard.
66+
static constexpr uintptr_t magicNumber = (uintptr_t)0xdeadbeafdeadbeafull;
67+
68+
/// The payload.
69+
char data[capacity];
70+
71+
/// Used for a cheap buffer overflow check - in the spirit of libgmalloc.
72+
uintptr_t overflowGuard = magicNumber;
73+
74+
public:
75+
/// Returns the payload pointing to \p T.
76+
template<typename T> T *dataFor() { return (T *)(&data[0]); }
77+
78+
/// Returns the payload pointing to const \p T
79+
template<typename T> const T *dataFor() const { return (const T *)(&data[0]); }
80+
};
81+
82+
} // end swift namespace
83+
5284
namespace llvm {
5385
namespace yaml {
5486
class Output;
5587
} // end namespace yaml
88+
89+
template <>
90+
struct ilist_traits<::swift::FixedSizeSlab> :
91+
public ilist_node_traits<::swift::FixedSizeSlab> {
92+
public:
93+
static void deleteNode(::swift::FixedSizeSlab *V) {
94+
llvm_unreachable("cannot delete a slab");
95+
}
96+
};
97+
5698
} // end namespace llvm
5799

58100
namespace swift {
@@ -129,6 +171,7 @@ class SILModule {
129171
};
130172

131173
using ActionCallback = std::function<void()>;
174+
using SlabList = llvm::ilist<FixedSizeSlab>;
132175

133176
private:
134177
friend KeyPathPattern;
@@ -151,6 +194,12 @@ class SILModule {
151194
/// Allocator that manages the memory of all the pieces of the SILModule.
152195
mutable llvm::BumpPtrAllocator BPA;
153196

197+
/// The list of freed slabs, which can be reused.
198+
SlabList freeSlabs;
199+
200+
/// For consistency checking.
201+
size_t numAllocatedSlabs = 0;
202+
154203
/// The swift Module associated with this SILModule.
155204
ModuleDecl *TheSwiftModule;
156205

@@ -733,6 +782,22 @@ class SILModule {
733782
return static_cast<T *>(allocate(sizeof(T) * Count, alignof(T)));
734783
}
735784

785+
/// Allocates a slab of memory.
786+
///
787+
/// This has (almost) zero cost, because for the first time, the allocation is
788+
/// done with the BPA.
789+
/// Subsequent allocations are reusing the already freed slabs.
790+
FixedSizeSlab *allocSlab();
791+
792+
/// Frees a slab.
793+
///
794+
/// This has (almost) zero cost, because the slab is just put into the
795+
/// freeSlabs list.
796+
void freeSlab(FixedSizeSlab *slab);
797+
798+
/// Frees all slabs of a list.
799+
void freeAllSlabs(SlabList &slabs);
800+
736801
template <typename T>
737802
MutableArrayRef<T> allocateCopy(ArrayRef<T> Array) const {
738803
MutableArrayRef<T> result(allocate<T>(Array.size()), Array.size());

include/swift/SIL/StackList.h

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
//===--- StackList.h - defines the StackList data structure -----*- C++ -*-===//
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+
#ifndef SWIFT_SIL_STACKLIST_H
14+
#define SWIFT_SIL_STACKLIST_H
15+
16+
#include "swift/SIL/SILModule.h"
17+
18+
namespace swift {
19+
20+
/// A very efficient implementation of a stack, which can also be iterated over.
21+
///
22+
/// A StackList is the best choice for things like worklists, etc., if no random
23+
/// access is needed.
24+
/// Regardless of how large a Stack gets, there is no memory allocation needed
25+
/// (except maybe for the first few uses in the compiler run).
26+
/// All operations have (almost) zero cost.
27+
template <typename Element> class StackList {
28+
/// The capacity of a single slab.
29+
static constexpr size_t slabCapacity =
30+
FixedSizeSlab::capacity / sizeof(Element);
31+
32+
static_assert(slabCapacity > 0, "Element type to large for StackList");
33+
static_assert(alignof(FixedSizeSlab) >= alignof(Element),
34+
"Element alignment to large for StackList");
35+
36+
/// Backlink to the module which manages the slab allocation.
37+
SILModule &module;
38+
39+
/// The list of slabs.
40+
///
41+
/// Invariant: there is always free space in the last slab to store at least
42+
/// one element.
43+
SILModule::SlabList slabs;
44+
45+
/// The index of the next free element in endSlab.
46+
///
47+
/// Invariant: endIndex < slabCapacity
48+
unsigned endIndex = 0;
49+
50+
FixedSizeSlab *lastSlab() { return &*slabs.rbegin(); }
51+
const FixedSizeSlab *lastSlab() const { return &*slabs.rbegin(); }
52+
53+
void allocSlab() { slabs.push_back(module.allocSlab()); }
54+
55+
void growIfNeeded() {
56+
if (endIndex == slabCapacity) {
57+
allocSlab();
58+
endIndex = 0;
59+
}
60+
}
61+
62+
63+
public:
64+
/// The Stack's iterator.
65+
class iterator {
66+
friend StackList<Element>;
67+
const FixedSizeSlab *slab;
68+
unsigned index;
69+
70+
iterator(const FixedSizeSlab *slab, unsigned index)
71+
: slab(slab), index(index) {}
72+
73+
public:
74+
const Element &operator*() const {
75+
assert(index < slabCapacity);
76+
return slab->dataFor<Element>()[index];
77+
}
78+
const Element &operator->() const { return *this; }
79+
80+
iterator &operator++() {
81+
assert(index < slabCapacity);
82+
index++;
83+
if (index == slabCapacity) {
84+
slab = &*std::next(slab->getIterator());
85+
index = 0;
86+
}
87+
return *this;
88+
}
89+
90+
iterator operator++(int unused) {
91+
iterator copy = *this;
92+
++*this;
93+
return copy;
94+
}
95+
96+
friend bool operator==(iterator lhs, iterator rhs) {
97+
return lhs.slab == rhs.slab && lhs.index == rhs.index;
98+
}
99+
100+
friend bool operator!=(iterator lhs, iterator rhs) {
101+
return !(lhs == rhs);
102+
}
103+
};
104+
105+
/// Constructor.
106+
StackList(SILFunction *function) : module(function->getModule()) {
107+
/// Allocate one slab so that there is free space for inserting the first
108+
/// element.
109+
allocSlab();
110+
}
111+
112+
~StackList() {
113+
module.freeAllSlabs(slabs);
114+
}
115+
116+
iterator begin() const { return iterator(&*slabs.begin(), 0); }
117+
iterator end() const { return iterator(lastSlab(), endIndex); }
118+
119+
bool empty() const {
120+
return begin() == end();
121+
}
122+
123+
/// Adds a new element at the end.
124+
void push_back(const Element &newElement) {
125+
assert(endIndex < slabCapacity);
126+
lastSlab()->template dataFor<Element>()[endIndex++] = newElement;
127+
growIfNeeded();
128+
}
129+
130+
/// Adds a new element at the end.
131+
void push_back(Element &&newElement) {
132+
assert(endIndex < slabCapacity);
133+
lastSlab()->template dataFor<Element>()[endIndex++] = std::move(newElement);
134+
growIfNeeded();
135+
}
136+
137+
/// Removes the last element and returns it.
138+
Element pop_back_val() {
139+
FixedSizeSlab *slab = lastSlab();
140+
if (endIndex > 0)
141+
return std::move(slab->dataFor<Element>()[--endIndex]);
142+
143+
assert(!empty());
144+
145+
slabs.remove(slab);
146+
module.freeSlab(slab);
147+
assert(!slabs.empty());
148+
endIndex = slabCapacity - 1;
149+
return std::move(lastSlab()->template dataFor<Element>()[endIndex]);
150+
}
151+
};
152+
153+
} // namespace swift
154+
155+
#endif

lib/SIL/IR/SILModule.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
using namespace swift;
3636
using namespace Lowering;
3737

38+
STATISTIC(NumSlabsAllocated, "number of slabs allocated in SILModule");
39+
3840
class SILModule::SerializationCallback final
3941
: public DeserializationNotificationHandler {
4042
void didDeserialize(ModuleDecl *M, SILFunction *fn) override {
@@ -116,6 +118,10 @@ SILModule::SILModule(llvm::PointerUnion<FileUnit *, ModuleDecl *> context,
116118
SILModule::~SILModule() {
117119
#ifndef NDEBUG
118120
checkForLeaks();
121+
122+
NumSlabsAllocated += numAllocatedSlabs;
123+
assert(numAllocatedSlabs == freeSlabs.size() && "leaking slabs in SILModule");
124+
freeSlabs.clearAndLeakNodesUnsafely();
119125
#endif
120126

121127
// Decrement ref count for each SILGlobalVariable with static initializers.
@@ -197,6 +203,26 @@ void *SILModule::allocate(unsigned Size, unsigned Align) const {
197203
return BPA.Allocate(Size, Align);
198204
}
199205

206+
FixedSizeSlab *SILModule::allocSlab() {
207+
if (freeSlabs.empty()) {
208+
numAllocatedSlabs++;
209+
return new (*this) FixedSizeSlab();
210+
}
211+
212+
FixedSizeSlab *slab = &*freeSlabs.rbegin();
213+
freeSlabs.remove(slab);
214+
return slab;
215+
}
216+
217+
void SILModule::freeSlab(FixedSizeSlab *slab) {
218+
freeSlabs.push_back(slab);
219+
assert(slab->overflowGuard == FixedSizeSlab::magicNumber);
220+
}
221+
222+
void SILModule::freeAllSlabs(SlabList &slabs) {
223+
freeSlabs.splice(freeSlabs.end(), slabs);
224+
}
225+
200226
void *SILModule::allocateInst(unsigned Size, unsigned Align) const {
201227
return AlignedAlloc(Size, Align);
202228
}

0 commit comments

Comments
 (0)