MID-V3 Phase 0-2: Design doc, type skeleton, and RegionIdBox API
- MID-V3-0: Create design doc (docs/analysis/MID_POOL_V3_DESIGN.md) - Lane vs Page role clarification - Phase plan and checklist - MID-V3-1: Type skeleton + ENV - MidHotBoxV3, MidLaneV3, MidPageDescV3 structures - ENV controls (HAKMEM_MID_V3_ENABLED, HAKMEM_MID_V3_CLASSES) - Cold interface declarations - MID-V3-2 (V6-HDR-2): RegionIdBox Registration API completion - RegionEntry structure with sorted array storage - Binary search lookup implementation - region_id_register_v6() / region_id_unregister_v6() - REGION_KIND_MID_V3 added to enum 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
110
core/box/mid_cold_iface_v3.h
Normal file
110
core/box/mid_cold_iface_v3.h
Normal file
@ -0,0 +1,110 @@
|
||||
// mid_cold_iface_v3.h - Cold Interface for Mid/Pool v3
|
||||
//
|
||||
// 役割:
|
||||
// - L1 レベルの refill / retire 操作を定義
|
||||
// - Page carve / return
|
||||
// - RegionIdBox 登録 / 解除
|
||||
// - Phase MID-V3-1: インターフェース宣言のみ
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "mid_hotbox_v3_box.h"
|
||||
|
||||
// ============================================================================
|
||||
// Cold Interface Function Pointers (for future vtable support)
|
||||
// ============================================================================
|
||||
|
||||
typedef MidPageDescV3* (*mid_cold_refill_fn)(MidHotBoxV3* hot, uint32_t class_idx);
|
||||
typedef void (*mid_cold_retire_fn)(MidHotBoxV3* hot, MidPageDescV3* page);
|
||||
typedef bool (*mid_cold_remote_push_fn)(MidPageDescV3* page, void* ptr, uint32_t tid);
|
||||
typedef void (*mid_cold_remote_drain_fn)(MidHotBoxV3* hot);
|
||||
|
||||
typedef struct MidColdIfaceV3 {
|
||||
mid_cold_refill_fn refill_page;
|
||||
mid_cold_retire_fn retire_page;
|
||||
mid_cold_remote_push_fn remote_push;
|
||||
mid_cold_remote_drain_fn remote_drain;
|
||||
} MidColdIfaceV3;
|
||||
|
||||
// ============================================================================
|
||||
// Default Cold Interface (direct function calls)
|
||||
// ============================================================================
|
||||
|
||||
/// Refill: Get a new page from segment/pool
|
||||
/// @param hot: TLS HotBox context
|
||||
/// @param class_idx: Size class index
|
||||
/// @return: New page with freelist, or NULL on failure
|
||||
MidPageDescV3* mid_cold_v3_refill_page(MidHotBoxV3* hot, uint32_t class_idx);
|
||||
|
||||
/// Retire: Return an empty page to segment/pool
|
||||
/// @param hot: TLS HotBox context
|
||||
/// @param page: Page to retire (must be empty)
|
||||
void mid_cold_v3_retire_page(MidHotBoxV3* hot, MidPageDescV3* page);
|
||||
|
||||
/// Remote push: Push freed block to remote thread's page
|
||||
/// @param page: Target page (owned by different thread)
|
||||
/// @param ptr: Block to free
|
||||
/// @param tid: Caller's thread ID
|
||||
/// @return: true if push succeeded, false if local handling needed
|
||||
bool mid_cold_v3_remote_push(MidPageDescV3* page, void* ptr, uint32_t tid);
|
||||
|
||||
/// Remote drain: Process blocks freed by other threads
|
||||
/// @param hot: TLS HotBox context
|
||||
void mid_cold_v3_remote_drain(MidHotBoxV3* hot);
|
||||
|
||||
// ============================================================================
|
||||
// Segment Operations
|
||||
// ============================================================================
|
||||
|
||||
/// Acquire a segment for the given class
|
||||
/// @param class_idx: Size class index
|
||||
/// @return: Segment pointer, or NULL on failure
|
||||
MidSegmentV3* mid_segment_v3_acquire(int class_idx);
|
||||
|
||||
/// Carve a page from segment
|
||||
/// @param seg: Segment to carve from
|
||||
/// @param class_idx: Size class index
|
||||
/// @return: New page descriptor, or NULL if segment exhausted
|
||||
MidPageDescV3* mid_segment_v3_carve_page(MidSegmentV3* seg, int class_idx);
|
||||
|
||||
/// Return a page to segment
|
||||
/// @param seg: Owning segment
|
||||
/// @param page: Page to return
|
||||
void mid_segment_v3_return_page(MidSegmentV3* seg, MidPageDescV3* page);
|
||||
|
||||
// ============================================================================
|
||||
// Lane Operations
|
||||
// ============================================================================
|
||||
|
||||
/// Refill lane from page
|
||||
/// @param lane: TLS lane
|
||||
/// @param page: Source page
|
||||
/// @param batch_size: Number of items to transfer
|
||||
void mid_lane_refill_from_page(MidLaneV3* lane, MidPageDescV3* page, uint32_t batch_size);
|
||||
|
||||
/// Flush lane back to page
|
||||
/// @param lane: TLS lane
|
||||
/// @param page: Target page
|
||||
void mid_lane_flush_to_page(MidLaneV3* lane, MidPageDescV3* page);
|
||||
|
||||
// ============================================================================
|
||||
// Page Operations
|
||||
// ============================================================================
|
||||
|
||||
/// Build freelist for a page
|
||||
/// @param page: Page descriptor
|
||||
/// @return: Freelist head
|
||||
void* mid_page_build_freelist(MidPageDescV3* page);
|
||||
|
||||
/// Push a block to page's freelist (thread-safe)
|
||||
/// @param page: Target page
|
||||
/// @param ptr: Block to push
|
||||
void mid_page_push_free(MidPageDescV3* page, void* ptr);
|
||||
|
||||
/// Pop a block from page's freelist
|
||||
/// @param page: Source page
|
||||
/// @return: Block pointer, or NULL if empty
|
||||
void* mid_page_pop_free(MidPageDescV3* page);
|
||||
127
core/box/mid_hotbox_v3_box.h
Normal file
127
core/box/mid_hotbox_v3_box.h
Normal file
@ -0,0 +1,127 @@
|
||||
// mid_hotbox_v3_box.h - Mid/Pool HotBox v3 (型スケルトン)
|
||||
//
|
||||
// 役割:
|
||||
// - MID v3 の Lane / Page / TLS コンテキスト型と API 宣言を定義する箱。
|
||||
// - RegionIdBox 統合を前提とした設計。
|
||||
// - Phase MID-V3-1: 型スケルトンのみ(実装は後続フェーズ)
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "tiny_geometry_box.h"
|
||||
|
||||
#ifndef MID_V3_NUM_CLASSES
|
||||
#define MID_V3_NUM_CLASSES TINY_NUM_CLASSES
|
||||
#endif
|
||||
|
||||
// Forward declarations
|
||||
struct MidSegmentV3;
|
||||
|
||||
// ============================================================================
|
||||
// MidPageDescV3: Page metadata for v3 HotBox
|
||||
// ============================================================================
|
||||
// Design note: Lane vs Page 二重管理を回避するため、
|
||||
// freelist は Page に authoritative として保持し、
|
||||
// Lane は TLS cache として機能する。
|
||||
|
||||
typedef struct MidPageDescV3 {
|
||||
uint8_t* base; // Page base address
|
||||
void* freelist; // Authoritative freelist
|
||||
uint32_t capacity; // Total slots
|
||||
uint32_t used; // Used count
|
||||
uint32_t region_id; // RegionIdBox registration ID (0 = not registered)
|
||||
uint32_t block_size; // Block size in bytes
|
||||
uint8_t class_idx; // Size class index
|
||||
uint8_t flags; // Page state flags
|
||||
uint16_t reserved; // Alignment padding
|
||||
void* slab_ref; // Underlying slab/segment reference
|
||||
struct MidSegmentV3* segment; // Owning segment (NULL if legacy path)
|
||||
struct MidPageDescV3* next; // Intrusive list linkage
|
||||
} MidPageDescV3;
|
||||
|
||||
// Page flags
|
||||
#define MID_PAGE_FLAG_ACTIVE (1u << 0) // Page is in active use
|
||||
#define MID_PAGE_FLAG_PARTIAL (1u << 1) // Page has free slots
|
||||
#define MID_PAGE_FLAG_FULL (1u << 2) // Page is exhausted
|
||||
#define MID_PAGE_FLAG_RETIRED (1u << 3) // Page returned to cold
|
||||
|
||||
// ============================================================================
|
||||
// MidLaneV3: TLS lane (per-class freelist cache)
|
||||
// ============================================================================
|
||||
// Design note: Lane は Page への index と freelist snapshot を保持。
|
||||
// freelist_head は TLS 内でのみ有効な cache であり、
|
||||
// 実際の freelist は MidPageDescV3.freelist にある。
|
||||
|
||||
typedef struct MidLaneV3 {
|
||||
uint32_t page_idx; // Current working page index (0 = none)
|
||||
void* freelist_head; // TLS-local freelist snapshot
|
||||
uint32_t freelist_count; // Remaining items in cache
|
||||
uint32_t alloc_count; // Allocation count (stats)
|
||||
uint32_t free_count; // Free count (stats)
|
||||
} MidLaneV3;
|
||||
|
||||
// ============================================================================
|
||||
// MidHotBoxV3: TLS heap context (per-thread)
|
||||
// ============================================================================
|
||||
|
||||
typedef struct MidHotBoxV3 {
|
||||
MidLaneV3 lanes[MID_V3_NUM_CLASSES];
|
||||
uint32_t tid; // Thread ID (for remote free detection)
|
||||
uint32_t flags; // Context flags
|
||||
} MidHotBoxV3;
|
||||
|
||||
// Context flags
|
||||
#define MID_CTX_FLAG_INIT (1u << 0) // Context initialized
|
||||
|
||||
// ============================================================================
|
||||
// MidSegmentV3: Segment descriptor (shared across threads)
|
||||
// ============================================================================
|
||||
|
||||
typedef struct MidSegmentV3 {
|
||||
uintptr_t base; // Segment base address
|
||||
size_t size; // Segment size in bytes
|
||||
uint32_t magic; // Validation magic number
|
||||
uint32_t num_pages; // Total pages in segment
|
||||
uint32_t region_id; // RegionIdBox registration ID
|
||||
uint8_t class_idx; // Primary class (or 0xFF for mixed)
|
||||
uint8_t flags; // Segment flags
|
||||
uint16_t reserved;
|
||||
// page_meta[] follows (flexible array member)
|
||||
} MidSegmentV3;
|
||||
|
||||
#define MID_SEGMENT_V3_MAGIC 0xCAFEBABE
|
||||
#define MID_SEGMENT_V3_SIZE (2 * 1024 * 1024) // 2 MiB default
|
||||
#define MID_PAGE_V3_SIZE (64 * 1024) // 64 KiB page
|
||||
#define MID_PAGE_V3_SHIFT 16 // log2(64 KiB)
|
||||
#define MID_PAGES_PER_SEGMENT (MID_SEGMENT_V3_SIZE / MID_PAGE_V3_SIZE)
|
||||
|
||||
// ============================================================================
|
||||
// API Declarations (stubs for MID-V3-1)
|
||||
// ============================================================================
|
||||
|
||||
/// Get TLS HotBox context
|
||||
MidHotBoxV3* mid_hot_box_v3_get(void);
|
||||
|
||||
/// Allocation fast path
|
||||
void* mid_hot_v3_alloc(MidHotBoxV3* hot, int class_idx);
|
||||
|
||||
/// Free fast path
|
||||
void mid_hot_v3_free(void* ptr);
|
||||
|
||||
/// Check if v3 can own this pointer
|
||||
int mid_hotbox_v3_can_own(int class_idx, void* ptr);
|
||||
|
||||
/// Page index conversion helpers
|
||||
static inline uint32_t mid_page_to_idx(MidPageDescV3* page) {
|
||||
// Stub: actual implementation depends on segment layout
|
||||
(void)page;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline MidPageDescV3* mid_page_from_idx(uint32_t idx) {
|
||||
// Stub: actual implementation depends on segment layout
|
||||
(void)idx;
|
||||
return NULL;
|
||||
}
|
||||
109
core/box/mid_hotbox_v3_env_box.h
Normal file
109
core/box/mid_hotbox_v3_env_box.h
Normal file
@ -0,0 +1,109 @@
|
||||
// mid_hotbox_v3_env_box.h - ENV gate for Mid/Pool HotBox v3
|
||||
// デフォルト: OFF(研究用に明示 opt-in)
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../hakmem_tiny_config.h"
|
||||
|
||||
// ============================================================================
|
||||
// HAKMEM_MID_V3_ENABLED: Master switch for MID v3
|
||||
// ============================================================================
|
||||
|
||||
static inline int mid_v3_enabled(void) {
|
||||
static int g_enable = -1;
|
||||
if (__builtin_expect(g_enable == -1, 0)) {
|
||||
const char* e = getenv("HAKMEM_MID_V3_ENABLED");
|
||||
if (e && *e) {
|
||||
g_enable = (*e != '0') ? 1 : 0;
|
||||
} else {
|
||||
// v3 は研究箱。明示しない限り OFF
|
||||
g_enable = 0;
|
||||
}
|
||||
}
|
||||
return g_enable;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HAKMEM_MID_V3_CLASSES: Per-class enable bitmask
|
||||
// ============================================================================
|
||||
// Default: 0x00 (all classes OFF)
|
||||
// Example: 0xC0 = C6 + C7 enabled
|
||||
|
||||
static inline int mid_v3_class_enabled(uint8_t class_idx) {
|
||||
static int g_parsed = 0;
|
||||
static unsigned g_mask = 0;
|
||||
if (__builtin_expect(!g_parsed, 0)) {
|
||||
const char* e = getenv("HAKMEM_MID_V3_CLASSES");
|
||||
if (e && *e) {
|
||||
unsigned v = (unsigned)strtoul(e, NULL, 0);
|
||||
g_mask = v & 0xFFu;
|
||||
} else {
|
||||
// デフォルトは全クラス OFF
|
||||
g_mask = 0;
|
||||
}
|
||||
g_parsed = 1;
|
||||
}
|
||||
if (!mid_v3_enabled()) return 0;
|
||||
if (class_idx >= TINY_NUM_CLASSES) return 0;
|
||||
return (g_mask & (1u << class_idx)) != 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Per-class convenience functions
|
||||
// ============================================================================
|
||||
|
||||
static inline int mid_v3_c7_enabled(void) {
|
||||
return mid_v3_class_enabled(7);
|
||||
}
|
||||
|
||||
static inline int mid_v3_c6_enabled(void) {
|
||||
return mid_v3_class_enabled(6);
|
||||
}
|
||||
|
||||
static inline int mid_v3_c5_enabled(void) {
|
||||
return mid_v3_class_enabled(5);
|
||||
}
|
||||
|
||||
static inline int mid_v3_c4_enabled(void) {
|
||||
return mid_v3_class_enabled(4);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HAKMEM_MID_V3_DEBUG: Debug logging
|
||||
// ============================================================================
|
||||
|
||||
static inline int mid_v3_debug_enabled(void) {
|
||||
static int g_debug = -1;
|
||||
if (__builtin_expect(g_debug == -1, 0)) {
|
||||
const char* e = getenv("HAKMEM_MID_V3_DEBUG");
|
||||
if (e && *e) {
|
||||
g_debug = (*e != '0') ? 1 : 0;
|
||||
} else {
|
||||
g_debug = 0;
|
||||
}
|
||||
}
|
||||
return g_debug;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HAKMEM_MID_V3_LANE_BATCH: Lane refill batch size
|
||||
// ============================================================================
|
||||
// How many items to transfer from page to lane on refill
|
||||
|
||||
static inline uint32_t mid_v3_lane_batch_size(void) {
|
||||
static int g_parsed = 0;
|
||||
static uint32_t g_batch = 0;
|
||||
if (__builtin_expect(!g_parsed, 0)) {
|
||||
const char* e = getenv("HAKMEM_MID_V3_LANE_BATCH");
|
||||
if (e && *e) {
|
||||
g_batch = (uint32_t)strtoul(e, NULL, 0);
|
||||
} else {
|
||||
// Default: 16 items per batch
|
||||
g_batch = 16;
|
||||
}
|
||||
g_parsed = 1;
|
||||
}
|
||||
return g_batch;
|
||||
}
|
||||
@ -23,6 +23,7 @@ typedef enum {
|
||||
REGION_KIND_POOL_V1, // Pool v1 region
|
||||
REGION_KIND_LARGE, // Large mmap allocation
|
||||
REGION_KIND_TINY_LEGACY, // Legacy tiny heap
|
||||
REGION_KIND_MID_V3, // Mid/Pool v3 page (MID-V3)
|
||||
REGION_KIND_MAX
|
||||
} region_kind_t;
|
||||
|
||||
|
||||
210
core/mid_hotbox_v3.c
Normal file
210
core/mid_hotbox_v3.c
Normal file
@ -0,0 +1,210 @@
|
||||
// mid_hotbox_v3.c - Mid/Pool HotBox v3 Implementation
|
||||
//
|
||||
// Phase MID-V3-1: Stub implementation (skeleton only)
|
||||
// Phase MID-V3-4/5: Full implementation
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef likely
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
#endif
|
||||
|
||||
#include "box/mid_hotbox_v3_box.h"
|
||||
#include "box/mid_hotbox_v3_env_box.h"
|
||||
#include "box/mid_cold_iface_v3.h"
|
||||
#include "box/region_id_v6_box.h"
|
||||
|
||||
// ============================================================================
|
||||
// TLS Context
|
||||
// ============================================================================
|
||||
|
||||
static __thread MidHotBoxV3 g_mid_hot_ctx_v3;
|
||||
static __thread int g_mid_hot_ctx_v3_init = 0;
|
||||
|
||||
MidHotBoxV3* mid_hot_box_v3_get(void) {
|
||||
if (unlikely(!g_mid_hot_ctx_v3_init)) {
|
||||
memset(&g_mid_hot_ctx_v3, 0, sizeof(g_mid_hot_ctx_v3));
|
||||
g_mid_hot_ctx_v3.flags = MID_CTX_FLAG_INIT;
|
||||
g_mid_hot_ctx_v3_init = 1;
|
||||
}
|
||||
return &g_mid_hot_ctx_v3;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Allocation Fast Path (MID-V3-4 stub)
|
||||
// ============================================================================
|
||||
|
||||
void* mid_hot_v3_alloc(MidHotBoxV3* hot, int class_idx) {
|
||||
if (unlikely(!mid_v3_class_enabled((uint8_t)class_idx))) {
|
||||
return NULL; // Class not enabled
|
||||
}
|
||||
|
||||
if (!hot) {
|
||||
hot = mid_hot_box_v3_get();
|
||||
}
|
||||
|
||||
MidLaneV3* lane = &hot->lanes[class_idx];
|
||||
|
||||
// L0: TLS freelist cache hit
|
||||
if (likely(lane->freelist_head)) {
|
||||
void* blk = lane->freelist_head;
|
||||
void* next = NULL;
|
||||
memcpy(&next, blk, sizeof(void*));
|
||||
lane->freelist_head = next;
|
||||
lane->freelist_count--;
|
||||
lane->alloc_count++;
|
||||
return blk;
|
||||
}
|
||||
|
||||
// L0 miss: slow path
|
||||
return NULL; // Stub: MID-V3-4 will implement slow path
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Free Fast Path (MID-V3-5 stub)
|
||||
// ============================================================================
|
||||
|
||||
void mid_hot_v3_free(void* ptr) {
|
||||
if (unlikely(!ptr)) return;
|
||||
if (unlikely(!mid_v3_enabled())) return;
|
||||
|
||||
// RegionIdBox lookup
|
||||
RegionLookupV6 lk = region_id_lookup_cached_v6(ptr);
|
||||
|
||||
// Stub: MID-V3-5 will implement full free path
|
||||
(void)lk;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Ownership Check
|
||||
// ============================================================================
|
||||
|
||||
int mid_hotbox_v3_can_own(int class_idx, void* ptr) {
|
||||
if (unlikely(!mid_v3_class_enabled((uint8_t)class_idx))) {
|
||||
return 0;
|
||||
}
|
||||
if (!ptr) return 0;
|
||||
|
||||
// RegionIdBox lookup
|
||||
RegionLookupV6 lk = region_id_lookup_v6(ptr);
|
||||
|
||||
// Check if this is a MID v3 region
|
||||
// Stub: For now, always return 0 until MID-V3-3 implements registration
|
||||
(void)lk;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Cold Interface Stubs (MID-V3-4/5)
|
||||
// ============================================================================
|
||||
|
||||
MidPageDescV3* mid_cold_v3_refill_page(MidHotBoxV3* hot, uint32_t class_idx) {
|
||||
(void)hot;
|
||||
(void)class_idx;
|
||||
// Stub: MID-V3-4 will implement
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mid_cold_v3_retire_page(MidHotBoxV3* hot, MidPageDescV3* page) {
|
||||
(void)hot;
|
||||
(void)page;
|
||||
// Stub: MID-V3-5 will implement
|
||||
}
|
||||
|
||||
bool mid_cold_v3_remote_push(MidPageDescV3* page, void* ptr, uint32_t tid) {
|
||||
(void)page;
|
||||
(void)ptr;
|
||||
(void)tid;
|
||||
// Stub: MID-V3-5 will implement
|
||||
return false;
|
||||
}
|
||||
|
||||
void mid_cold_v3_remote_drain(MidHotBoxV3* hot) {
|
||||
(void)hot;
|
||||
// Stub: MID-V3-5 will implement
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Segment Operations Stubs
|
||||
// ============================================================================
|
||||
|
||||
MidSegmentV3* mid_segment_v3_acquire(int class_idx) {
|
||||
(void)class_idx;
|
||||
// Stub: MID-V3-4 will implement
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MidPageDescV3* mid_segment_v3_carve_page(MidSegmentV3* seg, int class_idx) {
|
||||
(void)seg;
|
||||
(void)class_idx;
|
||||
// Stub: MID-V3-4 will implement
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mid_segment_v3_return_page(MidSegmentV3* seg, MidPageDescV3* page) {
|
||||
(void)seg;
|
||||
(void)page;
|
||||
// Stub: MID-V3-5 will implement
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Lane Operations Stubs
|
||||
// ============================================================================
|
||||
|
||||
void mid_lane_refill_from_page(MidLaneV3* lane, MidPageDescV3* page, uint32_t batch_size) {
|
||||
(void)lane;
|
||||
(void)page;
|
||||
(void)batch_size;
|
||||
// Stub: MID-V3-4 will implement
|
||||
}
|
||||
|
||||
void mid_lane_flush_to_page(MidLaneV3* lane, MidPageDescV3* page) {
|
||||
(void)lane;
|
||||
(void)page;
|
||||
// Stub: MID-V3-5 will implement
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Page Operations Stubs
|
||||
// ============================================================================
|
||||
|
||||
void* mid_page_build_freelist(MidPageDescV3* page) {
|
||||
(void)page;
|
||||
// Stub: MID-V3-4 will implement
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mid_page_push_free(MidPageDescV3* page, void* ptr) {
|
||||
(void)page;
|
||||
(void)ptr;
|
||||
// Stub: MID-V3-5 will implement
|
||||
}
|
||||
|
||||
void* mid_page_pop_free(MidPageDescV3* page) {
|
||||
(void)page;
|
||||
// Stub: MID-V3-4 will implement
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Debug
|
||||
// ============================================================================
|
||||
|
||||
void mid_hot_v3_dump_stats(void) {
|
||||
if (!mid_v3_debug_enabled()) return;
|
||||
|
||||
MidHotBoxV3* hot = mid_hot_box_v3_get();
|
||||
fprintf(stderr, "[MID_V3] HotBox stats:\n");
|
||||
|
||||
for (int i = 0; i < MID_V3_NUM_CLASSES; i++) {
|
||||
if (!mid_v3_class_enabled((uint8_t)i)) continue;
|
||||
|
||||
MidLaneV3* lane = &hot->lanes[i];
|
||||
fprintf(stderr, " C%d: page_idx=%u freelist_count=%u alloc=%u free=%u\n",
|
||||
i, lane->page_idx, lane->freelist_count,
|
||||
lane->alloc_count, lane->free_count);
|
||||
}
|
||||
}
|
||||
@ -68,23 +68,114 @@ void region_id_register_v6_segment(SmallSegmentV6* seg) {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Global RegionIdBox (stub for now)
|
||||
// Global RegionIdBox (V6-HDR-2: Full implementation)
|
||||
// ============================================================================
|
||||
|
||||
// Define the opaque RegionIdBox structure
|
||||
// Maximum number of registered regions
|
||||
#define REGION_MAX_ENTRIES 256
|
||||
|
||||
// RegionEntry: Single registered region
|
||||
typedef struct RegionEntry {
|
||||
uintptr_t base; // Region base address
|
||||
uintptr_t end; // Region end address (exclusive)
|
||||
region_kind_t kind; // Region type
|
||||
void* metadata; // Kind-specific metadata (e.g., page_meta)
|
||||
uint32_t id; // Unique region ID
|
||||
uint8_t active; // 1 = active, 0 = free slot
|
||||
uint8_t reserved[3]; // Padding
|
||||
} RegionEntry;
|
||||
|
||||
// RegionIdBox: Region registry
|
||||
struct RegionIdBox {
|
||||
uint32_t next_id;
|
||||
// Stub implementation - full implementation in V6-HDR-2
|
||||
uint32_t next_id; // Next available ID
|
||||
uint32_t count; // Number of active entries
|
||||
RegionEntry entries[REGION_MAX_ENTRIES]; // Sorted by base address
|
||||
// Note: entries are kept sorted by base for binary search lookup
|
||||
};
|
||||
|
||||
static struct RegionIdBox g_region_id_box;
|
||||
static struct RegionIdBox g_region_id_box = {
|
||||
.next_id = 2, // Start from 2 (1 is reserved for TLS segment)
|
||||
.count = 0
|
||||
};
|
||||
|
||||
RegionIdBox* region_id_box_get(void) {
|
||||
return &g_region_id_box;
|
||||
}
|
||||
|
||||
// Binary search helper: find entry containing address
|
||||
static RegionEntry* region_entry_find(uintptr_t addr) {
|
||||
RegionIdBox* box = &g_region_id_box;
|
||||
if (box->count == 0) return NULL;
|
||||
|
||||
// Binary search for entry where base <= addr < end
|
||||
uint32_t lo = 0, hi = box->count;
|
||||
while (lo < hi) {
|
||||
uint32_t mid = lo + (hi - lo) / 2;
|
||||
RegionEntry* e = &box->entries[mid];
|
||||
if (!e->active) {
|
||||
// Skip inactive entries (shouldn't happen in sorted array)
|
||||
lo = mid + 1;
|
||||
continue;
|
||||
}
|
||||
if (addr < e->base) {
|
||||
hi = mid;
|
||||
} else if (addr >= e->end) {
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
// addr is in range [base, end)
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Insert entry maintaining sorted order
|
||||
static int region_entry_insert(RegionEntry* entry) {
|
||||
RegionIdBox* box = &g_region_id_box;
|
||||
if (box->count >= REGION_MAX_ENTRIES) {
|
||||
return -1; // Registry full
|
||||
}
|
||||
|
||||
// Find insertion point (binary search)
|
||||
uint32_t pos = 0;
|
||||
for (uint32_t i = 0; i < box->count; i++) {
|
||||
if (box->entries[i].base > entry->base) {
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
pos = i + 1;
|
||||
}
|
||||
|
||||
// Shift entries to make room
|
||||
for (uint32_t i = box->count; i > pos; i--) {
|
||||
box->entries[i] = box->entries[i - 1];
|
||||
}
|
||||
|
||||
// Insert new entry
|
||||
box->entries[pos] = *entry;
|
||||
box->count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Remove entry by ID
|
||||
static int region_entry_remove(uint32_t id) {
|
||||
RegionIdBox* box = &g_region_id_box;
|
||||
|
||||
for (uint32_t i = 0; i < box->count; i++) {
|
||||
if (box->entries[i].id == id && box->entries[i].active) {
|
||||
// Shift entries to fill gap
|
||||
for (uint32_t j = i; j < box->count - 1; j++) {
|
||||
box->entries[j] = box->entries[j + 1];
|
||||
}
|
||||
box->count--;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1; // Not found
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Lookup Implementation (V6-HDR-1: TLS segment only)
|
||||
// Lookup Implementation (V6-HDR-2: TLS + Registry)
|
||||
// ============================================================================
|
||||
|
||||
// Forward declaration from smallsegment_v6.c
|
||||
@ -138,6 +229,23 @@ RegionLookupV6 region_id_lookup_v6(void* ptr) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// V6-HDR-2: Check registered regions (binary search)
|
||||
RegionEntry* entry = region_entry_find(addr);
|
||||
if (entry) {
|
||||
result.kind = entry->kind;
|
||||
result.region_id = entry->id;
|
||||
result.page_meta = entry->metadata;
|
||||
|
||||
// Update TLS cache for segment-level caching
|
||||
RegionIdTlsCache* cache = region_id_tls_cache_get();
|
||||
cache->last_base = entry->base;
|
||||
cache->last_end = entry->end;
|
||||
cache->last_result = result;
|
||||
// Note: page-level cache not applicable for arbitrary registered regions
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Legacy fallback (if TLS not registered yet)
|
||||
SmallPageMetaV6* page = small_page_meta_v6_of(ptr);
|
||||
if (page != NULL) {
|
||||
@ -196,26 +304,61 @@ RegionLookupV6 region_id_lookup_cached_v6(void* ptr) {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Registration API (Stub for V6-HDR-1)
|
||||
// Registration API (V6-HDR-2: Full implementation)
|
||||
// ============================================================================
|
||||
|
||||
uint32_t region_id_register_v6(void* base, size_t size, region_kind_t kind, void* metadata) {
|
||||
(void)base;
|
||||
(void)size;
|
||||
(void)kind;
|
||||
(void)metadata;
|
||||
// V6-HDR-1: Registration is not yet implemented
|
||||
// TLS segment is implicitly "registered" by its existence
|
||||
return 1; // Single region for now
|
||||
if (!base || size == 0) {
|
||||
return 0; // Invalid arguments
|
||||
}
|
||||
|
||||
RegionIdBox* box = &g_region_id_box;
|
||||
|
||||
// Create new entry
|
||||
RegionEntry entry = {
|
||||
.base = (uintptr_t)base,
|
||||
.end = (uintptr_t)base + size,
|
||||
.kind = kind,
|
||||
.metadata = metadata,
|
||||
.id = box->next_id,
|
||||
.active = 1
|
||||
};
|
||||
|
||||
// Insert into registry
|
||||
if (region_entry_insert(&entry) != 0) {
|
||||
return 0; // Registry full
|
||||
}
|
||||
|
||||
uint32_t id = box->next_id++;
|
||||
|
||||
// OBSERVE mode logging
|
||||
region_id_observe_register(base, size, kind, id);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void region_id_unregister_v6(uint32_t region_id) {
|
||||
(void)region_id;
|
||||
// V6-HDR-1: No-op
|
||||
if (region_id == 0 || region_id == 1) {
|
||||
return; // Invalid or reserved ID
|
||||
}
|
||||
|
||||
// OBSERVE mode logging
|
||||
region_id_observe_unregister(region_id);
|
||||
|
||||
region_entry_remove(region_id);
|
||||
}
|
||||
|
||||
bool region_id_is_valid_v6(uint32_t region_id) {
|
||||
return region_id == 1; // Only TLS segment is "registered"
|
||||
if (region_id == 0) return false;
|
||||
if (region_id == 1) return true; // Reserved for TLS segment
|
||||
|
||||
RegionIdBox* box = &g_region_id_box;
|
||||
for (uint32_t i = 0; i < box->count; i++) {
|
||||
if (box->entries[i].id == region_id && box->entries[i].active) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@ -230,13 +373,33 @@ const char* region_kind_to_string(region_kind_t kind) {
|
||||
case REGION_KIND_POOL_V1: return "POOL_V1";
|
||||
case REGION_KIND_LARGE: return "LARGE";
|
||||
case REGION_KIND_TINY_LEGACY: return "TINY_LEGACY";
|
||||
case REGION_KIND_MID_V3: return "MID_V3";
|
||||
default: return "INVALID";
|
||||
}
|
||||
}
|
||||
|
||||
void region_id_box_dump(void) {
|
||||
fprintf(stderr, "[REGION_ID_BOX] V6-HDR-1: TLS segment lookup only\n");
|
||||
fprintf(stderr, "[REGION_ID_BOX] V6-HDR-2: TLS segment + Registry\n");
|
||||
fprintf(stderr, "[REGION_ID_BOX] observe=%d\n", region_id_observe_enabled());
|
||||
|
||||
RegionIdBox* box = &g_region_id_box;
|
||||
fprintf(stderr, "[REGION_ID_BOX] next_id=%u count=%u\n", box->next_id, box->count);
|
||||
|
||||
// Dump TLS segment info
|
||||
if (g_v6_segment_registered) {
|
||||
fprintf(stderr, "[REGION_ID_BOX] TLS segment: base=%p end=%p (reserved id=1)\n",
|
||||
(void*)g_v6_segment_base, (void*)g_v6_segment_end);
|
||||
}
|
||||
|
||||
// Dump registered regions
|
||||
for (uint32_t i = 0; i < box->count; i++) {
|
||||
RegionEntry* e = &box->entries[i];
|
||||
if (e->active) {
|
||||
fprintf(stderr, "[REGION_ID_BOX] [%u] id=%u kind=%s base=%p end=%p meta=%p\n",
|
||||
i, e->id, region_kind_to_string(e->kind),
|
||||
(void*)e->base, (void*)e->end, e->metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
283
docs/analysis/MID_POOL_V3_DESIGN.md
Normal file
283
docs/analysis/MID_POOL_V3_DESIGN.md
Normal file
@ -0,0 +1,283 @@
|
||||
# MID_POOL_V3 設計書
|
||||
|
||||
## 概要
|
||||
|
||||
Mid/Pool v3 は既存の SmallObject v4 (MF2) を発展させ、RegionIdBox による ptr→page_meta O(1) lookup を統合した次世代アーキテクチャ。
|
||||
|
||||
## Phase Plan
|
||||
|
||||
| Phase | 内容 | 依存 |
|
||||
|-------|------|------|
|
||||
| MID-V3-0 | 設計 doc (本文書) | - |
|
||||
| MID-V3-1 | 型スケルトン + ENV | MID-V3-0 |
|
||||
| MID-V3-2 | RegionIdBox Registration API 完成 (V6-HDR-2) | MID-V3-1 |
|
||||
| MID-V3-3 | RegionId 統合 (page registration at carve) | MID-V3-2 |
|
||||
| MID-V3-4 | Allocation fast path 実装 | MID-V3-3 |
|
||||
| MID-V3-5 | Free/cold path 実装 | MID-V3-4 |
|
||||
|
||||
## 設計課題: Lane vs Page 二重管理問題
|
||||
|
||||
### 問題点 (Task Review で指摘)
|
||||
|
||||
当初の設計案では Lane と Page の両方で freelist を管理することを想定していたが、
|
||||
既存 v4 MF2 では per-page freelist が既に機能しており、
|
||||
Lane を追加すると管理責任が二重化する。
|
||||
|
||||
### 既存 v4 MF2 構造
|
||||
|
||||
```c
|
||||
// core/smallobject_hotbox_v4.c
|
||||
typedef struct small_page_v4 {
|
||||
uint8_t class_idx;
|
||||
uint16_t capacity;
|
||||
uint16_t used;
|
||||
uint32_t block_size;
|
||||
uint8_t* base;
|
||||
void* freelist; // ← Per-page freelist
|
||||
void* slab_ref;
|
||||
void* segment;
|
||||
struct small_page_v4* next;
|
||||
uint16_t flags;
|
||||
} small_page_v4;
|
||||
|
||||
typedef struct small_class_heap_v4 {
|
||||
small_page_v4* current; // Current working page
|
||||
small_page_v4* partial_head; // Partial pages list
|
||||
uint32_t partial_count;
|
||||
small_page_v4* full_head; // Full pages list
|
||||
} small_class_heap_v4;
|
||||
```
|
||||
|
||||
### 解決策: Lane = Page Index Cache
|
||||
|
||||
Lane を独立した freelist 管理単位としてではなく、
|
||||
**TLS が現在作業中の page への index/cache** として再定義する。
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ MidHotBoxV3 (L0 TLS) │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ lane[class] = { page_idx, freelist_cache } │ │
|
||||
│ │ ↓ │ │
|
||||
│ │ page_idx → MidPageDesc (via RegionIdBox) │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
↓ refill/retire
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ MidColdIfaceV3 (L1) │
|
||||
│ - page carve (segment → page) │
|
||||
│ - page return (page → segment) │
|
||||
│ - RegionIdBox registration │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Lane 構造 (Revised)
|
||||
|
||||
```c
|
||||
typedef struct MidLaneV3 {
|
||||
uint32_t page_idx; // Current working page index
|
||||
void* freelist_head; // TLS-local freelist snapshot (fast path)
|
||||
uint32_t freelist_count; // Remaining items in freelist
|
||||
// Note: 実際の freelist は MidPageDesc にあり、
|
||||
// lane は TLS cache として機能
|
||||
} MidLaneV3;
|
||||
```
|
||||
|
||||
### Page 構造
|
||||
|
||||
```c
|
||||
typedef struct MidPageDescV3 {
|
||||
uint8_t* base; // Page base address
|
||||
uint32_t capacity; // Total slots
|
||||
uint32_t used; // Used count
|
||||
void* freelist; // Actual freelist (authoritative)
|
||||
uint32_t region_id; // RegionIdBox registration ID
|
||||
uint8_t class_idx; // Size class
|
||||
uint8_t flags; // Page state flags
|
||||
} MidPageDescV3;
|
||||
```
|
||||
|
||||
## RegionIdBox 統合
|
||||
|
||||
### 現状 (V6-HDR-1)
|
||||
|
||||
`region_id_register_v6()` は stub 状態:
|
||||
|
||||
```c
|
||||
// core/region_id_v6.c:202
|
||||
uint32_t region_id_register_v6(void* base, size_t size, region_kind_t kind, void* metadata) {
|
||||
(void)base;
|
||||
(void)size;
|
||||
(void)kind;
|
||||
(void)metadata;
|
||||
return 1; // Single region for now
|
||||
}
|
||||
```
|
||||
|
||||
### V6-HDR-2: Registration API 完成 (MID-V3-2)
|
||||
|
||||
```c
|
||||
// 必要な機能:
|
||||
// 1. Region entry array (固定サイズ or 動的)
|
||||
// 2. ptr → region_entry lookup (radix tree or sorted array)
|
||||
// 3. Thread-safe registration/unregistration
|
||||
|
||||
typedef struct RegionEntry {
|
||||
uintptr_t base;
|
||||
uintptr_t end;
|
||||
region_kind_t kind;
|
||||
void* metadata; // MidPageDescV3* for SMALL_V4
|
||||
uint32_t id;
|
||||
} RegionEntry;
|
||||
|
||||
// API
|
||||
uint32_t region_id_register_v6(void* base, size_t size, region_kind_t kind, void* metadata);
|
||||
void region_id_unregister_v6(uint32_t region_id);
|
||||
RegionLookupV6 region_id_lookup_v6(void* ptr);
|
||||
```
|
||||
|
||||
### MID-V3-3: Page Registration at Carve
|
||||
|
||||
```c
|
||||
// Page carve 時に RegionIdBox に登録
|
||||
MidPageDescV3* mid_cold_v3_carve_page(MidSegmentV3* seg, int class_idx) {
|
||||
MidPageDescV3* page = /* ... carve from segment ... */;
|
||||
|
||||
// RegionIdBox に登録
|
||||
page->region_id = region_id_register_v6(
|
||||
page->base,
|
||||
page->capacity * stride_for_class(class_idx),
|
||||
REGION_KIND_SMALL_V4, // or new REGION_KIND_MID_V3
|
||||
page
|
||||
);
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
// Page return 時に登録解除
|
||||
void mid_cold_v3_return_page(MidPageDescV3* page) {
|
||||
region_id_unregister_v6(page->region_id);
|
||||
/* ... return to segment ... */
|
||||
}
|
||||
```
|
||||
|
||||
## Allocation Fast Path (MID-V3-4)
|
||||
|
||||
```c
|
||||
void* mid_hot_v3_alloc(MidHotBoxV3* hot, int class_idx) {
|
||||
MidLaneV3* lane = &hot->lanes[class_idx];
|
||||
|
||||
// L0: TLS freelist cache hit
|
||||
if (likely(lane->freelist_head)) {
|
||||
void* blk = lane->freelist_head;
|
||||
lane->freelist_head = *(void**)blk;
|
||||
lane->freelist_count--;
|
||||
return blk;
|
||||
}
|
||||
|
||||
// L0 miss: Refill from page or cold path
|
||||
return mid_hot_v3_alloc_slow(hot, class_idx);
|
||||
}
|
||||
|
||||
static void* mid_hot_v3_alloc_slow(MidHotBoxV3* hot, int class_idx) {
|
||||
MidLaneV3* lane = &hot->lanes[class_idx];
|
||||
|
||||
// Try to refill from current page
|
||||
if (lane->page_idx != 0) {
|
||||
MidPageDescV3* page = mid_page_from_idx(lane->page_idx);
|
||||
if (page && page->freelist) {
|
||||
// Batch transfer from page to lane
|
||||
mid_lane_refill_from_page(lane, page);
|
||||
return mid_hot_v3_alloc(hot, class_idx);
|
||||
}
|
||||
}
|
||||
|
||||
// Cold path: Get new page
|
||||
MidPageDescV3* new_page = mid_cold_v3_refill_page(hot, class_idx);
|
||||
if (!new_page) return NULL;
|
||||
|
||||
lane->page_idx = mid_page_to_idx(new_page);
|
||||
mid_lane_refill_from_page(lane, new_page);
|
||||
return mid_hot_v3_alloc(hot, class_idx);
|
||||
}
|
||||
```
|
||||
|
||||
## Free Path (MID-V3-5)
|
||||
|
||||
```c
|
||||
void mid_hot_v3_free(void* ptr) {
|
||||
// RegionIdBox lookup (O(1) via TLS cache)
|
||||
RegionLookupV6 lk = region_id_lookup_cached_v6(ptr);
|
||||
|
||||
if (lk.kind != REGION_KIND_MID_V3) {
|
||||
// Not our allocation, delegate
|
||||
return;
|
||||
}
|
||||
|
||||
MidPageDescV3* page = (MidPageDescV3*)lk.page_meta;
|
||||
|
||||
// Check if local thread owns this page
|
||||
MidHotBoxV3* hot = mid_hot_box_v3_get();
|
||||
MidLaneV3* lane = &hot->lanes[page->class_idx];
|
||||
|
||||
if (lane->page_idx == mid_page_to_idx(page)) {
|
||||
// Local page: direct push to lane freelist
|
||||
*(void**)ptr = lane->freelist_head;
|
||||
lane->freelist_head = ptr;
|
||||
lane->freelist_count++;
|
||||
return;
|
||||
}
|
||||
|
||||
// Remote page: push to page freelist (atomic if needed)
|
||||
mid_page_push_free(page, ptr);
|
||||
}
|
||||
```
|
||||
|
||||
## ENV Controls
|
||||
|
||||
```
|
||||
HAKMEM_MID_V3_ENABLED=1 # Enable MID v3 (default: 0)
|
||||
HAKMEM_MID_V3_CLASSES=0xFF # Class bitmask (default: 0xFF = all)
|
||||
HAKMEM_MID_V3_DEBUG=1 # Debug logging (default: 0)
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] MID-V3-1: 型スケルトン + ENV
|
||||
- [ ] MidHotBoxV3 structure
|
||||
- [ ] MidLaneV3 structure
|
||||
- [ ] MidPageDescV3 structure
|
||||
- [ ] MidColdIfaceV3 interface
|
||||
- [ ] ENV parsing
|
||||
|
||||
- [ ] MID-V3-2: RegionIdBox Registration API (V6-HDR-2)
|
||||
- [ ] RegionEntry structure
|
||||
- [ ] region_id_register_v6() implementation
|
||||
- [ ] region_id_unregister_v6() implementation
|
||||
- [ ] Lookup integration (ptr → page_meta)
|
||||
|
||||
- [ ] MID-V3-3: RegionId 統合
|
||||
- [ ] Page carve time registration
|
||||
- [ ] Page return time unregistration
|
||||
- [ ] TLS segment auto-registration
|
||||
|
||||
- [ ] MID-V3-4: Allocation fast path
|
||||
- [ ] Lane freelist fast path
|
||||
- [ ] Page refill slow path
|
||||
- [ ] Cold refill integration
|
||||
|
||||
- [ ] MID-V3-5: Free/cold path
|
||||
- [ ] RegionIdBox lookup in free
|
||||
- [ ] Local page fast free
|
||||
- [ ] Remote page handling
|
||||
|
||||
## 参考: 既存コードとの関係
|
||||
|
||||
| 既存 | v3 対応 |
|
||||
|------|---------|
|
||||
| smallobject_hotbox_v4.c | mid_hotbox_v3.c (新規) |
|
||||
| small_page_v4 | MidPageDescV3 |
|
||||
| small_class_heap_v4 | MidLaneV3 |
|
||||
| cold_refill_page_v4() | mid_cold_v3_refill_page() |
|
||||
| cold_retire_page_v4() | mid_cold_v3_retire_page() |
|
||||
| region_id_v6.c | RegionIdBox API 拡張 |
|
||||
Reference in New Issue
Block a user