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(newData, data_, capacity_ * sizeof(Descriptor *));
97 FreeMemory(data_);
98 data_ = newData;
99 capacity_ = newCapacity;
100}
101
102template <bool COPY_VALUES>
103Descriptor *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
111template <bool COPY_VALUES>
112DescriptorStorage<COPY_VALUES>::DescriptorStorage(
113 const char *sourceFile, int line)
114 : terminator_{sourceFile, line} {
115 resize(newCapacity: INITIAL_ALLOC);
116}
117
118template <bool COPY_VALUES>
119DescriptorStorage<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
130template <bool COPY_VALUES>
131void 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
152template <bool COPY_VALUES>
153void 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
163template <bool COPY_VALUES>
164void 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
172inline static ValueStack *getValueStorage(void *opaquePtr) {
173 return static_cast<ValueStack *>(opaquePtr);
174}
175inline static DescriptorStack *getDescriptorStorage(void *opaquePtr) {
176 return static_cast<DescriptorStack *>(opaquePtr);
177}
178
179namespace Fortran::runtime {
180extern "C" {
181void *RTNAME(CreateValueStack)(const char *sourceFile, int line) {
182 return ValueStack::allocate(sourceFile, line);
183}
184
185void RTNAME(PushValue)(void *opaquePtr, const Descriptor &value) {
186 getValueStorage(opaquePtr)->push(value);
187}
188
189void RTNAME(PopValue)(void *opaquePtr, Descriptor &value) {
190 getValueStorage(opaquePtr)->pop(value);
191}
192
193void RTNAME(ValueAt)(void *opaquePtr, uint64_t i, Descriptor &value) {
194 getValueStorage(opaquePtr)->at(i, value);
195}
196
197void RTNAME(DestroyValueStack)(void *opaquePtr) {
198 ValueStack::destroy(getValueStorage(opaquePtr));
199}
200
201void *RTNAME(CreateDescriptorStack)(const char *sourceFile, int line) {
202 return DescriptorStack::allocate(sourceFile, line);
203}
204
205void RTNAME(PushDescriptor)(void *opaquePtr, const Descriptor &value) {
206 getDescriptorStorage(opaquePtr)->push(value);
207}
208
209void RTNAME(PopDescriptor)(void *opaquePtr, Descriptor &value) {
210 getDescriptorStorage(opaquePtr)->pop(value);
211}
212
213void RTNAME(DescriptorAt)(void *opaquePtr, uint64_t i, Descriptor &value) {
214 getValueStorage(opaquePtr)->at(i, value);
215}
216
217void RTNAME(DestroyDescriptorStack)(void *opaquePtr) {
218 DescriptorStack::destroy(getDescriptorStorage(opaquePtr));
219}
220
221} // extern "C"
222} // namespace Fortran::runtime
223

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