aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-18 16:44:17 +0100
committereunhhu <[email protected]>2026-10-18 16:44:17 +0100
commit433f56c29b26b83cd2991b4a834b427fae3e75d4 (patch)
tree2ef84f30892e5dcabb196d63b1016d255d759beb
parenta9adaf2a1a62e5dd37f0067653b35e9cfa2c1860 (diff)
downloadpure-433f56c29b26b83cd2991b4a834b427fae3e75d4.tar.gz
pure-433f56c29b26b83cd2991b4a834b427fae3e75d4.zip
Module executor: a call's frame counted in the event's room by a table, as gas is (PURE_CALL_*: 256 a frame, 64 a value of its stack's height, 8 a local, 128 a loop it nests, 1024 a list of its own, 128 a held value, 16 a register; each at least what it stands for, checked as it compiles), not by the implementation's sizes: a module's calls fail at the same depth on every machine and build; a call's list let go is kept for the next calls unless what it keeps empty passes what a frame counts for it
-rw-r--r--core/module-list.c2
-rw-r--r--core/module.c14
-rw-r--r--core/module.h5
-rw-r--r--docs/design/008-module-format.md3
4 files changed, 21 insertions, 3 deletions
diff --git a/core/module-list.c b/core/module-list.c
index 67fbbdb..57e44c4 100644
--- a/core/module-list.c
+++ b/core/module-list.c
@@ -1580,6 +1580,8 @@ static bool logged(PureList *list, const PureUndo *undo) {
/* ---- The list */
+_Static_assert(sizeof(PureList) <= PURE_CALL_LIST, "a call's list counts at least its own bytes (module.h)");
+
PureList *pure_list_new(unsigned fields, const uint8_t *kinds) {
if (fields > 16) return NULL;
PureList *list = calloc(1, sizeof(*list));
diff --git a/core/module.c b/core/module.c
index fec9dd9..19fb7fb 100644
--- a/core/module.c
+++ b/core/module.c
@@ -4157,6 +4157,9 @@ typedef struct Call {
Walk *walk;
} Call;
typedef __typeof__(((PureApp *)0)->held[0]) Kept;
+_Static_assert(sizeof(Call) <= PURE_CALL_BYTES && sizeof(Slot) <= PURE_CALL_VALUE && sizeof(PureValue) <= PURE_CALL_LOCAL &&
+ sizeof(Loop) <= PURE_CALL_LOOP && sizeof(Kept) <= PURE_CALL_HELD && sizeof(PureList *) <= PURE_CALL_REGISTER,
+ "a frame counts at least what it holds");
/* Room for a frame of bytes among the app's blocks: where they stood before it, or NULL out of memory. */
static void *frame_room(PureApp *app, size_t bytes, unsigned *block, size_t *used) {
@@ -4193,12 +4196,14 @@ static PureList *spare_list(PureApp *app, uint32_t state) {
return spare->count ? spare->lists[--spare->count] : new_list(app->module, app->module->state_ref[state]);
}
-/* A call's list let go: emptied (what it held no longer counted) and kept for the next calls. */
+/* A call's list let go: emptied (what it held no longer counted) and kept for the next calls, unless what it keeps
+ * when empty passes what a frame counts for it. */
static void let_list_go(PureApp *app, uint32_t state, PureList *list) {
Held before = list_held(app, list);
pure_list_clear(list);
(void)grows(app, before, list_held(app, list));
__typeof__(app->spare[0]) *spare = &app->spare[state];
+ if (pure_list_extra(list) > PURE_CALL_LIST) { pure_list_free(list); return; }
if (spare->count == spare->capacity) {
unsigned capacity = spare->capacity ? 2 * spare->capacity : 4;
PureList **lists = realloc(spare->lists, capacity * sizeof(*lists));
@@ -4268,9 +4273,12 @@ static __attribute__((noinline, cold)) int enter_call(Frame *frame, Call **top,
unsigned keeps = (unsigned)__builtin_popcount(f->lists | f->registers), holds = (unsigned)__builtin_popcount(f->held);
size_t bytes = (sizeof(Call) + keeps * sizeof(PureList *) + holds * sizeof(Kept) + f->height * sizeof(Slot) +
f->locals * sizeof(PureValue) + f->nesting * sizeof(Loop) + 15) & ~(size_t)15;
+ size_t counted = PURE_CALL_BYTES + (size_t)f->height * PURE_CALL_VALUE + (size_t)f->locals * PURE_CALL_LOCAL +
+ (size_t)f->nesting * PURE_CALL_LOOP + (size_t)__builtin_popcount(f->lists) * PURE_CALL_LIST +
+ (size_t)holds * PURE_CALL_HELD + (size_t)__builtin_popcount(f->registers) * PURE_CALL_REGISTER;
frame->fault_pc = *pc - 1;
if (!charge(app, f->code.length)) { frame->kind = PURE_FAULT_WORK; return STEP_FAULT; }
- if (!grows(app, (Held){0, 0}, (Held){0, bytes})) { frame->kind = PURE_FAULT_MEMORY; return STEP_FAULT; }
+ if (!grows(app, (Held){0, 0}, (Held){0, counted})) { frame->kind = PURE_FAULT_MEMORY; return STEP_FAULT; }
unsigned block;
size_t used, base = frame->sp - f->params;
Call *call = frame_room(app, bytes, &block, &used);
@@ -4308,7 +4316,7 @@ static __attribute__((noinline, cold)) int enter_call(Frame *frame, Call **top,
*h = (Kept){0};
}
*call = (Call){.below = *top, .back = *pc, .end = frame->end, .function = function, .depth = frame->depth, .block = block,
- .sp = base, .used = used, .bytes = bytes, .row = ev->row, .stack = frame->stack, .loops = frame->loops,
+ .sp = base, .used = used, .bytes = counted, .row = ev->row, .stack = frame->stack, .loops = frame->loops,
.locals = ev->locals, .walk = ev->walk};
if (f->locals) memset(locals, 0, f->locals * sizeof(*locals));
frame->stack = stack, frame->sp = f->params, frame->loops = (Loop *)(locals + f->locals), frame->depth = 0;
diff --git a/core/module.h b/core/module.h
index 9ce354f..18947d0 100644
--- a/core/module.h
+++ b/core/module.h
@@ -131,6 +131,11 @@ enum { PURE_FORMAT_WIDTH = 8, PURE_FORMAT_ZEROS = 1 << 16, PURE_FORMAT_FORM = 17
* text of a code point, failing as LIMIT outside 0-0x10FFFF and as ENCODING
* for a surrogate. */
enum { PURE_MODULE_FUNCTIONS = 64, PURE_MODULE_SIGNATURES = 1024, PURE_SIGNATURE_BYTES = 5 };
+/* What a call's frame counts in the event's room: a table, as gas is, not the implementation's sizes (each at least
+ * what it stands for), so a module's calls fail at the same depth wherever and on whatever build they run: so much a
+ * frame, a value of its stack's height, a local, a loop it nests, a list of its own, a held value and a register. */
+enum { PURE_CALL_BYTES = 256, PURE_CALL_VALUE = 64, PURE_CALL_LOCAL = 8, PURE_CALL_LOOP = 128, PURE_CALL_LIST = 1024,
+ PURE_CALL_HELD = 128, PURE_CALL_REGISTER = 16 };
/* A local: one of an event's PURE_MODULE_LOCALS slots, 0 when the event
* begins and never saved. LOCAL_LOAD and LOCAL_STORE name the slot (bits
* 0-7), what it holds (bits 8-15: 0 a number, 1 an index, 2 a bool, 3 an i64,
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md
index 4c561f8..84c8e18 100644
--- a/docs/design/008-module-format.md
+++ b/docs/design/008-module-format.md
@@ -47,6 +47,9 @@ v6에는 뒤에 **i64**가 더해졌다([016](016-bool-and-i64.md)의 2단계):
- 함수의 코드는 메시지가 없는 핸들러의 코드다(필드, 문서, 응답이 없다). 상태를 바꿀 수 있다. CALL은 핸들러와 함수의 코드에만
있다(보기에는 없다).
- 부름의 깊이는 이벤트의 몫이 묶는다. 부름마다 그 프레임의 바이트를 몫에 세고(넘으면 `memory`), 블록 길이를 실행량에 센다.
+ 프레임의 바이트는 구현의 크기가 아닌 표다(`PURE_CALL_*`). 프레임 256, 스택 높이의 값마다 64, 지역 칸마다 8, 반복의 겹마다 128,
+ 제 리스트마다 1024, 담은 값마다 128, 레지스터마다 16이다. 각 값은 그것이 나타내는 구현의 크기 이상이다(컴파일할 때 확인한다).
+ 그래서 같은 모듈은 어느 기계, 어느 빌드에서나 같은 깊이에서 실패한다.
## 바이트 배치