Skip to content

Commit 4796e94

Browse files
authored
Make dn_simdhash OOM safe (#117059)
* Make simdhash add operations return a result enum instead of a 0/1 so they can signal OOM and whether an overwrite happened * Make simdhash handle malloc failures during new and resize operations * Explicitly NOMEM if the simdhash add fails due to an out of memory condition * Add dn_simdhash_ght_try_insert * Cleanup some erroneous uses of assert() instead of dn_simdhash_assert() * Interpreter adjustments based on simdhash changes
1 parent 21ae833 commit 4796e94

14 files changed

+256
-116
lines changed

src/coreclr/interpreter/compiler.h

Lines changed: 8 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -9,52 +9,8 @@
99
#include "datastructs.h"
1010
#include "enum_class_flags.h"
1111
#include <new>
12-
13-
#include "../../native/containers/dn-simdhash.h"
14-
#include "../../native/containers/dn-simdhash-specializations.h"
15-
#include "../../native/containers/dn-simdhash-utils.h"
16-
17-
class dn_simdhash_ptr_ptr_holder
18-
{
19-
dn_simdhash_ptr_ptr_t *Value;
20-
public:
21-
dn_simdhash_ptr_ptr_holder() :
22-
Value(nullptr)
23-
{
24-
}
25-
26-
dn_simdhash_ptr_ptr_t* GetValue()
27-
{
28-
if (!Value)
29-
Value = dn_simdhash_ptr_ptr_new(0, nullptr);
30-
return Value;
31-
}
32-
33-
dn_simdhash_ptr_ptr_holder(const dn_simdhash_ptr_ptr_holder&) = delete;
34-
dn_simdhash_ptr_ptr_holder& operator=(const dn_simdhash_ptr_ptr_holder&) = delete;
35-
dn_simdhash_ptr_ptr_holder(dn_simdhash_ptr_ptr_holder&& other)
36-
{
37-
Value = other.Value;
38-
other.Value = nullptr;
39-
}
40-
dn_simdhash_ptr_ptr_holder& operator=(dn_simdhash_ptr_ptr_holder&& other)
41-
{
42-
if (this != &other)
43-
{
44-
if (Value != nullptr)
45-
dn_simdhash_free(Value);
46-
Value = other.Value;
47-
other.Value = nullptr;
48-
}
49-
return *this;
50-
}
51-
52-
~dn_simdhash_ptr_ptr_holder()
53-
{
54-
if (Value != nullptr)
55-
dn_simdhash_free(Value);
56-
}
57-
};
12+
#include "failures.h"
13+
#include "simdhash.h"
5814

5915
struct InterpException
6016
{
@@ -67,16 +23,6 @@ struct InterpException
6723
const CorJitResult m_result;
6824
};
6925

70-
#if defined(__GNUC__) || defined(__clang__)
71-
#define INTERPRETER_NORETURN __attribute__((noreturn))
72-
#else
73-
#define INTERPRETER_NORETURN __declspec(noreturn)
74-
#endif
75-
76-
INTERPRETER_NORETURN void NO_WAY(const char* message);
77-
INTERPRETER_NORETURN void BADCODE(const char* message);
78-
INTERPRETER_NORETURN void NOMEM();
79-
8026
class InterpCompiler;
8127

8228
class InterpDataItemIndexMap
@@ -540,15 +486,11 @@ class InterpCompiler
540486
dn_simdhash_ptr_ptr_holder m_pointerToNameMap;
541487
bool PointerInNameMap(void* ptr)
542488
{
543-
if (dn_simdhash_ptr_ptr_try_get_value(m_pointerToNameMap.GetValue(), ptr, NULL))
544-
{
545-
return true;
546-
}
547-
return false;
489+
return dn_simdhash_ptr_ptr_try_get_value(m_pointerToNameMap.GetValue(), ptr, NULL) != 0;
548490
}
549491
void AddPointerToNameMap(void* ptr, const char* name)
550492
{
551-
dn_simdhash_ptr_ptr_try_add(m_pointerToNameMap.GetValue(), ptr, (void*)name);
493+
checkNoError(dn_simdhash_ptr_ptr_try_add(m_pointerToNameMap.GetValue(), ptr, (void*)name));
552494
}
553495
void PrintNameInPointerMap(void* ptr);
554496
#endif
@@ -865,7 +807,10 @@ int32_t InterpDataItemIndexMap::GetDataItemIndexForT(const T& lookup)
865807
VarSizedDataWithPayload<T>* pLookup = new(hashItemPayload) VarSizedDataWithPayload<T>();
866808
memcpy(&pLookup->payload, &lookup, sizeof(T));
867809

868-
dn_simdhash_ght_insert(hash, (void*)pLookup, (void*)(size_t)dataItemIndex);
810+
checkAddedNew(dn_simdhash_ght_try_insert(
811+
hash, (void*)pLookup, (void*)(size_t)dataItemIndex, DN_SIMDHASH_INSERT_MODE_ENSURE_UNIQUE
812+
));
813+
869814
return dataItemIndex;
870815
}
871816

src/coreclr/interpreter/failures.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#ifndef _FAILURES_H_
5+
#define _FAILURES_H_
6+
7+
#if defined(__GNUC__) || defined(__clang__)
8+
#define INTERPRETER_NORETURN __attribute__((noreturn))
9+
#else
10+
#define INTERPRETER_NORETURN __declspec(noreturn)
11+
#endif
12+
13+
INTERPRETER_NORETURN void NO_WAY(const char* message);
14+
INTERPRETER_NORETURN void BADCODE(const char* message);
15+
INTERPRETER_NORETURN void NOMEM();
16+
17+
#endif

src/coreclr/interpreter/interpreter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
#ifdef DEBUG
2929
extern "C" void assertAbort(const char* why, const char* file, unsigned line);
30+
3031
#undef assert
3132
#define assert(p) (void)((p) || (assertAbort(#p, __FILE__, __LINE__), 0))
3233
#endif // DEBUG

src/coreclr/interpreter/simdhash.h

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#ifndef _SIMDHASH_H_
5+
#define _SIMDHASH_H_
6+
7+
#include "failures.h"
8+
#include "../../native/containers/dn-simdhash.h"
9+
#include "../../native/containers/dn-simdhash-specializations.h"
10+
#include "../../native/containers/dn-simdhash-utils.h"
11+
12+
class dn_simdhash_ptr_ptr_holder
13+
{
14+
dn_simdhash_ptr_ptr_t *Value;
15+
public:
16+
dn_simdhash_ptr_ptr_holder() :
17+
Value(nullptr)
18+
{
19+
}
20+
21+
dn_simdhash_ptr_ptr_t* GetValue()
22+
{
23+
if (!Value)
24+
Value = dn_simdhash_ptr_ptr_new(0, nullptr);
25+
return Value;
26+
}
27+
28+
dn_simdhash_ptr_ptr_holder(const dn_simdhash_ptr_ptr_holder&) = delete;
29+
dn_simdhash_ptr_ptr_holder& operator=(const dn_simdhash_ptr_ptr_holder&) = delete;
30+
dn_simdhash_ptr_ptr_holder(dn_simdhash_ptr_ptr_holder&& other)
31+
{
32+
Value = other.Value;
33+
other.Value = nullptr;
34+
}
35+
dn_simdhash_ptr_ptr_holder& operator=(dn_simdhash_ptr_ptr_holder&& other)
36+
{
37+
if (this != &other)
38+
{
39+
if (Value != nullptr)
40+
dn_simdhash_free(Value);
41+
Value = other.Value;
42+
other.Value = nullptr;
43+
}
44+
return *this;
45+
}
46+
47+
~dn_simdhash_ptr_ptr_holder()
48+
{
49+
if (Value != nullptr)
50+
dn_simdhash_free(Value);
51+
}
52+
};
53+
54+
// Asserts that no error occurred during a simdhash add, but does not mind if no new item was inserted
55+
inline void checkNoError(dn_simdhash_add_result result)
56+
{
57+
if (result == DN_SIMDHASH_OUT_OF_MEMORY)
58+
NOMEM();
59+
else if (result < 0)
60+
NO_WAY("Internal error in simdhash");
61+
}
62+
63+
// Asserts that a new item was successfully inserted into the simdhash
64+
inline void checkAddedNew(dn_simdhash_add_result result)
65+
{
66+
if (result == DN_SIMDHASH_OUT_OF_MEMORY)
67+
NOMEM();
68+
else if (result != DN_SIMDHASH_ADD_INSERTED)
69+
NO_WAY("Failed to add new item into simdhash");
70+
}
71+
72+
#endif // _SIMDHASH_H_

src/coreclr/interpreter/stackmap.cpp

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@
66
#include "interpreter.h"
77
#include "stackmap.h"
88

9-
#include "../../native/containers/dn-simdhash.h"
10-
#include "../../native/containers/dn-simdhash-specializations.h"
11-
#include "../../native/containers/dn-simdhash-utils.h"
12-
13-
extern "C" void assertAbort(const char* why, const char* file, unsigned line);
9+
#include "failures.h"
10+
#include "simdhash.h"
1411

1512
void
1613
dn_simdhash_assert_fail (const char* file, int line, const char* condition) {
14+
#if DEBUG
1715
assertAbort(condition, file, line);
16+
#else
17+
NO_WAY(condition);
18+
#endif
1819
}
1920

2021
thread_local dn_simdhash_ptr_ptr_t *t_sharedStackMapLookup = nullptr;
@@ -24,10 +25,13 @@ InterpreterStackMap* GetInterpreterStackMap(ICorJitInfo* jitInfo, CORINFO_CLASS_
2425
InterpreterStackMap* result = nullptr;
2526
if (!t_sharedStackMapLookup)
2627
t_sharedStackMapLookup = dn_simdhash_ptr_ptr_new(0, nullptr);
28+
if (!t_sharedStackMapLookup)
29+
NOMEM();
30+
2731
if (!dn_simdhash_ptr_ptr_try_get_value(t_sharedStackMapLookup, classHandle, (void **)&result))
2832
{
2933
result = new InterpreterStackMap(jitInfo, classHandle);
30-
dn_simdhash_ptr_ptr_try_add(t_sharedStackMapLookup, classHandle, result);
34+
checkAddedNew(dn_simdhash_ptr_ptr_try_add(t_sharedStackMapLookup, classHandle, result));
3135
}
3236
return result;
3337
}

src/native/containers/dn-simdhash-ght-compatible.c

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ dn_simdhash_ght_replaced (dn_simdhash_ght_data data, void * old_key, void * new_
6969
static unsigned int
7070
dn_simdhash_ght_default_hash (const void * key)
7171
{
72-
// You might think we should avalanche the key bits but in my testing, it doesn't help.
73-
// Right now the default hash function is rarely used anyway
74-
return (unsigned int)(size_t)key;
72+
// You might think we should avalanche the key bits but in my testing, it doesn't help.
73+
// Right now the default hash function is rarely used anyway
74+
return (unsigned int)(size_t)key;
7575
}
7676

7777
static int32_t
@@ -87,6 +87,8 @@ dn_simdhash_ght_new (
8787
)
8888
{
8989
dn_simdhash_ght_t *hash = dn_simdhash_new_internal(&DN_SIMDHASH_T_META, DN_SIMDHASH_T_VTABLE, capacity, allocator);
90+
if (!hash)
91+
return NULL;
9092
// Most users of dn_simdhash_ght are passing a custom comparer, and always doing an indirect call ends up being faster
9193
// than conditionally doing a fast inline check when there's no comparer set. Somewhat counter-intuitive, but true
9294
// on both x64 and arm64. Probably due to the smaller code size and reduced branch predictor pressure.
@@ -107,6 +109,8 @@ dn_simdhash_ght_new_full (
107109
)
108110
{
109111
dn_simdhash_ght_t *hash = dn_simdhash_new_internal(&DN_SIMDHASH_T_META, DN_SIMDHASH_T_VTABLE, capacity, allocator);
112+
if (!hash)
113+
return NULL;
110114
if (!hash_func)
111115
hash_func = dn_simdhash_ght_default_hash;
112116
if (!key_equal_func)
@@ -133,7 +137,14 @@ dn_simdhash_ght_insert_replace (
133137

134138
dn_simdhash_insert_result ok = DN_SIMDHASH_TRY_INSERT_INTERNAL(hash, key, key_hash, value, imode);
135139
if (ok == DN_SIMDHASH_INSERT_NEED_TO_GROW) {
136-
dn_simdhash_buffers_t old_buffers = dn_simdhash_ensure_capacity_internal(hash, dn_simdhash_capacity(hash) + 1);
140+
uint8_t grow_ok;
141+
dn_simdhash_buffers_t old_buffers = dn_simdhash_ensure_capacity_internal(hash, dn_simdhash_capacity(hash) + 1, &grow_ok);
142+
// The ghashtable API doesn't have a way to signal failure, so just assert that we didn't fail to grow
143+
dn_simdhash_assert(grow_ok);
144+
// Don't attempt to insert after a failed grow (it's possible to get here because dn_simdhash_assert is configurable)
145+
if (!grow_ok)
146+
return;
147+
137148
if (old_buffers.buckets) {
138149
DN_SIMDHASH_REHASH_INTERNAL(hash, old_buffers);
139150
dn_simdhash_free_buffers(old_buffers);
@@ -151,11 +162,51 @@ dn_simdhash_ght_insert_replace (
151162
case DN_SIMDHASH_INSERT_KEY_ALREADY_PRESENT:
152163
case DN_SIMDHASH_INSERT_NEED_TO_GROW:
153164
default:
154-
assert(0);
165+
dn_simdhash_assert(!"Internal error in dn_simdhash_ght_insert_replace");
155166
return;
156167
}
157168
}
158169

170+
dn_simdhash_add_result
171+
dn_simdhash_ght_try_insert (
172+
dn_simdhash_ght_t *hash,
173+
void *key, void *value,
174+
dn_simdhash_insert_mode mode
175+
)
176+
{
177+
check_self(hash);
178+
uint32_t key_hash = DN_SIMDHASH_KEY_HASHER(DN_SIMDHASH_GET_DATA(hash), key);
179+
180+
dn_simdhash_insert_result ok = DN_SIMDHASH_TRY_INSERT_INTERNAL(hash, key, key_hash, value, mode);
181+
if (ok == DN_SIMDHASH_INSERT_NEED_TO_GROW) {
182+
uint8_t grow_ok;
183+
dn_simdhash_buffers_t old_buffers = dn_simdhash_ensure_capacity_internal(hash, dn_simdhash_capacity(hash) + 1, &grow_ok);
184+
if (!grow_ok)
185+
return DN_SIMDHASH_OUT_OF_MEMORY;
186+
187+
if (old_buffers.buckets) {
188+
DN_SIMDHASH_REHASH_INTERNAL(hash, old_buffers);
189+
dn_simdhash_free_buffers(old_buffers);
190+
}
191+
ok = DN_SIMDHASH_TRY_INSERT_INTERNAL(hash, key, key_hash, value, mode);
192+
}
193+
194+
switch (ok) {
195+
case DN_SIMDHASH_INSERT_OK_ADDED_NEW:
196+
hash->count++;
197+
return DN_SIMDHASH_ADD_INSERTED;
198+
case DN_SIMDHASH_INSERT_OK_OVERWROTE_EXISTING:
199+
return DN_SIMDHASH_ADD_OVERWROTE;
200+
case DN_SIMDHASH_INSERT_KEY_ALREADY_PRESENT:
201+
return DN_SIMDHASH_ADD_FAILED;
202+
203+
case DN_SIMDHASH_INSERT_NEED_TO_GROW:
204+
default:
205+
dn_simdhash_assert(!"Internal error in dn_simdhash_ght_insert_replace");
206+
return DN_SIMDHASH_INTERNAL_ERROR;
207+
}
208+
}
209+
159210
void *
160211
dn_simdhash_ght_get_value_or_default (
161212
dn_simdhash_ght_t *hash, void * key

src/native/containers/dn-simdhash-ght-compatible.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ dn_simdhash_ght_get_value_or_default (
3838
dn_simdhash_ght_t *hash, void * key
3939
);
4040

41+
dn_simdhash_add_result
42+
dn_simdhash_ght_try_insert (
43+
dn_simdhash_ght_t *hash,
44+
void *key, void *value,
45+
dn_simdhash_insert_mode mode
46+
);
47+
4148
#ifdef __cplusplus
4249
}
4350
#endif // __cplusplus

src/native/containers/dn-simdhash-specialization-declarations.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ DN_SIMDHASH_T_PTR
5252
DN_SIMDHASH_NEW (uint32_t capacity, dn_allocator_t *allocator);
5353
#endif
5454

55-
uint8_t
55+
dn_simdhash_add_result
5656
DN_SIMDHASH_TRY_ADD (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, DN_SIMDHASH_VALUE_T value);
5757

58-
uint8_t
58+
dn_simdhash_add_result
5959
DN_SIMDHASH_TRY_ADD_WITH_HASH (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, uint32_t key_hash, DN_SIMDHASH_VALUE_T value);
6060

6161
// result is an optional parameter that will be set to the value of the item if it was found.

0 commit comments

Comments
 (0)