| 1 | //===-- lib/runtime/temporary-stack.cpp -------------------------*- C++ -*-===// |
| 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 "flang-rt/runtime/descriptor.h" |
| 14 | #include "flang-rt/runtime/memory.h" |
| 15 | #include "flang-rt/runtime/terminator.h" |
| 16 | #include "flang/Common/ISO_Fortran_binding_wrapper.h" |
| 17 | #include "flang/Runtime/assign.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" 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 | |
| 107 | template <bool COPY_VALUES> |
| 108 | Descriptor *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 | |
| 116 | template <bool COPY_VALUES> |
| 117 | DescriptorStorage<COPY_VALUES>::DescriptorStorage( |
| 118 | const char *sourceFile, int line) |
| 119 | : terminator_{sourceFile, line} { |
| 120 | resize(INITIAL_ALLOC); |
| 121 | } |
| 122 | |
| 123 | template <bool COPY_VALUES> |
| 124 | DescriptorStorage<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 | |
| 135 | template <bool COPY_VALUES> |
| 136 | void DescriptorStorage<COPY_VALUES>::push(const Descriptor &source) { |
| 137 | if (size_ == capacity_) { |
| 138 | size_type newSize; |
| 139 | if (checkedMultiply(capacity_, 2, newSize)) { |
| 140 | terminator_.Crash("temporary-stack: out of address space" ); |
| 141 | } |
| 142 | resize(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(kNoAsyncObject); |
| 152 | RTNAME(AssignTemporary) |
| 153 | (box, source, terminator_.sourceFileName(), terminator_.sourceLine()); |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | template <bool COPY_VALUES> |
| 158 | void 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 | |
| 168 | template <bool COPY_VALUES> |
| 169 | void 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 | |
| 177 | inline static ValueStack *getValueStorage(void *opaquePtr) { |
| 178 | return static_cast<ValueStack *>(opaquePtr); |
| 179 | } |
| 180 | inline static DescriptorStack *getDescriptorStorage(void *opaquePtr) { |
| 181 | return static_cast<DescriptorStack *>(opaquePtr); |
| 182 | } |
| 183 | |
| 184 | namespace Fortran::runtime { |
| 185 | extern "C" { |
| 186 | void *RTNAME(CreateValueStack)(const char *sourceFile, int line) { |
| 187 | return ValueStack::allocate(sourceFile, line); |
| 188 | } |
| 189 | |
| 190 | void RTNAME(PushValue)(void *opaquePtr, const Descriptor &value) { |
| 191 | getValueStorage(opaquePtr)->push(value); |
| 192 | } |
| 193 | |
| 194 | void RTNAME(PopValue)(void *opaquePtr, Descriptor &value) { |
| 195 | getValueStorage(opaquePtr)->pop(value); |
| 196 | } |
| 197 | |
| 198 | void RTNAME(ValueAt)(void *opaquePtr, uint64_t i, Descriptor &value) { |
| 199 | getValueStorage(opaquePtr)->at(i, value); |
| 200 | } |
| 201 | |
| 202 | void RTNAME(DestroyValueStack)(void *opaquePtr) { |
| 203 | ValueStack::destroy(instance: getValueStorage(opaquePtr)); |
| 204 | } |
| 205 | |
| 206 | void *RTNAME(CreateDescriptorStack)(const char *sourceFile, int line) { |
| 207 | return DescriptorStack::allocate(sourceFile: sourceFile, line: line); |
| 208 | } |
| 209 | |
| 210 | void RTNAME(PushDescriptor)(void *opaquePtr, const Descriptor &value) { |
| 211 | getDescriptorStorage(opaquePtr)->push(value); |
| 212 | } |
| 213 | |
| 214 | void RTNAME(PopDescriptor)(void *opaquePtr, Descriptor &value) { |
| 215 | getDescriptorStorage(opaquePtr)->pop(value); |
| 216 | } |
| 217 | |
| 218 | void RTNAME(DescriptorAt)(void *opaquePtr, uint64_t i, Descriptor &value) { |
| 219 | getValueStorage(opaquePtr)->at(i, value); |
| 220 | } |
| 221 | |
| 222 | void RTNAME(DestroyDescriptorStack)(void *opaquePtr) { |
| 223 | DescriptorStack::destroy(getDescriptorStorage(opaquePtr)); |
| 224 | } |
| 225 | |
| 226 | } // extern "C" |
| 227 | } // namespace Fortran::runtime |
| 228 | |