diff options
| author | eunhhu <[email protected]> | 2026-09-25 15:08:34 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-25 15:08:34 +0100 |
| commit | 2ae802cfaa3dae01627d4c80e2655fb5e69f4e1e (patch) | |
| tree | d78772098f1f22f08c4c413dc92a244939677184 | |
| parent | 82f685e5583d3edc4e5d9d327e9925705ce51b1d (diff) | |
| download | pure-2ae802cfaa3dae01627d4c80e2655fb5e69f4e1e.tar.gz pure-2ae802cfaa3dae01627d4c80e2655fb5e69f4e1e.zip | |
Add stack copies to Pure modules
PICK copies the value n below the top, with its type and owner; SLIDE
drops n values under the top. A toolchain can compute a function's
arguments once and read each as often as the inlined body needs, instead
of repeating the argument's code. The format stays v5: no producer has
shipped modules without them.
| -rw-r--r-- | docs/design/008-module-format.md | 5 | ||||
| -rw-r--r-- | prototypes/connections/module.c | 19 | ||||
| -rw-r--r-- | prototypes/connections/module.h | 1 | ||||
| -rw-r--r-- | prototypes/connections/test-module.c | 33 |
4 files changed, 54 insertions, 4 deletions
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md index a78c5f2..cc1a1a1 100644 --- a/docs/design/008-module-format.md +++ b/docs/design/008-module-format.md @@ -10,7 +10,8 @@ v1(스칼라)은 워크벤치 앱과 같은 의미였다: f64 상태, 키 입력 v2는 타입 있는 바이트 값, 필드가 있는 **메시지 입력**, 키별 **영속 컬렉션**, 응답을 더했다. v3은 **text 상태와 커서**, text 상수, text 입력, text 보기를 더했다. v4는 **레코드**, 레코드의 **리스트** 상태, 리스트의 **인덱스** 상태를 더했다. -v5는 값을 가진 채 합류하는 분기(조건식)와, 리스트 항목마다 행을 내는 **표 보기**를 더한다. 이전 판은 받지 않는다(생산자는 aup 하나다). +v5는 값을 가진 채 합류하는 분기(조건식)와, 리스트 항목마다 행을 내는 **표 보기**, 한 번 계산한 값을 여러 번 +쓰게 하는 스택 복사(PICK·SLIDE, 툴체인의 함수 인라인용)를 더한다. 이전 판은 받지 않는다(생산자는 aup 하나다). ## 바이트 배치 @@ -92,6 +93,8 @@ v5는 값을 가진 채 합류하는 분기(조건식)와, 리스트 항목마� | 59–60 | INDEX_FIRST INDEX_LAST | → i | 리스트 상태 `인자`의 첫·마지막 항목(빈 리스트면 0) | | 61 | INDEX_NUM | i → n | 0부터 센 위치 | | 62 | ROW | → i | 열이 계산하는 행. `인자`는 그 표의 리스트 상태. 열 전용 | +| 63 | PICK | … → … v | `인자`개 아래의 값을 복사해 올린다(0이면 맨 위). 타입과 대상도 그대로 | +| 64 | SLIDE | … v → v | 맨 위 값 아래의 `인자`개(1 이상)를 버린다 | `f`는 레코드 필드의 타입(수 또는 text)이다. 수 연산 결과가 유한하지 않으면 실패한다. 실패하면 그 이벤트의 모든 핸들러가 한 일이 취소된다. diff --git a/prototypes/connections/module.c b/prototypes/connections/module.c index 4b5d15d..3d3ee3e 100644 --- a/prototypes/connections/module.c +++ b/prototypes/connections/module.c @@ -82,6 +82,9 @@ static const struct { unsigned char pops, in[3], out, where; } signature[PURE_OP [PURE_OP_INDEX_LAST] = {0, {0}, T_INDEX, READ}, [PURE_OP_INDEX_NUM] = {1, {T_INDEX}, T_NUM, READ}, [PURE_OP_ROW] = {0, {0}, T_INDEX, IN_ROW}, + /* Stack copies: their types come from the values they move. */ + [PURE_OP_PICK] = {0, {0}, 0, READ}, + [PURE_OP_SLIDE] = {0, {0}, 0, READ}, }; static uint32_t read16(const unsigned char *p) { return (uint32_t)p[0] | (uint32_t)p[1] << 8; } @@ -152,7 +155,7 @@ static bool uses_arg(uint8_t op) { case PURE_OP_TEXT_LINE: case PURE_OP_TEXT_SET: case PURE_OP_CUR_COUNT: case PURE_OP_LIST_COUNT: case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: case PURE_OP_LIST_REMOVE: case PURE_OP_ITEM_GET: case PURE_OP_ITEM_SET: case PURE_OP_INDEX_LOAD: case PURE_OP_INDEX_STORE: case PURE_OP_INDEX_FIRST: - case PURE_OP_INDEX_LAST: case PURE_OP_ROW: + case PURE_OP_INDEX_LAST: case PURE_OP_ROW: case PURE_OP_PICK: case PURE_OP_SLIDE: return true; default: return false; } @@ -260,6 +263,18 @@ static bool verify_block(const PureModule *m, PureBlock block, int view, int row default: break; } if (!known) return reject(error, size, "operand names no such state, field, constant or unit", pc); + /* PICK copies the value arg below the top; SLIDE drops arg values under the top. */ + if (op->op == PURE_OP_PICK || op->op == PURE_OP_SLIDE) { + bool pick = op->op == PURE_OP_PICK; + if (pick ? arg >= (uint32_t)height : !arg || arg >= (uint32_t)height) + return reject(error, size, "stack underflow", pc); + if (pick && height == PURE_MODULE_STACK) return reject(error, size, "stack overflow", pc); + int from = height - 1 - (int)arg, to = pick ? height++ : from; + types[to] = pick ? types[from] : types[height - 1]; + owners[to] = pick ? owners[from] : owners[height - 1]; + if (!pick) height = from + 1; + continue; + } /* Operand types, bottom to top; record operations take their fields. */ unsigned char need[PURE_RECORD_FIELDS + 2]; unsigned pops = signature[op->op].pops; @@ -992,6 +1007,8 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even stack[sp++].bytes.owner = (uint16_t)op->arg; ++pc; continue; + case PURE_OP_PICK: stack[sp] = stack[sp - 1 - op->arg]; ++sp; ++pc; continue; + case PURE_OP_SLIDE: stack[sp - 1 - op->arg] = stack[sp - 1]; sp -= op->arg; ++pc; continue; default: break; } b = stack[--sp].num; diff --git a/prototypes/connections/module.h b/prototypes/connections/module.h index 749f9d0..dd20d4a 100644 --- a/prototypes/connections/module.h +++ b/prototypes/connections/module.h @@ -37,6 +37,7 @@ typedef enum { PURE_OP_LIST_COUNT, PURE_OP_LIST_APPEND, PURE_OP_LIST_INSERT, PURE_OP_LIST_REMOVE, PURE_OP_ITEM_GET, PURE_OP_ITEM_SET, PURE_OP_INDEX_LOAD, PURE_OP_INDEX_STORE, PURE_OP_INDEX_MOVE, PURE_OP_INDEX_FIRST, PURE_OP_INDEX_LAST, PURE_OP_INDEX_NUM, PURE_OP_ROW, + PURE_OP_PICK, PURE_OP_SLIDE, PURE_OP_COUNT } PureOpcode; diff --git a/prototypes/connections/test-module.c b/prototypes/connections/test-module.c index 3c12d49..a4e746c 100644 --- a/prototypes/connections/test-module.c +++ b/prototypes/connections/test-module.c @@ -1099,7 +1099,7 @@ static const char *cell_text(PureApp *app, uint32_t column, size_t row) { return t.text; } -enum { ROW = PURE_OP_ROW, EQ = PURE_OP_EQ, LT = PURE_OP_LT }; +enum { ROW = PURE_OP_ROW, EQ = PURE_OP_EQ, LT = PURE_OP_LT, PICK = PURE_OP_PICK, SLIDE = PURE_OP_SLIDE }; /* A table view has a row per item; its columns read the row and may branch. * A value crosses a join when every path brings the same stack. */ @@ -1193,6 +1193,35 @@ static void tables(void) { CHECK(load(bad, &module_b) == (open == 8 ? PURE_OK : PURE_INVALID)); } CHECK(strstr(error, "more than 8 joins ahead")); + + /* PICK copies a value from below, SLIDE drops values under the top: a + * value used twice is computed once. (3 + 4) * (3 + 4) with a text between. */ + static const O picked[] = {{C, 0, 3}, {C, 0, 4}, {ADD, 0, 0}, {TK, 1, 0}, {PICK, 1, 0}, {PICK, 2, 0}, {MUL, 0, 0}, + {SLIDE, 2, 0}}; + static const V squared[] = {{"sq", 0, 8}}; + bad = (Spec){nums, 1, inputs, 0, squared, 1, NULL, 0, picked, 8, NULL, 0, NULL, 0, constants, 2, NULL, 0, NULL, 0, NULL, 0}; + CHECK(load(bad, &module_b) == PURE_OK); + CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK && number_view(&app, 0) == 49); + pure_app_stop(&app); + static const O pick_text[] = {{TK, 1, 0}, {C, 0, 1}, {PICK, 1, 0}, {SLIDE, 2, 0}}; + static const V text_left[] = {{"t", 0, 4}}; + bad.v = NULL; bad.nv = 0; bad.tv = text_left; bad.ntv = 1; bad.o = pick_text; bad.no = 4; + CHECK(load(bad, &module_b) == PURE_OK); + CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK && !strcmp(view_text(&app, 0), "x")); + pure_app_stop(&app); + static const O pick_type[] = {{TK, 1, 0}, {PICK, 0, 0}, {C, 0, 1}, {ADD, 0, 0}, {SLIDE, 1, 0}}; + static const V five[] = {{"sq", 0, 5}}, three[] = {{"sq", 0, 3}}; + bad.o = pick_type; bad.no = 5; bad.tv = NULL; bad.ntv = 0; bad.v = five; bad.nv = 1; + CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "operand type")); /* a copy keeps its type */ + static const O pick_far[] = {{C, 0, 1}, {PICK, 1, 0}, {SLIDE, 1, 0}}; + bad.o = pick_far; bad.no = 3; bad.v = three; + CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "stack underflow")); + static const O slide_all[] = {{C, 0, 1}, {C, 0, 2}, {SLIDE, 2, 0}}; + bad.o = slide_all; + CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "stack underflow")); + static const O slide_none[] = {{C, 0, 1}, {C, 0, 2}, {SLIDE, 0, 0}}; + bad.o = slide_none; + CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "stack underflow")); } /* Damaged table modules either fail to load or evaluate every cell safely. */ @@ -1247,6 +1276,6 @@ int main(void) { tables(); table_mutations(); printf("{\"sizeof_module\":%zu,\"sizeof_app\":%zu}\n", sizeof(PureModule), sizeof(PureApp)); - puts("{\"module_tests\":\"verified-load-atomic-events-faults-signed-zero-if-else-replace-by-name-rejections-mutations-messages-documents-typed-verification-text-cursors-utf8-views-rollback-replace-lists-indices-undo-migration-joins-tables-columns\"}"); + puts("{\"module_tests\":\"verified-load-atomic-events-faults-signed-zero-if-else-replace-by-name-rejections-mutations-messages-documents-typed-verification-text-cursors-utf8-views-rollback-replace-lists-indices-undo-migration-joins-tables-columns-pick-slide\"}"); return 0; } |
