forked from emul8/tlib
-
Notifications
You must be signed in to change notification settings - Fork 40
Expand file tree
/
Copy pathhash-table-store-test.c
More file actions
345 lines (284 loc) · 14.1 KB
/
hash-table-store-test.c
File metadata and controls
345 lines (284 loc) · 14.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
#include "hash-table-store-test.h"
#include "cpu.h"
#include "infrastructure.h"
#include "tcg-op.h"
#include "tcg-op-atomic.h"
#include "atomic-intrinsics.h"
#include "debug.h"
static uint64_t hst_guest_address_mask = 0;
static uint64_t hst_table_entries_count = 0;
static void calculate_hst_mask(const uint8_t store_table_bits)
{
// _bits means how many bits are used in the addresses, not how many bits
// large the contents is. I.e. if an entry is 64 bits large it uses 3 bits.
const uint64_t content_bits = sizeof(uintptr_t) * 8 - (uint64_t)store_table_bits;
const uint64_t table_size_bytes = 1ULL << content_bits;
const uint64_t entry_size_bytes = sizeof(store_table_entry_t);
hst_table_entries_count = table_size_bytes / entry_size_bytes;
#ifdef DEBUG
uint64_t size_kib = table_size_bytes >> 10;
uint64_t size_mib = table_size_bytes >> 20;
uint64_t size_gib = table_size_bytes >> 30;
if(size_kib == 0) {
tlib_printf(LOG_LEVEL_DEBUG, "Store table is %u B (%u bits)", table_size_bytes, store_table_bits);
} else if(size_mib == 0) {
tlib_printf(LOG_LEVEL_DEBUG, "Store table is %u KiB (%u bits)", size_kib, store_table_bits);
} else if(size_gib == 0) {
tlib_printf(LOG_LEVEL_DEBUG, "Store table is %u MiB (%u bits)", size_mib, store_table_bits);
} else {
tlib_printf(LOG_LEVEL_DEBUG, "Store table is %u GiB (%u bits)", size_gib, store_table_bits);
}
#endif
const uint64_t n_bits = (1ULL << store_table_bits) - 1;
const uint64_t table_mask = n_bits << content_bits;
const uint64_t interior_mask = ~table_mask;
const uint64_t entry_mask = sizeof(store_table_entry_t) - 1;
const uint64_t alignment_mask = ~entry_mask;
hst_guest_address_mask = interior_mask & alignment_mask;
}
void initialize_store_table(store_table_entry_t *store_table, uint8_t store_table_bits, bool after_deserialization)
{
calculate_hst_mask(store_table_bits);
tlib_printf(LOG_LEVEL_DEBUG, "%s: initializing with ptr 0x%016llx", __func__, store_table);
tlib_assert(hst_table_entries_count != 0);
bool all_entries_unlocked = true;
/* Initialize store table. */
for(uint64_t i = 0; i < hst_table_entries_count; i++) {
if(after_deserialization) {
/*
* Every entry must already be unlocked when deserializing, because
* we assume that when serializing the current instruction gets to
* finish executing, meaning it _should_ have been able to release
* its store table lock. If the lock was never released, something
* has gone wrong.
*/
uint32_t locked_by_cpu_id = store_table[i].lock;
if(unlikely(locked_by_cpu_id != HST_UNLOCKED)) {
tlib_printf(LOG_LEVEL_WARNING,
"%s: serialized store table entry at offset 0x%x contains dangling lock for cpu %u", __func__, i,
locked_by_cpu_id);
all_entries_unlocked = false;
}
} else {
/*
* Initialize a new entry from scratch.
*/
store_table[i].last_accessed_by_core_id = HST_NO_CORE;
store_table[i].lock = HST_UNLOCKED;
}
}
tlib_assert(all_entries_unlocked);
}
/* Hashes `guest_address` and places the result in `hashed_address`. */
static void gen_hash_address(CPUState *env, TCGv_hostptr hashed_address, TCGv_guestptr guest_address)
{
tcg_gen_mov_tl(hashed_address, guest_address);
// Zero out upper bits of address, to make room for the address of the table.
// Zero out lower bits, both for alignment and to make room for the fine-grained lock.
tcg_gen_andi_i64(hashed_address, hashed_address, hst_guest_address_mask);
// Replace the upper bits of address with start of table.
uintptr_t store_table_address = (uintptr_t)env->store_table;
tcg_gen_ori_i64(hashed_address, hashed_address, store_table_address);
}
// Abstract away what the actual core id comes from.
uint32_t get_core_id(CPUState *env)
{
// Use the atomic_id as the "core id" required for HST.
return env->atomic_id;
}
/* Result is 1 if check succeeds, 0 otherwise. */
void gen_store_table_check(CPUState *env, TCGv result, TCGv_guestptr guest_address)
{
// Check will always succeed when there is only one core present, as there is no other core to access the reserved address.
if(!are_multiple_cpus_registered()) {
tcg_gen_movi_tl(result, 1);
return;
}
TCGv_hostptr hashed_address = tcg_temp_new_hostptr();
gen_hash_address(env, hashed_address, guest_address);
// Load core id from store table, to see which core last accessed the address.
tcg_gen_ld32u_tl(result, hashed_address, offsetof(store_table_entry_t, last_accessed_by_core_id));
// See if the current core is the one who last accessed the reserved address.
// returns 1 if condition succeeds, 0 otherwise.
tcg_gen_setcondi_tl(TCG_COND_EQ, result, result, get_core_id(env));
tcg_temp_free_hostptr(hashed_address);
}
/* Debug assert that ensures the current core owns the hash table entry lock
* of the entry associated with the given `guest_address`.
*/
void ensure_entry_locked(CPUState *env, TCGv_guestptr guest_address, const char *function_name)
{
#ifdef DEBUG
TCGv_hostptr hashed_address = tcg_temp_local_new_hostptr();
gen_hash_address(env, hashed_address, guest_address);
TCGv_i32 lock = tcg_temp_local_new_i32();
// Load lock from store table, to see which core holds it.
tcg_gen_ld32u_tl(lock, hashed_address, offsetof(store_table_entry_t, lock));
int done = gen_new_label();
uint32_t core_id = get_core_id(env);
// Check if the lock is owned by the current core.
tcg_gen_brcondi_i32(TCG_COND_EQ, lock, core_id, done);
// Lock isn't owned by the current core, abort.
generate_log(0, "%s: %s: hash table entry lock for guest address:", __func__, function_name);
generate_var_log(guest_address);
generate_log(0, "is not held by current core (id %u), it is held by:", core_id);
generate_var_log(lock);
generate_backtrace_print();
gen_helper_abort();
gen_set_label(done);
tcg_temp_free_i32(lock);
tcg_temp_free_hostptr(hashed_address);
#endif
}
void gen_store_table_set(CPUState *env, TCGv_guestptr guest_address)
{
// There's no need for updating the hash table when there's only a single core present, since it already tracks its own
// reservations.
if(!are_multiple_cpus_registered()) {
return;
}
ensure_entry_locked(env, guest_address, __func__);
TCGv_hostptr hashed_address = tcg_temp_local_new_hostptr();
gen_hash_address(env, hashed_address, guest_address);
TCGv_i32 core_id = tcg_const_i32(get_core_id(env));
// The hashed address now points to the table entry for the core id, so store it there.
// Note that the table entry update occurs atomically, with a single store.
tcg_gen_st32_tl(core_id, hashed_address, offsetof(store_table_entry_t, last_accessed_by_core_id));
// Memory barrier to ensure that this store doesn't get reordered with a store
// that will release the lock.
tcg_gen_mb(TCG_MO_ST_ST);
tcg_temp_free_hostptr(hashed_address);
tcg_temp_free_i32(core_id);
}
static void gen_store_table_lock_address(CPUState *env, TCGv_guestptr guest_address, tcg_target_long locked_address_offset)
{
// Skip the hash table locks when there is only one core present, since no synchronization is necessary.
if(!are_multiple_cpus_registered()) {
return;
}
TCGv_hostptr hashed_address = tcg_temp_local_new_hostptr();
gen_hash_address(env, hashed_address, guest_address);
// Add the offset of the lock field, since we want to access the lock and not the core id.
TCGv_hostptr lock_address = tcg_temp_local_new_hostptr();
tcg_gen_addi_i64(lock_address, hashed_address, offsetof(store_table_entry_t, lock));
TCGv_i32 expected_lock = tcg_const_local_i32(HST_UNLOCKED);
// Acquiring the lock means storing this core's id.
TCGv_i32 new_lock = tcg_const_local_i32(get_core_id(env));
/* We need to check two cases, hence the two branching instructions
* after the initial CAS. The table entry is either unlocked,
* locked by another thread, or locked by the current thread.
*
* │
* ┌───────────────────────▼────────────────────────────┐
* │result = CAS(expected_lock, lock_address, new_lock) |◄────────┐
* └───────────────────────┬────────────────────────────┘ │
* true ┌────────▼──────────┐ │
* ┌──────┼ result == core_id │ "already locked by me?" │
* ▼ └────────┬──────────┘ │
* abort │ false │
* ┌────────▼───────────────┐ true │
* "locked?" │ result != HST_UNLOCKED ┼──────────────────────┘
* └────────┬───────────────┘
* │ false
* ▼
* lock acquired!
*/
int retry = gen_new_label();
gen_set_label(retry);
TCGv_i32 result = tcg_temp_local_new_i32();
// Optimistically try to atomically acquire the lock (only succeeds if it's currently unlocked).
tcg_gen_atomic_compare_and_swap_host_intrinsic_i32(result, expected_lock, lock_address, new_lock);
int start_retrying = gen_new_label();
// Locks are not reentrant, so it is an implementation bug
// if the lock is already taken by this core.
tcg_gen_brcondi_i32(TCG_COND_NE, result, get_core_id(env), start_retrying);
// Abort if result == get_core_id(env) (reentrant lock attempt)
TCGv_hostptr abort_message =
tcg_const_hostptr((uintptr_t)"Attempted to acquire a store table lock that this CPU already holds");
gen_helper_abort_message(abort_message);
tcg_temp_free_hostptr(abort_message);
gen_set_label(start_retrying);
// If result != HST_UNLOCKED, then the lock is taken, and we should keep retrying.
tcg_gen_brcondi_i32(TCG_COND_NE, result, HST_UNLOCKED, retry);
// Lock is now owned by the current core.
// Update cpu's currently locked address.
tcg_gen_st_tl(guest_address, cpu_env, locked_address_offset);
tcg_temp_free_hostptr(hashed_address);
tcg_temp_free_hostptr(lock_address);
tcg_temp_free_i32(expected_lock);
tcg_temp_free_i32(new_lock);
tcg_temp_free_i32(result);
}
void gen_store_table_lock(CPUState *env, TCGv_guestptr guest_address)
{
gen_store_table_lock_address(env, guest_address, offsetof(CPUState, locked_address));
}
static void gen_store_table_unlock_address(CPUState *env, TCGv_guestptr guest_address, tcg_target_long locked_address_offset)
{
// There is no reason to unlock address when there is only one core present, as it was never locked to begin with.
if(!are_multiple_cpus_registered()) {
return;
}
ensure_entry_locked(env, guest_address, __func__);
TCGv_hostptr hashed_address = tcg_temp_new_hostptr();
gen_hash_address(env, hashed_address, guest_address);
TCGv_i32 unlocked = tcg_const_i32(HST_UNLOCKED);
// Unlock the table entry.
tcg_gen_st32_tl(unlocked, hashed_address, offsetof(store_table_entry_t, lock));
// Emit a barrier to ensure that the store is visible to other processors.
tcg_gen_mb(TCG_MO_ST_ST);
// Update cpu's currently locked address.
TCGv_guestptr null = tcg_const_tl(0);
tcg_gen_st_tl(null, cpu_env, locked_address_offset);
tcg_temp_free(null);
tcg_temp_free_hostptr(hashed_address);
tcg_temp_free_i32(unlocked);
}
void gen_store_table_unlock(CPUState *env, TCGv_guestptr guest_address)
{
gen_store_table_unlock_address(env, guest_address, offsetof(CPUState, locked_address));
}
uintptr_t address_hash(CPUState *env, target_ulong guest_address)
{
uintptr_t table_offset = (uintptr_t)env->store_table;
uintptr_t hashed_address = guest_address;
hashed_address &= hst_guest_address_mask;
hashed_address |= table_offset;
return hashed_address;
}
static void gen_store_table_lock_high(CPUState *env, TCGv_guestptr guest_address)
{
gen_store_table_lock_address(env, guest_address, offsetof(CPUState, locked_address_high));
}
static void gen_store_table_unlock_high(CPUState *env, TCGv_guestptr guest_address)
{
gen_store_table_unlock_address(env, guest_address, offsetof(CPUState, locked_address_high));
}
void gen_store_table_lock_128(CPUState *env, TCGv_guestptr guest_addr_low, TCGv_guestptr guest_addr_high)
{
#ifdef DEBUG
int addr_is_equal = gen_new_label();
TCGv_guestptr comp_addr = tcg_temp_local_new_ptr();
tcg_gen_addi_tl(comp_addr, guest_addr_low, sizeof(uint64_t));
tcg_gen_brcond_tl(TCG_COND_EQ, comp_addr, guest_addr_high, addr_is_equal);
/* If we didn't jump, the address pair is illegal */
generate_log(0, "Illegal pair of guest addresses in %s", __func__);
generate_log(0, "guest_addr_low:");
generate_var_log(guest_addr_low);
generate_log(0, "guest_addr_high:");
generate_var_log(guest_addr_high);
generate_log(0, "Should be: guest_addr_high == guest_addr_low+8bytes");
generate_backtrace_print();
gen_helper_abort();
gen_set_label(addr_is_equal);
tcg_temp_free_ptr(comp_addr);
#endif
/* To avoid deadlocks, the order is important and should always lock the lowest 64 bits first */
gen_store_table_lock(env, guest_addr_low);
gen_store_table_lock_high(env, guest_addr_high);
}
void gen_store_table_unlock_128(CPUState *env, TCGv_guestptr guest_addr_low, TCGv_guestptr guest_addr_high)
{
gen_store_table_unlock(env, guest_addr_low);
gen_store_table_unlock_high(env, guest_addr_high);
}