mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-28 07:36:06 +00:00

Certain functions in glibc have "nonnull" attributes on pointer parameters (even in cases where passing a null pointer should be handled correctly). There are a few cases of such calls in flang: memcmp and memcpy with the length parameter set to 0. Avoid passing a null pointer to these functions, since the conflict with the nonnull attribute could cause an undefined behavior. This was detected by the undefined behavior sanitizer.
228 lines
6.7 KiB
C++
228 lines
6.7 KiB
C++
//===-- 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
|