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
19namespace {
20
21using namespace Fortran::runtime;
22
23// the number of elements to allocate when first creating the vector
24constexpr 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++
28template <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
43public:
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
70using ValueStack = DescriptorStorage</*COPY_VALUES=*/true>;
71using DescriptorStack = DescriptorStorage</*COPY_VALUES=*/false>;
72} // namespace
73
74template <bool COPY_VALUES>
75bool 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
85template <bool COPY_VALUES>
86void 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" in glibc has a "nonnull" attribute on the source pointer.
97 // Avoid passing a null pointer, since it would result in an undefined
98 // behavior.
99 if (data_ != nullptr) {
100 memcpy(newData, data_, capacity_ * sizeof(Descriptor *));
101 FreeMemory(data_);
102 }
103 data_ = newData;
104 capacity_ = newCapacity;
105}
106
107template <bool COPY_VALUES>
108Descriptor *DescriptorStorage<COPY_VALUES>::cloneDescriptor(
109 const Descriptor &source) {
110 const std::size_t bytes = source.SizeInBytes();
111 void *memory = AllocateMemoryOrCrash(terminator_, bytes);
112 Descriptor *desc = new (memory) Descriptor{source};
113 return desc;
114}
115
116template <bool COPY_VALUES>
117DescriptorStorage<COPY_VALUES>::DescriptorStorage(
118 const char *sourceFile, int line)
119 : terminator_{sourceFile, line} {
120 resize(newCapacity: INITIAL_ALLOC);
121}
122
123template <bool COPY_VALUES>
124DescriptorStorage<COPY_VALUES>::~DescriptorStorage() {
125 for (size_type i = 0; i < size_; ++i) {
126 Descriptor *element = data_[i];
127 if constexpr (COPY_VALUES) {
128 element->Destroy(false, true);
129 }
130 FreeMemory(element);
131 }
132 FreeMemory(data_);
133}
134
135template <bool COPY_VALUES>
136void DescriptorStorage<COPY_VALUES>::push(const Descriptor &source) {
137 if (size_ == capacity_) {
138 size_type newSize;
139 if (checkedMultiply(x: capacity_, y: 2, res&: newSize)) {
140 terminator_.Crash("temporary-stack: out of address space");
141 }
142 resize(newCapacity: newSize);
143 }
144 data_[size_] = cloneDescriptor(source);
145 Descriptor &box = *data_[size_];
146 size_ += 1;
147
148 if constexpr (COPY_VALUES) {
149 // copy the data pointed to by the box
150 box.set_base_addr(nullptr);
151 box.Allocate();
152 RTNAME(AssignTemporary)
153 (box, source, terminator_.sourceFileName(), terminator_.sourceLine());
154 }
155}
156
157template <bool COPY_VALUES>
158void DescriptorStorage<COPY_VALUES>::pop(Descriptor &out) {
159 if (size_ == 0) {
160 terminator_.Crash("temporary-stack: pop empty storage");
161 }
162 size_ -= 1;
163 Descriptor *ptr = data_[size_];
164 out = *ptr; // Descriptor::operator= handles the different sizes
165 FreeMemory(ptr);
166}
167
168template <bool COPY_VALUES>
169void DescriptorStorage<COPY_VALUES>::at(size_type i, Descriptor &out) {
170 if (i >= size_) {
171 terminator_.Crash("temporary-stack: out of bounds access");
172 }
173 Descriptor *ptr = data_[i];
174 out = *ptr; // Descriptor::operator= handles the different sizes
175}
176
177inline static ValueStack *getValueStorage(void *opaquePtr) {
178 return static_cast<ValueStack *>(opaquePtr);
179}
180inline static DescriptorStack *getDescriptorStorage(void *opaquePtr) {
181 return static_cast<DescriptorStack *>(opaquePtr);
182}
183
184namespace Fortran::runtime {
185extern "C" {
186void *RTNAME(CreateValueStack)(const char *sourceFile, int line) {
187 return ValueStack::allocate(sourceFile, line);
188}
189
190void RTNAME(PushValue)(void *opaquePtr, const Descriptor &value) {
191 getValueStorage(opaquePtr)->push(value);
192}
193
194void RTNAME(PopValue)(void *opaquePtr, Descriptor &value) {
195 getValueStorage(opaquePtr)->pop(value);
196}
197
198void RTNAME(ValueAt)(void *opaquePtr, uint64_t i, Descriptor &value) {
199 getValueStorage(opaquePtr)->at(i, value);
200}
201
202void RTNAME(DestroyValueStack)(void *opaquePtr) {
203 ValueStack::destroy(getValueStorage(opaquePtr));
204}
205
206void *RTNAME(CreateDescriptorStack)(const char *sourceFile, int line) {
207 return DescriptorStack::allocate(sourceFile, line);
208}
209
210void RTNAME(PushDescriptor)(void *opaquePtr, const Descriptor &value) {
211 getDescriptorStorage(opaquePtr)->push(value);
212}
213
214void RTNAME(PopDescriptor)(void *opaquePtr, Descriptor &value) {
215 getDescriptorStorage(opaquePtr)->pop(value);
216}
217
218void RTNAME(DescriptorAt)(void *opaquePtr, uint64_t i, Descriptor &value) {
219 getValueStorage(opaquePtr)->at(i, value);
220}
221
222void RTNAME(DestroyDescriptorStack)(void *opaquePtr) {
223 DescriptorStack::destroy(getDescriptorStorage(opaquePtr));
224}
225
226} // extern "C"
227} // namespace Fortran::runtime
228

source code of flang/runtime/temporary-stack.cpp