[K/N] Crash with OOM on large array allocations ^KT-54659
Merge-request: KT-MR-7507 Merged-by: Alexander Shabalin <Alexander.Shabalin@jetbrains.com>
This commit is contained in:
committed by
Space Team
parent
cc4d22b4c9
commit
5ef9a5a240
@@ -3441,6 +3441,24 @@ standaloneTest("stress_gc_allocations") {
|
|||||||
flags = ['-tr', '-opt-in=kotlin.native.internal.InternalForKotlinNative']
|
flags = ['-tr', '-opt-in=kotlin.native.internal.InternalForKotlinNative']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
standaloneTest("array_out_of_memory") {
|
||||||
|
source = "runtime/memory/array_out_of_memory.kt"
|
||||||
|
flags = ['-tr']
|
||||||
|
switch(project.target.architecture) {
|
||||||
|
case Architecture.X64:
|
||||||
|
case Architecture.ARM64:
|
||||||
|
break;
|
||||||
|
case Architecture.X86:
|
||||||
|
case Architecture.ARM32:
|
||||||
|
case Architecture.MIPS32:
|
||||||
|
case Architecture.MIPSEL32:
|
||||||
|
case Architecture.WASM32:
|
||||||
|
expectedExitStatusChecker = { it != 0 }
|
||||||
|
outputChecker = { s -> s.contains("Out of memory trying to allocate") }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
standaloneTest("mpp1") {
|
standaloneTest("mpp1") {
|
||||||
source = "codegen/mpp/mpp1.kt"
|
source = "codegen/mpp/mpp1.kt"
|
||||||
flags = ['-tr', '-Xmulti-platform']
|
flags = ['-tr', '-Xmulti-platform']
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
fun testArrayAllocation(size: Int) {
|
||||||
|
val arr = IntArray(size)
|
||||||
|
// Force a write into the memory.
|
||||||
|
// TODO: How to make sure the optimizer never deletes this write?
|
||||||
|
arr[size - 1] = 42
|
||||||
|
assertEquals(42, arr[size - 1])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun sanity() {
|
||||||
|
// Should always succeed everywhere
|
||||||
|
testArrayAllocation(1 shl 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test() {
|
||||||
|
// Will fail on 32 bits.
|
||||||
|
testArrayAllocation(1 shl 30)
|
||||||
|
}
|
||||||
@@ -54,7 +54,7 @@ ALWAYS_INLINE ObjHeader* gc::GC::ThreadData::CreateObject(const TypeInfo* typeIn
|
|||||||
return impl_->objectFactoryThreadQueue().CreateObject(typeInfo);
|
return impl_->objectFactoryThreadQueue().CreateObject(typeInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
ALWAYS_INLINE ArrayHeader* gc::GC::ThreadData::CreateArray(const TypeInfo* typeInfo, uint32_t elements) {
|
ALWAYS_INLINE ArrayHeader* gc::GC::ThreadData::CreateArray(const TypeInfo* typeInfo, uint32_t elements) noexcept {
|
||||||
return impl_->objectFactoryThreadQueue().CreateArray(typeInfo, elements);
|
return impl_->objectFactoryThreadQueue().CreateArray(typeInfo, elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public:
|
|||||||
void ClearForTests() noexcept;
|
void ClearForTests() noexcept;
|
||||||
|
|
||||||
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept;
|
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept;
|
||||||
ArrayHeader* CreateArray(const TypeInfo* typeInfo, uint32_t elements);
|
ArrayHeader* CreateArray(const TypeInfo* typeInfo, uint32_t elements) noexcept;
|
||||||
|
|
||||||
void OnStoppedForGC() noexcept;
|
void OnStoppedForGC() noexcept;
|
||||||
void OnSuspendForGC() noexcept;
|
void OnSuspendForGC() noexcept;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ ALWAYS_INLINE ObjHeader* gc::GC::ThreadData::CreateObject(const TypeInfo* typeIn
|
|||||||
return impl_->objectFactoryThreadQueue().CreateObject(typeInfo);
|
return impl_->objectFactoryThreadQueue().CreateObject(typeInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
ALWAYS_INLINE ArrayHeader* gc::GC::ThreadData::CreateArray(const TypeInfo* typeInfo, uint32_t elements) {
|
ALWAYS_INLINE ArrayHeader* gc::GC::ThreadData::CreateArray(const TypeInfo* typeInfo, uint32_t elements) noexcept {
|
||||||
return impl_->objectFactoryThreadQueue().CreateArray(typeInfo, elements);
|
return impl_->objectFactoryThreadQueue().CreateArray(typeInfo, elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ ALWAYS_INLINE ObjHeader* gc::GC::ThreadData::CreateObject(const TypeInfo* typeIn
|
|||||||
return impl_->objectFactoryThreadQueue().CreateObject(typeInfo);
|
return impl_->objectFactoryThreadQueue().CreateObject(typeInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
ALWAYS_INLINE ArrayHeader* gc::GC::ThreadData::CreateArray(const TypeInfo* typeInfo, uint32_t elements) {
|
ALWAYS_INLINE ArrayHeader* gc::GC::ThreadData::CreateArray(const TypeInfo* typeInfo, uint32_t elements) noexcept {
|
||||||
return impl_->objectFactoryThreadQueue().CreateArray(typeInfo, elements);
|
return impl_->objectFactoryThreadQueue().CreateArray(typeInfo, elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
#include <cstddef> // for offsetof
|
#include <cstddef> // for offsetof
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "KAssert.h"
|
#include "KAssert.h"
|
||||||
|
#include "Alignment.hpp"
|
||||||
#include "Atomic.h"
|
#include "Atomic.h"
|
||||||
#include "Cleaner.h"
|
#include "Cleaner.h"
|
||||||
#include "CompilerConstants.hpp"
|
#include "CompilerConstants.hpp"
|
||||||
@@ -92,7 +94,7 @@ ALWAYS_INLINE bool IsStrictMemoryModel() noexcept {
|
|||||||
|
|
||||||
inline constexpr ObjectPoolAllocator<char> objectAllocator;
|
inline constexpr ObjectPoolAllocator<char> objectAllocator;
|
||||||
|
|
||||||
typedef uint32_t container_size_t;
|
using container_size_t = size_t;
|
||||||
|
|
||||||
// Granularity of arena container chunks.
|
// Granularity of arena container chunks.
|
||||||
constexpr container_size_t kContainerAlignment = 1024;
|
constexpr container_size_t kContainerAlignment = 1024;
|
||||||
@@ -913,7 +915,7 @@ class ArenaContainer {
|
|||||||
// Places an array of certain type in this container. Note that array_type_info
|
// Places an array of certain type in this container. Note that array_type_info
|
||||||
// is type info for an array, not for an individual element. Also note that exactly
|
// is type info for an array, not for an individual element. Also note that exactly
|
||||||
// same operation could be used to place strings.
|
// same operation could be used to place strings.
|
||||||
ArrayHeader* PlaceArray(const TypeInfo* array_type_info, container_size_t count);
|
ArrayHeader* PlaceArray(const TypeInfo* array_type_info, uint32_t count);
|
||||||
|
|
||||||
ObjHeader** getSlot();
|
ObjHeader** getSlot();
|
||||||
|
|
||||||
@@ -962,23 +964,23 @@ inline ContainerHeader* clearRemoved(ContainerHeader* container) {
|
|||||||
reinterpret_cast<uintptr_t>(container) & ~static_cast<uintptr_t>(1));
|
reinterpret_cast<uintptr_t>(container) & ~static_cast<uintptr_t>(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline container_size_t alignUp(container_size_t size, int alignment) {
|
|
||||||
return (size + alignment - 1) & ~(alignment - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline ContainerHeader* realShareableContainer(ContainerHeader* container) {
|
inline ContainerHeader* realShareableContainer(ContainerHeader* container) {
|
||||||
RuntimeAssert(container->shareable(), "Only makes sense on shareable objects");
|
RuntimeAssert(container->shareable(), "Only makes sense on shareable objects");
|
||||||
return containerFor(reinterpret_cast<ObjHeader*>(container + 1));
|
return containerFor(reinterpret_cast<ObjHeader*>(container + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint32_t arrayObjectSize(const TypeInfo* typeInfo, uint32_t count) {
|
inline uint64_t arrayObjectSize(const TypeInfo* typeInfo, uint32_t count) {
|
||||||
// Note: array body is aligned, but for size computation it is enough to align the sum.
|
|
||||||
static_assert(kObjectAlignment % alignof(KLong) == 0, "");
|
static_assert(kObjectAlignment % alignof(KLong) == 0, "");
|
||||||
static_assert(kObjectAlignment % alignof(KDouble) == 0, "");
|
static_assert(kObjectAlignment % alignof(KDouble) == 0, "");
|
||||||
return alignUp(sizeof(ArrayHeader) - typeInfo->instanceSize_ * count, kObjectAlignment);
|
// -(int32_t min) * uint32_t max cannot overflow uint64_t. And are capped
|
||||||
|
// at about half of uint64_t max.
|
||||||
|
uint64_t membersSize = static_cast<uint64_t>(-typeInfo->instanceSize_) * count;
|
||||||
|
// Note: array body is aligned, but for size computation it is enough to align the sum.
|
||||||
|
return AlignUp<uint64_t>(sizeof(ArrayHeader) + membersSize, kObjectAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint32_t arrayObjectSize(const ArrayHeader* obj) {
|
inline container_size_t arrayObjectSize(const ArrayHeader* obj) {
|
||||||
|
// Only used for already allocated arrays. Cannov overflow size_t.
|
||||||
return arrayObjectSize(obj->type_info(), obj->count_);
|
return arrayObjectSize(obj->type_info(), obj->count_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -990,7 +992,7 @@ inline container_size_t objectSize(const ObjHeader* obj) {
|
|||||||
arrayObjectSize(obj->array())
|
arrayObjectSize(obj->array())
|
||||||
:
|
:
|
||||||
type_info->instanceSize_);
|
type_info->instanceSize_);
|
||||||
return alignUp(size, kObjectAlignment);
|
return kotlin::AlignUp(size, kObjectAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename func>
|
template <typename func>
|
||||||
@@ -1079,7 +1081,7 @@ ContainerHeader* allocContainer(MemoryState* state, size_t size) {
|
|||||||
if (state != nullptr)
|
if (state != nullptr)
|
||||||
state->allocSinceLastGc += size;
|
state->allocSinceLastGc += size;
|
||||||
#endif
|
#endif
|
||||||
result = new (allocateInObjectPool(alignUp(size, kObjectAlignment))) ContainerHeader();
|
result = new (allocateInObjectPool(kotlin::AlignUp(size, kObjectAlignment))) ContainerHeader();
|
||||||
atomicAdd(&allocCount, 1);
|
atomicAdd(&allocCount, 1);
|
||||||
}
|
}
|
||||||
if (state != nullptr) {
|
if (state != nullptr) {
|
||||||
@@ -1780,11 +1782,11 @@ inline ArenaContainer* initedArena(ObjHeader** auxSlot) {
|
|||||||
return arena;
|
return arena;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline size_t containerSize(const ContainerHeader* container) {
|
inline container_size_t containerSize(const ContainerHeader* container) {
|
||||||
size_t result = 0;
|
container_size_t result = 0;
|
||||||
const ObjHeader* obj = reinterpret_cast<const ObjHeader*>(container + 1);
|
const ObjHeader* obj = reinterpret_cast<const ObjHeader*>(container + 1);
|
||||||
for (uint32_t object = 0; object < container->objectCount(); object++) {
|
for (uint32_t object = 0; object < container->objectCount(); object++) {
|
||||||
size_t size = objectSize(obj);
|
container_size_t size = objectSize(obj);
|
||||||
result += size;
|
result += size;
|
||||||
obj = reinterpret_cast<ObjHeader*>(reinterpret_cast<uintptr_t>(obj) + size);
|
obj = reinterpret_cast<ObjHeader*>(reinterpret_cast<uintptr_t>(obj) + size);
|
||||||
}
|
}
|
||||||
@@ -3188,7 +3190,7 @@ void ObjHeader::destroyMetaObject(ObjHeader* object) {
|
|||||||
|
|
||||||
void ObjectContainer::Init(MemoryState* state, const TypeInfo* typeInfo) {
|
void ObjectContainer::Init(MemoryState* state, const TypeInfo* typeInfo) {
|
||||||
RuntimeAssert(typeInfo->instanceSize_ >= 0, "Must be an object");
|
RuntimeAssert(typeInfo->instanceSize_ >= 0, "Must be an object");
|
||||||
uint32_t allocSize = sizeof(ContainerHeader) + typeInfo->instanceSize_;
|
container_size_t allocSize = sizeof(ContainerHeader) + typeInfo->instanceSize_;
|
||||||
header_ = allocContainer(state, allocSize);
|
header_ = allocContainer(state, allocSize);
|
||||||
RuntimeCheck(header_ != nullptr, "Cannot alloc memory");
|
RuntimeCheck(header_ != nullptr, "Cannot alloc memory");
|
||||||
// One object in this container, no need to set.
|
// One object in this container, no need to set.
|
||||||
@@ -3201,8 +3203,12 @@ void ObjectContainer::Init(MemoryState* state, const TypeInfo* typeInfo) {
|
|||||||
|
|
||||||
void ArrayContainer::Init(MemoryState* state, const TypeInfo* typeInfo, uint32_t elements) {
|
void ArrayContainer::Init(MemoryState* state, const TypeInfo* typeInfo, uint32_t elements) {
|
||||||
RuntimeAssert(typeInfo->instanceSize_ < 0, "Must be an array");
|
RuntimeAssert(typeInfo->instanceSize_ < 0, "Must be an array");
|
||||||
uint32_t allocSize =
|
uint64_t dataSize = arrayObjectSize(typeInfo, elements);
|
||||||
sizeof(ContainerHeader) + arrayObjectSize(typeInfo, elements);
|
uint64_t allocSize = sizeof(ContainerHeader) + dataSize;
|
||||||
|
if (allocSize > std::numeric_limits<size_t>::max()) {
|
||||||
|
konan::consoleErrorf("Out of memory trying to allocate %" PRIu64 " bytes. Aborting.\n", allocSize);
|
||||||
|
konan::abort();
|
||||||
|
}
|
||||||
header_ = allocContainer(state, allocSize);
|
header_ = allocContainer(state, allocSize);
|
||||||
RuntimeCheck(header_ != nullptr, "Cannot alloc memory");
|
RuntimeCheck(header_ != nullptr, "Cannot alloc memory");
|
||||||
// One object in this container, no need to set.
|
// One object in this container, no need to set.
|
||||||
@@ -3211,7 +3217,7 @@ void ArrayContainer::Init(MemoryState* state, const TypeInfo* typeInfo, uint32_t
|
|||||||
// header->refCount_ is zero initialized by allocContainer().
|
// header->refCount_ is zero initialized by allocContainer().
|
||||||
GetPlace()->count_ = elements;
|
GetPlace()->count_ = elements;
|
||||||
SetHeader(GetPlace()->obj(), typeInfo);
|
SetHeader(GetPlace()->obj(), typeInfo);
|
||||||
OBJECT_ALLOC_EVENT(memoryState, arrayObjectSize(typeInfo, elements), GetPlace()->obj())
|
OBJECT_ALLOC_EVENT(memoryState, dataSize, GetPlace()->obj())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: store arena containers in some reuseable data structure, similar to
|
// TODO: store arena containers in some reuseable data structure, similar to
|
||||||
@@ -3239,7 +3245,7 @@ void ArenaContainer::Deinit() {
|
|||||||
|
|
||||||
bool ArenaContainer::allocContainer(container_size_t minSize) {
|
bool ArenaContainer::allocContainer(container_size_t minSize) {
|
||||||
auto size = minSize + sizeof(ContainerHeader) + sizeof(ContainerChunk);
|
auto size = minSize + sizeof(ContainerHeader) + sizeof(ContainerChunk);
|
||||||
size = alignUp(size, kContainerAlignment);
|
size = kotlin::AlignUp(size, kContainerAlignment);
|
||||||
// TODO: keep simple cache of container chunks.
|
// TODO: keep simple cache of container chunks.
|
||||||
ContainerChunk* result = new (allocateInObjectPool(size)) ContainerChunk();
|
ContainerChunk* result = new (allocateInObjectPool(size)) ContainerChunk();
|
||||||
RuntimeCheck(result != nullptr, "Cannot alloc memory");
|
RuntimeCheck(result != nullptr, "Cannot alloc memory");
|
||||||
@@ -3254,7 +3260,7 @@ bool ArenaContainer::allocContainer(container_size_t minSize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void* ArenaContainer::place(container_size_t size) {
|
void* ArenaContainer::place(container_size_t size) {
|
||||||
size = alignUp(size, kObjectAlignment);
|
size = kotlin::AlignUp(size, kObjectAlignment);
|
||||||
// Fast path.
|
// Fast path.
|
||||||
if (current_ + size < end_) {
|
if (current_ + size < end_) {
|
||||||
void* result = current_;
|
void* result = current_;
|
||||||
@@ -3282,7 +3288,7 @@ ObjHeader** ArenaContainer::getSlot() {
|
|||||||
|
|
||||||
ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) {
|
ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) {
|
||||||
RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object");
|
RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object");
|
||||||
uint32_t size = type_info->instanceSize_;
|
container_size_t size = type_info->instanceSize_;
|
||||||
ObjHeader* result = reinterpret_cast<ObjHeader*>(place(size));
|
ObjHeader* result = reinterpret_cast<ObjHeader*>(place(size));
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -3295,12 +3301,16 @@ ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) {
|
|||||||
|
|
||||||
ArrayHeader* ArenaContainer::PlaceArray(const TypeInfo* type_info, uint32_t count) {
|
ArrayHeader* ArenaContainer::PlaceArray(const TypeInfo* type_info, uint32_t count) {
|
||||||
RuntimeAssert(type_info->instanceSize_ < 0, "must be an array");
|
RuntimeAssert(type_info->instanceSize_ < 0, "must be an array");
|
||||||
container_size_t size = arrayObjectSize(type_info, count);
|
uint64_t size = arrayObjectSize(type_info, count);
|
||||||
|
if (size > std::numeric_limits<size_t>::max()) {
|
||||||
|
konan::consoleErrorf("Out of memory trying to allocate %" PRIu64 " bytes. Aborting.\n", size);
|
||||||
|
konan::abort();
|
||||||
|
}
|
||||||
ArrayHeader* result = reinterpret_cast<ArrayHeader*>(place(size));
|
ArrayHeader* result = reinterpret_cast<ArrayHeader*>(place(size));
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
OBJECT_ALLOC_EVENT(memoryState, arrayObjectSize(type_info, count), result->obj())
|
OBJECT_ALLOC_EVENT(memoryState, size, result->obj())
|
||||||
currentChunk_->asHeader()->incObjectCount();
|
currentChunk_->asHeader()->incObjectCount();
|
||||||
setHeader(result->obj(), type_info);
|
setHeader(result->obj(), type_info);
|
||||||
result->count_ = count;
|
result->count_ = count;
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ namespace kotlin {
|
|||||||
|
|
||||||
constexpr size_t kObjectAlignment = 8;
|
constexpr size_t kObjectAlignment = 8;
|
||||||
|
|
||||||
constexpr inline size_t AlignUp(size_t size, size_t alignment) {
|
template <typename T>
|
||||||
|
constexpr T AlignUp(T size, T alignment) {
|
||||||
return (size + alignment - 1) & ~(alignment - 1);
|
return (size + alignment - 1) & ~(alignment - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#if __has_include(<optional>)
|
#if __has_include(<optional>)
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#elif __has_include(<experimental/optional>)
|
#elif __has_include(<experimental/optional>)
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
#define RUNTIME_MM_OBJECT_FACTORY_H
|
#define RUNTIME_MM_OBJECT_FACTORY_H
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cinttypes>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
#include "Alignment.hpp"
|
#include "Alignment.hpp"
|
||||||
#include "Exceptions.h"
|
|
||||||
#include "FinalizerHooks.hpp"
|
#include "FinalizerHooks.hpp"
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
#include "Mutex.hpp"
|
#include "Mutex.hpp"
|
||||||
@@ -54,9 +54,9 @@ public:
|
|||||||
public:
|
public:
|
||||||
~Node() = default;
|
~Node() = default;
|
||||||
|
|
||||||
constexpr static size_t GetSizeForDataSize(size_t dataSize) noexcept {
|
constexpr static uint64_t GetSizeForDataSize(uint64_t dataSize) noexcept {
|
||||||
size_t dataSizeAligned = AlignUp(dataSize, DataAlignment);
|
uint64_t dataSizeAligned = AlignUp<uint64_t>(dataSize, DataAlignment);
|
||||||
size_t totalSize = AlignUp(sizeof(Node) + dataSizeAligned, DataAlignment);
|
uint64_t totalSize = AlignUp<uint64_t>(sizeof(Node) + dataSizeAligned, DataAlignment);
|
||||||
return totalSize;
|
return totalSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,14 +86,19 @@ public:
|
|||||||
|
|
||||||
Node() noexcept = default;
|
Node() noexcept = default;
|
||||||
|
|
||||||
static unique_ptr<Node> Create(Allocator& allocator, size_t dataSize) noexcept {
|
static unique_ptr<Node> Create(Allocator& allocator, uint64_t dataSize) noexcept {
|
||||||
auto totalSize = GetSizeForDataSize(dataSize);
|
auto totalSize = GetSizeForDataSize(dataSize);
|
||||||
RuntimeAssert(
|
void* ptr = nullptr;
|
||||||
DataOffset() + dataSize <= totalSize, "totalSize %zu is not enough to fit data %zu at offset %zu", totalSize, dataSize,
|
if (totalSize <= std::numeric_limits<size_t>::max()) {
|
||||||
DataOffset());
|
RuntimeAssert(
|
||||||
void* ptr = allocator.Alloc(totalSize);
|
DataOffset() + dataSize <= totalSize, "totalSize %" PRIu64 " is not enough to fit data %" PRIu64 " at offset %zu", totalSize, dataSize,
|
||||||
|
DataOffset());
|
||||||
|
ptr = allocator.Alloc(totalSize);
|
||||||
|
}
|
||||||
if (!ptr) {
|
if (!ptr) {
|
||||||
konan::consoleErrorf("Out of memory trying to allocate %zu bytes. Aborting.\n", totalSize);
|
// TODO: This should throw OutOfMemoryError in the future if we add hard memory limits instead
|
||||||
|
// of limiting at virtual address space boundary.
|
||||||
|
konan::consoleErrorf("Out of memory trying to allocate %" PRIu64 " bytes. Aborting.\n", totalSize);
|
||||||
konan::abort();
|
konan::abort();
|
||||||
}
|
}
|
||||||
RuntimeAssert(IsAligned(ptr, DataAlignment), "Allocator returned unaligned to %zu pointer %p", DataAlignment, ptr);
|
RuntimeAssert(IsAligned(ptr, DataAlignment), "Allocator returned unaligned to %zu pointer %p", DataAlignment, ptr);
|
||||||
@@ -134,7 +139,7 @@ public:
|
|||||||
|
|
||||||
size_t size() const noexcept { return size_; }
|
size_t size() const noexcept { return size_; }
|
||||||
|
|
||||||
Node& Insert(size_t dataSize) noexcept {
|
Node& Insert(uint64_t dataSize) noexcept {
|
||||||
AssertCorrect();
|
AssertCorrect();
|
||||||
auto node = Node::Create(allocator_, dataSize);
|
auto node = Node::Create(allocator_, dataSize);
|
||||||
auto* nodePtr = node.get();
|
auto* nodePtr = node.get();
|
||||||
@@ -513,7 +518,8 @@ public:
|
|||||||
|
|
||||||
static size_t ObjectAllocatedSize(const TypeInfo* typeInfo) noexcept {
|
static size_t ObjectAllocatedSize(const TypeInfo* typeInfo) noexcept {
|
||||||
RuntimeAssert(!typeInfo->IsArray(), "Must not be an array");
|
RuntimeAssert(!typeInfo->IsArray(), "Must not be an array");
|
||||||
size_t allocSize = ObjectAllocatedDataSize(typeInfo);
|
// Only used for already allocated objects. Cannot overflow size_t
|
||||||
|
auto allocSize = ObjectAllocatedDataSize(typeInfo);
|
||||||
return Storage::Node::GetSizeForDataSize(allocSize);
|
return Storage::Node::GetSizeForDataSize(allocSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,21 +536,14 @@ public:
|
|||||||
|
|
||||||
static size_t ArrayAllocatedSize(const TypeInfo* typeInfo, uint32_t count) noexcept {
|
static size_t ArrayAllocatedSize(const TypeInfo* typeInfo, uint32_t count) noexcept {
|
||||||
RuntimeAssert(typeInfo->IsArray(), "Must be an array");
|
RuntimeAssert(typeInfo->IsArray(), "Must be an array");
|
||||||
size_t allocSize = ArrayAllocatedDataSize(typeInfo, count);
|
// Only used for already allocated arrays. Cannot overflow size_t
|
||||||
|
auto allocSize = ArrayAllocatedDataSize(typeInfo, count);
|
||||||
return Storage::Node::GetSizeForDataSize(allocSize);
|
return Storage::Node::GetSizeForDataSize(allocSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayHeader* CreateArray(const TypeInfo* typeInfo, uint32_t count) {
|
ArrayHeader* CreateArray(const TypeInfo* typeInfo, uint32_t count) noexcept {
|
||||||
RuntimeAssert(typeInfo->IsArray(), "Must be an array");
|
RuntimeAssert(typeInfo->IsArray(), "Must be an array");
|
||||||
size_t allocSize = ArrayAllocatedDataSize(typeInfo, count);
|
auto allocSize = ArrayAllocatedDataSize(typeInfo, count);
|
||||||
// On 32-bit systems, overflow can happen in several places along
|
|
||||||
// the size calculation. If overflow did not happen in the
|
|
||||||
// multiplication checked in the previous call, but any of the
|
|
||||||
// subsequent additions overflowed, then the overflowed value will
|
|
||||||
// be small compared to the number of entries in the array.
|
|
||||||
if (Storage::Node::GetSizeForDataSize(allocSize) < count) {
|
|
||||||
ThrowOutOfMemoryError();
|
|
||||||
}
|
|
||||||
auto& node = producer_.Insert(allocSize);
|
auto& node = producer_.Insert(allocSize);
|
||||||
auto* heapArray = new (node.Data()) HeapArrayHeader();
|
auto* heapArray = new (node.Data()) HeapArrayHeader();
|
||||||
auto* array = &heapArray->array;
|
auto* array = &heapArray->array;
|
||||||
@@ -567,13 +566,12 @@ public:
|
|||||||
return AlignUp(sizeof(HeapObjHeader) + membersSize, kObjectAlignment);
|
return AlignUp(sizeof(HeapObjHeader) + membersSize, kObjectAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t ArrayAllocatedDataSize(const TypeInfo* typeInfo, uint32_t count) noexcept {
|
static uint64_t ArrayAllocatedDataSize(const TypeInfo* typeInfo, uint32_t count) noexcept {
|
||||||
size_t membersSize;
|
// -(int32_t min) * uint32_t max cannot overflow uint64_t. And are capped
|
||||||
if (__builtin_mul_overflow(static_cast<size_t>(-typeInfo->instanceSize_), count, &membersSize)) {
|
// at about half of uint64_t max.
|
||||||
ThrowOutOfMemoryError();
|
uint64_t membersSize = static_cast<uint64_t>(-typeInfo->instanceSize_) * count;
|
||||||
}
|
|
||||||
// Note: array body is aligned, but for size computation it is enough to align the sum.
|
// Note: array body is aligned, but for size computation it is enough to align the sum.
|
||||||
return AlignUp(sizeof(HeapArrayHeader) + membersSize, kObjectAlignment);
|
return AlignUp<uint64_t>(sizeof(HeapArrayHeader) + membersSize, kObjectAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
typename Storage::Producer producer_;
|
typename Storage::Producer producer_;
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ OBJ_GETTER(mm::AllocateObject, ThreadData* threadData, const TypeInfo* typeInfo)
|
|||||||
RETURN_OBJ(object);
|
RETURN_OBJ(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
OBJ_GETTER(mm::AllocateArray, ThreadData* threadData, const TypeInfo* typeInfo, uint32_t elements) {
|
OBJ_GETTER(mm::AllocateArray, ThreadData* threadData, const TypeInfo* typeInfo, uint32_t elements) noexcept {
|
||||||
AssertThreadState(threadData, ThreadState::kRunnable);
|
AssertThreadState(threadData, ThreadState::kRunnable);
|
||||||
// TODO: Make this work with GCs that can stop thread at any point.
|
// TODO: Make this work with GCs that can stop thread at any point.
|
||||||
auto* array = threadData->gc().CreateArray(typeInfo, static_cast<uint32_t>(elements));
|
auto* array = threadData->gc().CreateArray(typeInfo, static_cast<uint32_t>(elements));
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ void SetHeapRefAtomic(ObjHeader** location, ObjHeader* value) noexcept;
|
|||||||
OBJ_GETTER(ReadHeapRefAtomic, ObjHeader** location) noexcept;
|
OBJ_GETTER(ReadHeapRefAtomic, ObjHeader** location) noexcept;
|
||||||
OBJ_GETTER(CompareAndSwapHeapRef, ObjHeader** location, ObjHeader* expected, ObjHeader* value) noexcept;
|
OBJ_GETTER(CompareAndSwapHeapRef, ObjHeader** location, ObjHeader* expected, ObjHeader* value) noexcept;
|
||||||
OBJ_GETTER(AllocateObject, ThreadData* threadData, const TypeInfo* typeInfo) noexcept;
|
OBJ_GETTER(AllocateObject, ThreadData* threadData, const TypeInfo* typeInfo) noexcept;
|
||||||
OBJ_GETTER(AllocateArray, ThreadData* threadData, const TypeInfo* typeInfo, uint32_t elements);
|
OBJ_GETTER(AllocateArray, ThreadData* threadData, const TypeInfo* typeInfo, uint32_t elements) noexcept;
|
||||||
|
|
||||||
// This does not take into account how much storage did the underlying allocator (malloc/mimalloc) reserved.
|
// This does not take into account how much storage did the underlying allocator (malloc/mimalloc) reserved.
|
||||||
size_t GetAllocatedHeapSize(ObjHeader* object) noexcept;
|
size_t GetAllocatedHeapSize(ObjHeader* object) noexcept;
|
||||||
|
|||||||
Reference in New Issue
Block a user