| //===-- runtime/temporary-stack.cpp ---------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| // Implements std::vector like storage for a dynamically resizable number of |
| // temporaries. For use in HLFIR lowering. |
| |
| #include "flang/Runtime/temporary-stack.h" |
| #include "terminator.h" |
| #include "flang/ISO_Fortran_binding_wrapper.h" |
| #include "flang/Runtime/assign.h" |
| #include "flang/Runtime/descriptor.h" |
| #include "flang/Runtime/memory.h" |
| |
| namespace { |
| |
| using namespace Fortran::runtime; |
| |
| // the number of elements to allocate when first creating the vector |
| constexpr size_t INITIAL_ALLOC = 8; |
| |
| /// To store C style data. Does not run constructors/destructors. |
| /// Not using std::vector to avoid linking the runtime library to stdc++ |
| template <bool COPY_VALUES> class DescriptorStorage final { |
| using size_type = uint64_t; // see checkedMultiply() |
| |
| size_type capacity_{0}; |
| size_type size_{0}; |
| Descriptor **data_{nullptr}; |
| Terminator terminator_; |
| |
| // return true on overflow |
| static bool checkedMultiply(size_type x, size_type y, size_type &res); |
| |
| void resize(size_type newCapacity); |
| |
| Descriptor *cloneDescriptor(const Descriptor &source); |
| |
| public: |
| DescriptorStorage(const char *sourceFile, int line); |
| ~DescriptorStorage(); |
| |
| // `new` but using the runtime allocation API |
| static inline DescriptorStorage *allocate(const char *sourceFile, int line) { |
| Terminator term{sourceFile, line}; |
| void *ptr = AllocateMemoryOrCrash(term, sizeof(DescriptorStorage)); |
| return new (ptr) DescriptorStorage{sourceFile, line}; |
| } |
| |
| // `delete` but using the runtime allocation API |
| static inline void destroy(DescriptorStorage *instance) { |
| instance->~DescriptorStorage(); |
| FreeMemory(instance); |
| } |
| |
| // clones a descriptor into this storage |
| void push(const Descriptor &source); |
| |
| // out must be big enough to hold a descriptor of the right rank and addendum |
| void pop(Descriptor &out); |
| |
| // out must be big enough to hold a descriptor of the right rank and addendum |
| void at(size_type i, Descriptor &out); |
| }; |
| |
| using ValueStack = DescriptorStorage</*COPY_VALUES=*/true>; |
| using DescriptorStack = DescriptorStorage</*COPY_VALUES=*/false>; |
| } // namespace |
| |
| template <bool COPY_VALUES> |
| bool DescriptorStorage<COPY_VALUES>::checkedMultiply( |
| size_type x, size_type y, size_type &res) { |
| // TODO: c++20 [[unlikely]] |
| if (x > UINT64_MAX / y) { |
| return true; |
| } |
| res = x * y; |
| return false; |
| } |
| |
| template <bool COPY_VALUES> |
| void DescriptorStorage<COPY_VALUES>::resize(size_type newCapacity) { |
| if (newCapacity <= capacity_) { |
| return; |
| } |
| size_type bytes; |
| if (checkedMultiply(newCapacity, sizeof(Descriptor *), bytes)) { |
| terminator_.Crash("temporary-stack: out of memory"); |
| } |
| Descriptor **newData = |
| static_cast<Descriptor **>(AllocateMemoryOrCrash(terminator_, bytes)); |
| // "memcpy" in glibc has a "nonnull" attribute on the source pointer. |
| // Avoid passing a null pointer, since it would result in an undefined |
| // behavior. |
| if (data_ != nullptr) { |
| memcpy(newData, data_, capacity_ * sizeof(Descriptor *)); |
| FreeMemory(data_); |
| } |
| data_ = newData; |
| capacity_ = newCapacity; |
| } |
| |
| template <bool COPY_VALUES> |
| Descriptor *DescriptorStorage<COPY_VALUES>::cloneDescriptor( |
| const Descriptor &source) { |
| const std::size_t bytes = source.SizeInBytes(); |
| void *memory = AllocateMemoryOrCrash(terminator_, bytes); |
| Descriptor *desc = new (memory) Descriptor{source}; |
| return desc; |
| } |
| |
| template <bool COPY_VALUES> |
| DescriptorStorage<COPY_VALUES>::DescriptorStorage( |
| const char *sourceFile, int line) |
| : terminator_{sourceFile, line} { |
| resize(INITIAL_ALLOC); |
| } |
| |
| template <bool COPY_VALUES> |
| DescriptorStorage<COPY_VALUES>::~DescriptorStorage() { |
| for (size_type i = 0; i < size_; ++i) { |
| Descriptor *element = data_[i]; |
| if constexpr (COPY_VALUES) { |
| element->Destroy(false, true); |
| } |
| FreeMemory(element); |
| } |
| FreeMemory(data_); |
| } |
| |
| template <bool COPY_VALUES> |
| void DescriptorStorage<COPY_VALUES>::push(const Descriptor &source) { |
| if (size_ == capacity_) { |
| size_type newSize; |
| if (checkedMultiply(capacity_, 2, newSize)) { |
| terminator_.Crash("temporary-stack: out of address space"); |
| } |
| resize(newSize); |
| } |
| data_[size_] = cloneDescriptor(source); |
| Descriptor &box = *data_[size_]; |
| size_ += 1; |
| |
| if constexpr (COPY_VALUES) { |
| // copy the data pointed to by the box |
| box.set_base_addr(nullptr); |
| box.Allocate(); |
| RTNAME(AssignTemporary) |
| (box, source, terminator_.sourceFileName(), terminator_.sourceLine()); |
| } |
| } |
| |
| template <bool COPY_VALUES> |
| void DescriptorStorage<COPY_VALUES>::pop(Descriptor &out) { |
| if (size_ == 0) { |
| terminator_.Crash("temporary-stack: pop empty storage"); |
| } |
| size_ -= 1; |
| Descriptor *ptr = data_[size_]; |
| out = *ptr; // Descriptor::operator= handles the different sizes |
| FreeMemory(ptr); |
| } |
| |
| template <bool COPY_VALUES> |
| void DescriptorStorage<COPY_VALUES>::at(size_type i, Descriptor &out) { |
| if (i >= size_) { |
| terminator_.Crash("temporary-stack: out of bounds access"); |
| } |
| Descriptor *ptr = data_[i]; |
| out = *ptr; // Descriptor::operator= handles the different sizes |
| } |
| |
| inline static ValueStack *getValueStorage(void *opaquePtr) { |
| return static_cast<ValueStack *>(opaquePtr); |
| } |
| inline static DescriptorStack *getDescriptorStorage(void *opaquePtr) { |
| return static_cast<DescriptorStack *>(opaquePtr); |
| } |
| |
| namespace Fortran::runtime { |
| extern "C" { |
| void *RTNAME(CreateValueStack)(const char *sourceFile, int line) { |
| return ValueStack::allocate(sourceFile, line); |
| } |
| |
| void RTNAME(PushValue)(void *opaquePtr, const Descriptor &value) { |
| getValueStorage(opaquePtr)->push(value); |
| } |
| |
| void RTNAME(PopValue)(void *opaquePtr, Descriptor &value) { |
| getValueStorage(opaquePtr)->pop(value); |
| } |
| |
| void RTNAME(ValueAt)(void *opaquePtr, uint64_t i, Descriptor &value) { |
| getValueStorage(opaquePtr)->at(i, value); |
| } |
| |
| void RTNAME(DestroyValueStack)(void *opaquePtr) { |
| ValueStack::destroy(getValueStorage(opaquePtr)); |
| } |
| |
| void *RTNAME(CreateDescriptorStack)(const char *sourceFile, int line) { |
| return DescriptorStack::allocate(sourceFile, line); |
| } |
| |
| void RTNAME(PushDescriptor)(void *opaquePtr, const Descriptor &value) { |
| getDescriptorStorage(opaquePtr)->push(value); |
| } |
| |
| void RTNAME(PopDescriptor)(void *opaquePtr, Descriptor &value) { |
| getDescriptorStorage(opaquePtr)->pop(value); |
| } |
| |
| void RTNAME(DescriptorAt)(void *opaquePtr, uint64_t i, Descriptor &value) { |
| getValueStorage(opaquePtr)->at(i, value); |
| } |
| |
| void RTNAME(DestroyDescriptorStack)(void *opaquePtr) { |
| DescriptorStack::destroy(getDescriptorStorage(opaquePtr)); |
| } |
| |
| } // extern "C" |
| } // namespace Fortran::runtime |