1 | //===-- runtime/temporary-stack.cpp ---------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | // Implements std::vector like storage for a dynamically resizable number of |
10 | // temporaries. For use in HLFIR lowering. |
11 | |
12 | #include "flang/Runtime/temporary-stack.h" |
13 | #include "terminator.h" |
14 | #include "flang/ISO_Fortran_binding_wrapper.h" |
15 | #include "flang/Runtime/assign.h" |
16 | #include "flang/Runtime/descriptor.h" |
17 | #include "flang/Runtime/memory.h" |
18 | |
19 | namespace { |
20 | |
21 | using namespace Fortran::runtime; |
22 | |
23 | // the number of elements to allocate when first creating the vector |
24 | constexpr size_t INITIAL_ALLOC = 8; |
25 | |
26 | /// To store C style data. Does not run constructors/destructors. |
27 | /// Not using std::vector to avoid linking the runtime library to stdc++ |
28 | template <bool COPY_VALUES> class DescriptorStorage final { |
29 | using size_type = uint64_t; // see checkedMultiply() |
30 | |
31 | size_type capacity_{0}; |
32 | size_type size_{0}; |
33 | Descriptor **data_{nullptr}; |
34 | Terminator terminator_; |
35 | |
36 | // return true on overflow |
37 | static bool checkedMultiply(size_type x, size_type y, size_type &res); |
38 | |
39 | void resize(size_type newCapacity); |
40 | |
41 | Descriptor *cloneDescriptor(const Descriptor &source); |
42 | |
43 | public: |
44 | DescriptorStorage(const char *sourceFile, int line); |
45 | ~DescriptorStorage(); |
46 | |
47 | // `new` but using the runtime allocation API |
48 | static inline DescriptorStorage *allocate(const char *sourceFile, int line) { |
49 | Terminator term{sourceFile, line}; |
50 | void *ptr = AllocateMemoryOrCrash(term, sizeof(DescriptorStorage)); |
51 | return new (ptr) DescriptorStorage{sourceFile, line}; |
52 | } |
53 | |
54 | // `delete` but using the runtime allocation API |
55 | static inline void destroy(DescriptorStorage *instance) { |
56 | instance->~DescriptorStorage(); |
57 | FreeMemory(instance); |
58 | } |
59 | |
60 | // clones a descriptor into this storage |
61 | void push(const Descriptor &source); |
62 | |
63 | // out must be big enough to hold a descriptor of the right rank and addendum |
64 | void pop(Descriptor &out); |
65 | |
66 | // out must be big enough to hold a descriptor of the right rank and addendum |
67 | void at(size_type i, Descriptor &out); |
68 | }; |
69 | |
70 | using ValueStack = DescriptorStorage</*COPY_VALUES=*/true>; |
71 | using DescriptorStack = DescriptorStorage</*COPY_VALUES=*/false>; |
72 | } // namespace |
73 | |
74 | template <bool COPY_VALUES> |
75 | bool DescriptorStorage<COPY_VALUES>::checkedMultiply( |
76 | size_type x, size_type y, size_type &res) { |
77 | // TODO: c++20 [[unlikely]] |
78 | if (x > UINT64_MAX / y) { |
79 | return true; |
80 | } |
81 | res = x * y; |
82 | return false; |
83 | } |
84 | |
85 | template <bool COPY_VALUES> |
86 | void DescriptorStorage<COPY_VALUES>::resize(size_type newCapacity) { |
87 | if (newCapacity <= capacity_) { |
88 | return; |
89 | } |
90 | size_type bytes; |
91 | if (checkedMultiply(newCapacity, sizeof(Descriptor *), bytes)) { |
92 | terminator_.Crash("temporary-stack: out of memory" ); |
93 | } |
94 | Descriptor **newData = |
95 | static_cast<Descriptor **>(AllocateMemoryOrCrash(terminator_, bytes)); |
96 | memcpy(newData, data_, capacity_ * sizeof(Descriptor *)); |
97 | FreeMemory(data_); |
98 | data_ = newData; |
99 | capacity_ = newCapacity; |
100 | } |
101 | |
102 | template <bool COPY_VALUES> |
103 | Descriptor *DescriptorStorage<COPY_VALUES>::cloneDescriptor( |
104 | const Descriptor &source) { |
105 | const std::size_t bytes = source.SizeInBytes(); |
106 | void *memory = AllocateMemoryOrCrash(terminator_, bytes); |
107 | Descriptor *desc = new (memory) Descriptor{source}; |
108 | return desc; |
109 | } |
110 | |
111 | template <bool COPY_VALUES> |
112 | DescriptorStorage<COPY_VALUES>::DescriptorStorage( |
113 | const char *sourceFile, int line) |
114 | : terminator_{sourceFile, line} { |
115 | resize(newCapacity: INITIAL_ALLOC); |
116 | } |
117 | |
118 | template <bool COPY_VALUES> |
119 | DescriptorStorage<COPY_VALUES>::~DescriptorStorage() { |
120 | for (size_type i = 0; i < size_; ++i) { |
121 | Descriptor *element = data_[i]; |
122 | if constexpr (COPY_VALUES) { |
123 | element->Destroy(false, true); |
124 | } |
125 | FreeMemory(element); |
126 | } |
127 | FreeMemory(data_); |
128 | } |
129 | |
130 | template <bool COPY_VALUES> |
131 | void DescriptorStorage<COPY_VALUES>::push(const Descriptor &source) { |
132 | if (size_ == capacity_) { |
133 | size_type newSize; |
134 | if (checkedMultiply(x: capacity_, y: 2, res&: newSize)) { |
135 | terminator_.Crash("temporary-stack: out of address space" ); |
136 | } |
137 | resize(newCapacity: newSize); |
138 | } |
139 | data_[size_] = cloneDescriptor(source); |
140 | Descriptor &box = *data_[size_]; |
141 | size_ += 1; |
142 | |
143 | if constexpr (COPY_VALUES) { |
144 | // copy the data pointed to by the box |
145 | box.set_base_addr(nullptr); |
146 | box.Allocate(); |
147 | RTNAME(AssignTemporary) |
148 | (box, source, terminator_.sourceFileName(), terminator_.sourceLine()); |
149 | } |
150 | } |
151 | |
152 | template <bool COPY_VALUES> |
153 | void DescriptorStorage<COPY_VALUES>::pop(Descriptor &out) { |
154 | if (size_ == 0) { |
155 | terminator_.Crash("temporary-stack: pop empty storage" ); |
156 | } |
157 | size_ -= 1; |
158 | Descriptor *ptr = data_[size_]; |
159 | out = *ptr; // Descriptor::operator= handles the different sizes |
160 | FreeMemory(ptr); |
161 | } |
162 | |
163 | template <bool COPY_VALUES> |
164 | void DescriptorStorage<COPY_VALUES>::at(size_type i, Descriptor &out) { |
165 | if (i >= size_) { |
166 | terminator_.Crash("temporary-stack: out of bounds access" ); |
167 | } |
168 | Descriptor *ptr = data_[i]; |
169 | out = *ptr; // Descriptor::operator= handles the different sizes |
170 | } |
171 | |
172 | inline static ValueStack *getValueStorage(void *opaquePtr) { |
173 | return static_cast<ValueStack *>(opaquePtr); |
174 | } |
175 | inline static DescriptorStack *getDescriptorStorage(void *opaquePtr) { |
176 | return static_cast<DescriptorStack *>(opaquePtr); |
177 | } |
178 | |
179 | namespace Fortran::runtime { |
180 | extern "C" { |
181 | void *RTNAME(CreateValueStack)(const char *sourceFile, int line) { |
182 | return ValueStack::allocate(sourceFile, line); |
183 | } |
184 | |
185 | void RTNAME(PushValue)(void *opaquePtr, const Descriptor &value) { |
186 | getValueStorage(opaquePtr)->push(value); |
187 | } |
188 | |
189 | void RTNAME(PopValue)(void *opaquePtr, Descriptor &value) { |
190 | getValueStorage(opaquePtr)->pop(value); |
191 | } |
192 | |
193 | void RTNAME(ValueAt)(void *opaquePtr, uint64_t i, Descriptor &value) { |
194 | getValueStorage(opaquePtr)->at(i, value); |
195 | } |
196 | |
197 | void RTNAME(DestroyValueStack)(void *opaquePtr) { |
198 | ValueStack::destroy(getValueStorage(opaquePtr)); |
199 | } |
200 | |
201 | void *RTNAME(CreateDescriptorStack)(const char *sourceFile, int line) { |
202 | return DescriptorStack::allocate(sourceFile, line); |
203 | } |
204 | |
205 | void RTNAME(PushDescriptor)(void *opaquePtr, const Descriptor &value) { |
206 | getDescriptorStorage(opaquePtr)->push(value); |
207 | } |
208 | |
209 | void RTNAME(PopDescriptor)(void *opaquePtr, Descriptor &value) { |
210 | getDescriptorStorage(opaquePtr)->pop(value); |
211 | } |
212 | |
213 | void RTNAME(DescriptorAt)(void *opaquePtr, uint64_t i, Descriptor &value) { |
214 | getValueStorage(opaquePtr)->at(i, value); |
215 | } |
216 | |
217 | void RTNAME(DestroyDescriptorStack)(void *opaquePtr) { |
218 | DescriptorStack::destroy(getDescriptorStorage(opaquePtr)); |
219 | } |
220 | |
221 | } // extern "C" |
222 | } // namespace Fortran::runtime |
223 | |