aboutsummaryrefslogtreecommitdiffstats
path: root/docs/design
diff options
context:
space:
mode:
Diffstat (limited to 'docs/design')
-rw-r--r--docs/design/008-module-format.md35
1 files changed, 33 insertions, 2 deletions
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 텍스트도 조각 트리로 편집한다. 커서는 바이트
위치이며 항상 글자 경계에 있다. 넣기는 그 위치 이후의 커서를 뒤로(삽입 위치의 커서 포함, 마크는 제자리) 밀고, 지우기는 범위