問題:
- リリース版sh8benchでunified_cache_refill+0x46fでSEGVAULT
- コンパイラ最適化により、ヘッダー書き込みとtiny_next_read()の
順序が入れ替わり、破損したポインタをout[]に格納
根本原因:
- ヘッダー書き込みがtiny_next_read()の後にあった
- volatile barrierがなく、コンパイラが自由に順序を変更
- ASan版では最適化が制限されるため問題が隠蔽されていた
修正内容(P1-P3):
P1: unified_cache_refill SEGVAULT修正 (core/front/tiny_unified_cache.c:341-350)
- ヘッダー書き込みをtiny_next_read()の前に移動
- __atomic_thread_fence(__ATOMIC_RELEASE)追加
- コンパイラ最適化による順序入れ替えを防止
P2: 二重書き込み削除 (core/box/tiny_front_cold_box.h:75-82)
- tiny_region_id_write_header()削除
- unified_cache_refillが既にヘッダー書き込み済み
- 不要なメモリ操作を削除して効率化
P3: tiny_next_read()安全性強化 (core/tiny_nextptr.h:73-86)
- __atomic_thread_fence(__ATOMIC_ACQUIRE)追加
- メモリ操作の順序を保証
P4: ヘッダー書き込みデフォルトON (core/tiny_region_id.h - ChatGPT修正)
- g_write_headerのデフォルトを1に変更
- HAKMEM_TINY_WRITE_HEADER=0で旧挙動に戻せる
テスト結果:
✅ unified_cache_refill SEGVAULT: 解消(sh8bench実行可能に)
❌ TLS_SLL_HDR_RESET: まだ発生中(別の根本原因、調査継続)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
126 lines
4.9 KiB
C
126 lines
4.9 KiB
C
// tiny_nextptr.h - Authoritative next-pointer offset/load/store for tiny boxes
|
||
//
|
||
// Finalized Phase E1-CORRECT spec (物理制約込み):
|
||
// P0.1 updated: C0 and C7 use offset 0, C1-C6 use offset 1 (header preserved)
|
||
//
|
||
// HAKMEM_TINY_HEADER_CLASSIDX != 0 のとき:
|
||
//
|
||
// Class 0:
|
||
// [1B header][7B payload] (total 8B stride)
|
||
// → 8B stride に 1B header + 8B next pointer は収まらない(1B溢れる)
|
||
// → next は base+0 に格納(headerを上書き)
|
||
// → next_off = 0
|
||
//
|
||
// Class 1〜6:
|
||
// [1B header][payload >= 15B] (stride >= 16B)
|
||
// → headerは保持し、next は header直後 base+1 に格納
|
||
// → next_off = 1
|
||
//
|
||
// Class 7:
|
||
// [1B header][payload 2047B]
|
||
// → headerは上書きし、next は base+0 に格納(最大サイズなので許容)
|
||
// → next_off = 0
|
||
//
|
||
// HAKMEM_TINY_HEADER_CLASSIDX == 0 のとき:
|
||
//
|
||
// 全クラス headerなし → next_off = 0
|
||
//
|
||
// このヘッダは上記仕様を唯一の真実として提供する。
|
||
// すべての tiny freelist / TLS / fast-cache / refill / SLL で
|
||
// tiny_next_off/tiny_next_load/tiny_next_store を経由すること。
|
||
// 直接の *(void**) アクセスやローカルな offset 分岐は使用禁止。
|
||
|
||
#ifndef TINY_NEXTPTR_H
|
||
#define TINY_NEXTPTR_H
|
||
|
||
#include <stdint.h>
|
||
#include <string.h>
|
||
#include <stdlib.h> // P2.3: for getenv()
|
||
#include "hakmem_build_flags.h"
|
||
#include "tiny_region_id.h" // HEADER_MAGIC/HEADER_CLASS_MASK for header repair/logging
|
||
#include "hakmem_super_registry.h" // hak_super_lookup
|
||
#include "superslab/superslab_inline.h" // slab_index_for
|
||
#include <stdio.h>
|
||
#include <stdatomic.h>
|
||
#include <dlfcn.h>
|
||
#include <execinfo.h> // backtrace for rare misalign diagnostics
|
||
|
||
// Compute freelist next-pointer offset within a block for the given class.
|
||
// P0.1 updated: C0 and C7 use offset 0, C1-C6 use offset 1 (header preserved)
|
||
// Rationale for C0: 8B stride cannot fit [1B header][8B next pointer] without overflow
|
||
static inline __attribute__((always_inline)) size_t tiny_next_off(int class_idx) {
|
||
#if HAKMEM_TINY_HEADER_CLASSIDX
|
||
// C0 (8B): offset 0 (8B stride too small for header + 8B pointer - would overflow)
|
||
// C7 (2048B): offset 0 (overwrites header in freelist - largest class can tolerate)
|
||
// C1-C6: offset 1 (header preserved - user data is not disturbed)
|
||
// Optimized: Use bitmask lookup instead of branching
|
||
// Bit pattern: C0=0, C1-C6=1, C7=0 → 0b01111110 = 0x7E
|
||
return (0x7Eu >> class_idx) & 1u;
|
||
#else
|
||
(void)class_idx;
|
||
return 0u;
|
||
#endif
|
||
}
|
||
|
||
// Safe load of next pointer from a block base.
|
||
static inline __attribute__((always_inline)) void* tiny_next_load(const void* base, int class_idx) {
|
||
size_t off = tiny_next_off(class_idx);
|
||
|
||
if (off == 0) {
|
||
// Aligned access at base (header無し or C7 freelist時)
|
||
void* next = *(void* const*)base;
|
||
|
||
// P3: Prevent compiler from reordering this load
|
||
__atomic_thread_fence(__ATOMIC_ACQUIRE);
|
||
return next;
|
||
}
|
||
|
||
// off != 0: use memcpy to avoid UB on architectures that forbid unaligned loads.
|
||
// C0-C6: offset 1 (header preserved)
|
||
void* next = NULL;
|
||
const uint8_t* p = (const uint8_t*)base + off;
|
||
memcpy(&next, p, sizeof(void*));
|
||
|
||
// P3: Prevent compiler from reordering this load
|
||
__atomic_thread_fence(__ATOMIC_ACQUIRE);
|
||
return next;
|
||
}
|
||
|
||
// Safe store of next pointer into a block base.
|
||
// P2.3: Header restoration is now conditional (default: skip when class_map is active)
|
||
// - When class_map is used for class_idx lookup (default), header restoration is unnecessary
|
||
// - Alloc path always writes fresh header before returning block to user (HAK_RET_ALLOC)
|
||
// - ENV: HAKMEM_TINY_RESTORE_HEADER=1 to force header restoration (legacy mode)
|
||
// P0.1: C7 uses offset 0 (overwrites header), C0-C6 use offset 1 (header preserved)
|
||
static inline __attribute__((always_inline)) void tiny_next_store(void* base, int class_idx, void* next) {
|
||
size_t off = tiny_next_off(class_idx);
|
||
|
||
#if HAKMEM_TINY_HEADER_CLASSIDX
|
||
// P2.3: Skip header restoration by default (class_map is now default for class_idx lookup)
|
||
// ENV: HAKMEM_TINY_RESTORE_HEADER=1 to force header restoration (legacy fallback mode)
|
||
if (off != 0) {
|
||
static int g_restore_header = -1;
|
||
if (__builtin_expect(g_restore_header == -1, 0)) {
|
||
const char* e = getenv("HAKMEM_TINY_RESTORE_HEADER");
|
||
g_restore_header = (e && *e && *e != '0') ? 1 : 0;
|
||
}
|
||
if (__builtin_expect(g_restore_header, 0)) {
|
||
// Legacy mode: Restore header for classes that preserve it (C0-C6)
|
||
*(uint8_t*)base = HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
if (off == 0) {
|
||
// Aligned access at base (overwrites header for C7).
|
||
*(void**)base = next;
|
||
return;
|
||
}
|
||
|
||
// off != 0: use memcpy for portability / UB-avoidance.
|
||
uint8_t* p = (uint8_t*)base + off;
|
||
memcpy(p, &next, sizeof(void*));
|
||
}
|
||
|
||
#endif // TINY_NEXTPTR_H
|