From a9adaf2a1a62e5dd37f0067653b35e9cfa2c1860 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 18 Oct 2026 16:05:34 +0100 Subject: Module format and executor: 027's stage 2c, functions and characters: a function is a block CALL runs (102), its parameters the stack it begins with and RETURN (103) its results on top, which take the arguments' place; each call has its own stack and locals and its own of what the function's code names (held values empty, the event's lists empty, registers bound to none), the caller's as they were when it returns; a function's code is a handler's but for a message, CALL in handlers and functions alone, and a walk around a call keeps to the lists the callee (or what it calls) changes; calls nest as deep as the event's room allows, each frame's bytes counted there (memory) and its block's length as work; the functions' table in the header's last u16 and after the handlers (a block, parameters and results) with their signature items (a record field's type, 5 bytes); run_calls, run_batches' steps with frames in the app's blocks that never move (a CALL opens one, a RETURN closes it, a stop closes all), an event's lists calls let go kept for the next; a value held by a slot the callee has its own of copied to the arena as it crosses; held values of the payload, the arguments or the constants held as they are (nothing copied until appended to); the JIT leaves blocks with calls or list locals to the executor; local type 4 (a list value, its record in bits 16-31); CHAR_AT (104, the code point beginning at a byte position of a text) and CHAR (105, a code point's text: limit outside 0-0x10FFFF, encoding for a surrogate); and a hole closed: LOOP and EACH open on a stack holding their operands alone (a loop's block begins at the bottom of its run's stack: a verified module with values below a loop crashed the executor, 62 below and 64 in its block); unit tests (fib, a tree summed by a walk that calls itself around its register, each call's own list and held text, a list through a local, characters and their faults, a call without end failing as memory and as work with nothing changed, 11 refused; under ASan and UBSan too) --- docs/design/008-module-format.md | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) (limited to 'docs/design/008-module-format.md') diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md index 642da9b..4c561f8 100644 --- a/docs/design/008-module-format.md +++ b/docs/design/008-module-format.md @@ -24,7 +24,8 @@ v6에는 뒤에 **i64**가 더해졌다([016](016-bool-and-i64.md)의 2단계): 그 뒤 [027](027-computation-core.md)의 1단계로 **비트 연산, 자리의 바이트, i64 리스트의 바이트, 자리로 찾은 항목**이 더해졌다(새 명령 넷, 90–93). 2a로 **바이트 자르기, 한 바이트, 이름으로 담은 바이트와 text**(94–97), CONCAT의 바이트, NUM_TEXT의 형식이 더해졌다. 2b로 **레코드 안의 리스트**(레코드 필드의 타입 4, 98–100), 레지스터(리스트 상태의 플래그 512), 경로 이름의 레코드 필드, **수 읽기**(101)가 -더해졌다. 판은 그대로다. +더해졌다. 2c로 **함수**(CALL·RETURN, 102–103, 머리말의 함수 수와 함수 표)와 **글자**(CHAR_AT·CHAR, 104–105)가 더해졌다. 판은 +그대로다(0이던 머리말 자리에 함수 수, 함수가 없는 모듈은 배치가 같다). **레코드 안의 리스트**(027의 2b). - 레코드 필드의 타입 4(LIST)는 항목들의 레코드를 기본값 자리(i64)에 둔다. @@ -38,6 +39,15 @@ v6에는 뒤에 **i64**가 더해졌다([016](016-bool-and-i64.md)의 2단계): 읽기만 한다. - 레코드 필드의 이름은 경로일 수 있다(`at.x`). 펼친 레코드 안의 레코드다. +**함수**(027의 2c). +- 함수는 CALL이 도는 블록이다. 부르는 쪽 스택의 인자들이 그 블록의 첫 스택(매개변수의 타입)이 되고, RETURN이 맨 위의 결과들을 + 내며 끝난다(스택의 나머지는 버린다). 결과는 부르는 쪽 스택에서 인자의 자리를 갖는다. +- 부름마다 제 것이 있다. 스택과 지역 칸(0에서 시작)이 그렇고, 함수의 코드가 이름을 대는 것도 그렇다. 이름으로 담은 값(비어 + 시작), 이벤트의 리스트(비어 시작), 레지스터(아무것에도 묶이지 않고 시작)다. 부르는 쪽의 것은 부름이 돌아올 때 그대로다. +- 함수의 코드는 메시지가 없는 핸들러의 코드다(필드, 문서, 응답이 없다). 상태를 바꿀 수 있다. CALL은 핸들러와 함수의 코드에만 + 있다(보기에는 없다). +- 부름의 깊이는 이벤트의 몫이 묶는다. 부름마다 그 프레임의 바이트를 몫에 세고(넘으면 `memory`), 블록 길이를 실행량에 센다. + ## 바이트 배치 모든 정수는 little-endian이고, 실수는 IEEE binary64 비트 그대로다. @@ -48,7 +58,7 @@ v6에는 뒤에 **i64**가 더해졌다([016](016-bool-and-i64.md)의 2단계): | 8 | u16 상태, u16 입력, u16 보기, u16 핸들러 | | 16 | u32 명령 수, u32 문자열 바이트 수 | | 24 | u16 필드, u16 컬렉션, u16 상수, u16 레코드 | -| 32 | u32 상수 블롭 바이트 수, u16 레코드 필드, u16 0 | +| 32 | u32 상수 블롭 바이트 수, u16 레코드 필드, u16 함수 | | 40 | 상태 × 16 B: u32 이름, u32 타입(비트 0–7: 0 수, 1 text, 2 커서, 3 리스트, 4 인덱스, 5 bool, 6 i64. 비트 8: 저장하지 않는 상태(transient, [018](018-lifetimes-and-computation.md)), 그 위는 0. 메시지의 행 목록은 transient가 아니고, transient text나 리스트를 가리키는 커서와 인덱스는 transient다), 8 B 값: 수와 bool은 f64 초기값(bool은 0이나 1), i64는 초기값(부호 있는 little-endian), text는 u32 초기 상수, 커서는 u32 대상 text 상태와 u32 플래그(1이면 마크: 그 자리에 넣은 글 앞에 남는다), 리스트는 u32 레코드와 u32 키(0이면 없음, 아니면 키 필드 + 1, 256이면 메시지의 행 목록: 이름은 그 필드의 경로 `입력.필드`이고 키가 없다), 인덱스는 u32 대상 리스트 상태 | | | 컬렉션 × 12 B: u32 이름, u32 1(바이트), u32 1(키별) | | | 입력 × 16 B: u32 이름, u8 종류(1 키, 2 메시지, 3 text, 4 타이머, 5 출력: 앱이 보내는 메시지, 모양은 메시지와 같고 핸들러가 없다, 6 인자: 한 보기가 받는 인자, 모양은 메시지와 같고 핸들러가 없으며 이름은 `보기.args`), u8 키별 여부, u16 0, u32 키 이름(키 입력)·주기(타이머, 1 ms–24시간을 ms로)·보기 번호(인자) 또는 0, u16 첫 필드, u16 필드 수 | @@ -58,6 +68,8 @@ v6에는 뒤에 **i64**가 더해졌다([016](016-bool-and-i64.md)의 2단계): | | 레코드 필드 × 16 B: u32 이름, u32 타입(0 수, 1 text, 2 bool, 3 i64), 8 B 기본값: 수와 bool은 f64(bool은 0이나 1), i64는 정수, text는 u32 상수 | | | 보기 × 16 B: u32 이름, u8 타입(0 수, 1 text, 2 표, 3 bool, 4 i64), u8 걸러진 표의 리스트 상태 + 1(그 밖에는 0), u16 표(열이면 그 표 보기 + 1, 아니면 0), u32 시작 명령, u32 명령 수. 표는 시작 명령 자리에 리스트 상태, 명령 수 0. 걸러진 표는 타입 다음 바이트에 리스트 상태 + 1, 시작·명령 수에 필터 블록 | | | 핸들러 × 12 B: u32 이벤트, u32 시작 명령, u32 명령 수 | +| | 함수 × 12 B: u32 시작 명령, u32 명령 수, u8 매개변수 수, u8 결과 수, u16 0 | +| | 서명 항목 × 4 B: 함수마다 매개변수, 그다음 결과의 차례로. u8 타입(레코드 필드의 타입: 0 수, 1 text, 2 bool, 3 i64, 4 리스트, 그리고 5 바이트), u8 0, u16 리스트의 레코드(그 밖에는 0) | | | 명령 × 16 B: u8 opcode, 0 3바이트, u32 인자, f64 값 | | | 문자열: `[u8 길이][바이트]`의 연속 | | | 상수 블롭 (최대 8 KiB) | @@ -153,6 +165,10 @@ v6에는 뒤에 **i64**가 더해졌다([016](016-bool-and-i64.md)의 2단계): | 99 | LIST_BIND | l → | 묶이는 리스트 상태 `인자`(레지스터)가 그 리스트를 읽게 한다 | | 100 | APPEND_IN | i f… → | 리스트 상태 `인자`의 비트 0–15(이벤트의 리스트)에서 인덱스의 항목, 그 필드(비트 16–31, 리스트 필드)에 담긴 리스트에 항목을 덧붙인다. 그 리스트를 다른 값이 나눠 가지면 먼저 제 것으로 복사한다 | | 101 | NUM_PARSE | t → n | JSON의 수로 쓴 text의 가장 가까운 수. 그런 text가 아니거나 유한하지 않으면 `limit` | +| 102 | CALL | a… → r… | 함수 `인자`를 부른다. 매개변수를 꺼내고 결과를 놓는다. 위 "함수" | +| 103 | RETURN | … r… → | 함수 `인자`(자신)를 끝낸다. 맨 위의 결과가 부른 쪽으로 간다 | +| 104 | CHAR_AT | t q → q | text의 바이트 자리 q(i64)에서 시작하는 글자의 코드 포인트(i64). 글자가 시작하지 않는 자리나 밖이면 `limit` | +| 105 | CHAR | q → t | 코드 포인트 하나의 text. 0–0x10FFFF 밖이면 `limit`, 서로게이트면 `encoding` | bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이트의 정수다. CONST의 인자 2는 i64 상수(값의 8바이트), NUM_TEXT의 인자 2는 i64의 10진 글자다. LOAD·STORE·지역 칸·항목·FIELD·SEND는 i64를 수처럼 옮긴다. FOLD의 종류에 64(PURE_FOLD_INTEGER)를 @@ -212,6 +228,13 @@ bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이 (읽기뿐: 응답과 문서 연산은 핸들러 자신의 코드에만 있다). - 표는 리스트 상태를 가리키고 열은 앞에 나온 표를 가리킨다. 표 밖의 이름은 한 이름공간이고, 열 이름은 표 안에서만 유일하다. +- 반복(LOOP, EACH)은 제 피연산자 말고는 빈 스택에서 연다. 반복의 블록은 그 실행의 스택 바닥에서 시작하므로, 아래에 값을 둔 반복은 + 스택의 64칸을 넘을 수 있었다(검증을 지난 모듈이 실행기를 죽였다. 2c에서 찾아 막았다). +- 함수(2c): 블록은 매개변수를 스택에 두고 시작하고, 모든 길이 RETURN으로 끝난다. RETURN은 자기 함수의 결과 타입을 맨 위에서 + 꺼낸다. 안의 반복 블록은 핸들러의 코드로 검증한다. CALL은 매개변수의 타입(리스트는 그 레코드까지)대로 꺼내고 결과를 놓는다. + 로드할 때 함수마다 그 코드가 이름을 대는 이벤트의 리스트, 레지스터, 담은 값과 지역 칸의 수, 반복의 깊이, 스택 높이를 적는다. + 또 이벤트의 리스트가 아닌 리스트 가운데 그 함수가(그것이 부르는 함수들이) 넣거나 지우는 것을 모은다. 둘레의 걷기가 도는 리스트를 + 바꾸는 부름은 거절한다(`call that changes a list walked`). 검증된 모듈은 실행 중 스택·타입 검사가 필요 없다. 이벤트 하나가 도는 명령 수는 실행량 예산이 묶는다: 앱이 정하지 않으면 2^28 명령이다(018: 세는 반복이 들어와 구조만으로는 묶이지 않는다). 집계와 걷기는 시작할 때 블록 길이 × 항목 수를, LOOP는 돌 때마다 @@ -221,6 +244,14 @@ bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이 ## 실행 +**부름**(027의 2c). 함수가 있는 모듈의 블록은 부름을 아는 실행기(`run_calls`)가 돈다. CALL은 앱의 프레임 블록(옮겨지지 않는다: +반복이 프레임 안을 가리킨다)에 프레임을 연다. 부른 쪽의 자리(CALL 다음, 코드의 끝, 반복, 스택, 지역 칸, 걷기)와, 함수가 이름을 +대는 부른 쪽의 리스트·레지스터·담은 값을 프레임에 두고, 함수의 것을 새로 준다. 이벤트의 리스트는 앞선 부름이 비워 둔 것을 다시 +쓴다. RETURN은 결과를 부른 쪽 스택에 놓고 부른 쪽의 것을 돌려준다. 부름 안에서 멈춘 실행은 열린 프레임을 모두 닫는다. 부른 쪽의 +담은 값을 가리키는 인자와 함수의 담은 값을 가리키는 결과는 그 값이 사라지기 전에 이벤트 공간으로 복사한다. 페이로드, 인자, +상수의 값은 이벤트 동안 그대로이므로 이름으로 담을 때 복사하지 않는다(덧붙일 때 처음 복사한다). 함수가 든 블록은 컴파일하지 +않는다(JIT은 부름이 없는 블록만). + `PureApp`은 모듈 참조, 상태 값, text 상태 draft, 리스트, 열린 이벤트 하나를 가진다(3,128 B, `PureModule`은 상수 블롭을 포함해 28,600 B). text 상태는 저장소 파일이 없는 draft라 1 MiB 텍스트도 조각 트리로 편집한다. 커서는 바이트 위치이며 항상 글자 경계에 있다. 넣기는 그 위치 이후의 커서를 뒤로(삽입 위치의 커서 포함, 마크는 제자리) 밀고, 지우기는 범위 -- cgit v1.2.3