19 호스트 인터페이스

(1) 호스트 인터페이스는 C 헤더 pinax.h 하나다. 그 헤더는 <stddef.h> 와 <stdint.h> 만 쓰고 C99, C23, C++ 컴파일러로 읽힌다. 이 절은 그 함수들이 무엇을 하는지 정한다. 함수마다 헤더의 주석이 더 자세하다.

(2) 한 상태는 한 번에 한 스레드만 쓴다. 상태 사이에는 나누는 것이 없다. 함수가 돌려주는 pinax_str 는 따로 적지 않으면 그 상태에 대한 다음 호출 전까지만 쓸 수 있다.

19.1 오류 번호

(1) 함수는 pinax_err 를 돌려준다. PINAX_OK 는 0 이다. 실패가 아닌 결과 셋이 있다: PINAX_SUSPENDED(예산이 다함, 19.5), PINAX_WAITING(모든 작업이 호스트를 기다림, 19.8), PINAX_PENDING(호스트 낱말이 나중에 끝남). 0x100 부터는 스크립트의 결함과 실패(11.1 의 번호와 같다), 0x200 부터는 인터페이스 자체의 실패다: ARGUMENT, NO_ROOM(덩이가 모자람), SYNTAX, TYPE(적재 거절), NOT_FOUND, SIGNATURE, FORMAT, STATE(지금 할 수 없는 호출).

(1a) 0x400 부터는 프로그램이 선언한 실패 코드다(8.14). 상태가 선언한 차례대로 번호가 붙는다. 그런 코드로 실패한 낱말은 그 번호를 주고, pinax_error_text 는 그 이름을 준다. 호스트는 이름을 읽는다. 코드의 번호는 그보다 앞에 적재된 것에 따라 달라진다.

(2) pinax_error_text 는 마지막 실패를 글로 준다. 결함이면 모듈:줄: 이름 꼴이다.

(3) 인터페이스의 실패는 어느 함수에서나 같은 뜻이다.

번호뜻
PINAX_ERR_ARGUMENT널 포인터, 없는 낱말 번호, 선언과 다른 크기처럼 인자가 틀렸다
PINAX_ERR_NO_ROOM덩이에 자리가 없다. pinax_needed 가 적어도 얼마나 커야 했는지 말한다(19.3)
PINAX_ERR_SYNTAX PINAX_ERR_TYPE소스가 거절되었다. 자리와 까닭은 diag 와 pinax_error_text 에
PINAX_ERR_NOT_FOUND그 이름의 낱말, 전역, 모듈, 호스트 낱말이 없다. 상에서 연 상태에 아직 안 준 호스트 낱말도
PINAX_ERR_SIGNATURE서명이 다르다: 찾는 낱말, 등록하는 호스트 낱말, 공유하는 전역의 선언
PINAX_ERR_FORMAT컨테이너, 상, 공유 파일이 이 처리기의 꼴이 아니거나 검사에 걸렸다
PINAX_ERR_STATE지금은 할 수 없는 호출: 멈춘 실행이 있다, 호스트 낱말 안이다, 코드가 없다, 상태가 얼었다

19.2 열기와 닫기

(1) pinax_open(block, config, &state) 은 덩이 block 안에 상태를 연다. config 의 0 인 칸은 처리기의 기본값이다: 뱅크의 칸 수, 주 실행의 구역 크기, 동시에 살 수 있는 핸들의 수, 실행 한 번의 명령 예산(0 이면 끝이 없다), 적재 진단을 받을 함수. 덩이가 모자라면 PINAX_ERR_NO_ROOM 이고, pinax_needed 가 적어도 얼마나 커야 했는지 알려 준다: 모자란 때에 잡으려던 것까지의 크기다. 적재는 그 뒤에 더 잡을 수 있으므로, 그 크기로 다시 열어 또 모자라면 다시 읽는다.

(2) config.hash_key 는 찾아보기(8.10)가 쓰는 해시의 열쇠 16 바이트다. 밖에서 온 글을 키로 찾는 호스트는 무작위 열쇠를 준다. 모두 0 이면 정해진 열쇠를 쓴다.

(3) pinax_close 는 아직 열린 핸들을 닫는다(그 타입의 닫는 함수를 부른다). 덩이는 놓지 않는다 — 덩이는 호스트의 것이다.

(4) 덩이는 세 몫으로 쓰인다. 열 때 상태의 고정된 부분이 잡힌다: 뱅크, 호출 틀, 주 실행의 구역, 호스트 낱말과 핸들의 표. 기본값으로는 약 150 KB 이고, 구역(64 KB)과 정수 뱅크(32 KB)가 그 대부분이므로 config 로 줄일 수 있다. 적재한 프로그램은 그 뒤에 쌓여 남는다. 적재하는 동안은 덩이의 남은 꼬리가 컴파일러의 작업 메모리다: 작은 소스에 약 20 KB, 낱말 하나에 약 1 KB 씩 더, 상수 패턴(16.7)이 있으면 64 KB 더 든다. 작업 메모리는 적재가 끝나면 다시 빈다. 한 몸체가 쓰는 작업 메모리는 그 몸체가 실제로 겹친 블록만큼이지 허용된 가장 깊은 겹침만큼이 아니다.

19.3 호스트 낱말

(1) pinax_register(state, 모듈, defs, count) 는 호스트 함수를 모듈 하나의 낱말로 등록한다. defs 의 칸마다 이름, 서명 글(9.2 의 꼴, 예: "(s str gives n int or fail)"), 함수, 사용자 포인터, 깃발이 있다. 서명은 등록할 때 한 번 검사한다. 그 낱말을 쓰는 소스보다 먼저 등록한다.

(2) 호스트 함수는 (call, user, args) 를 받는다. 인자와 결과는 타입별 배열에 있다: 정수(그리고 bool, 문자, 핸들, int 위의 선언한 타입)는 args->i[] 와 args->ri[], 문자열은 args->s[] 와 args->rs[]. 서명에 적힌 차례로, 타입마다 0 부터 센다. 정수 포인터는 상태의 칸을 바로 가리킨다. 서명의 좁은 정수(7.6)는 64 비트 칸 하나로 오가고, 함수가 돌아온 뒤 처리기가 결과의 범위를 검사한다: 밖이면 결함 overflow 다(기다리는 낱말의 결과는 pinax_complete 에서 같은 검사를 받아 그 번호로 끝난다). pinax_run 에 준 좁은 정수 인자가 범위 밖이면 PINAX_ERR_ARGUMENT 이고 아무것도 돌지 않는다.

(3) 함수가 PINAX_OK 가 아닌 값을 돌려주면, 서명에 or fail 이 있는 낱말은 그 번호로 실패하고(11.2) 없는 낱말은 결함으로 실행을 끝낸다.

(4) 문자열 결과는 PINAX_WORD_KEEP_STR 깃발이 없으면 부른 쪽의 구역으로 복사된다. 깃발이 있으면 복사하지 않고 그 바이트를 가리킨다. 그 바이트는 상태보다 오래 살아야 하고 바뀌지 않아야 한다(정적인 글, 프로그램의 인자 따위). 그런 결과의 수에는 한계가 없다. 새 바이트를 만들 자리는 pinax_call_alloc 이 부른 쪽의 구역에서 준다. 서명에서 buffer 타입으로 받은 매개변수는 스크립트의 버퍼다. pinax_call_buffer 로 그 버퍼의 빈 자리를 얻고, 채운 바이트 수를 알린다.

19-host-words.c

#include "pinax.h"
#include <stdio.h>
#include <string.h>

/* (s str gives r str): the result is written into the caller's region. */
static pinax_err shout(pinax_call *call, void *user, const pinax_args *args) {
    (void)user;
    pinax_mem room = pinax_call_alloc(call, args->s[0].len + 1);
    if (!room.ptr)
        return PINAX_ERR_FULL;                       /* no `or fail`: this is a fault */
    unsigned char *out = room.ptr;
    for (size_t k = 0; k < args->s[0].len; ++k) {
        unsigned char c = args->s[0].ptr[k];
        out[k] = c >= 'a' && c <= 'z' ? (unsigned char)(c - 32) : c;
    }
    out[args->s[0].len] = '!';
    args->rs[0] = (pinax_str){out, args->s[0].len + 1};
    return PINAX_OK;
}

/* (s str gives n int or fail): an outcome other than PINAX_OK is the word's failure. */
static pinax_err digits(pinax_call *call, void *user, const pinax_args *args) {
    (void)call; (void)user;
    int64_t n = 0;
    if (args->s[0].len == 0)
        return PINAX_ERR_FORMAT;
    for (size_t k = 0; k < args->s[0].len; ++k) {
        unsigned char c = args->s[0].ptr[k];
        if (c < '0' || c > '9')
            return PINAX_ERR_FORMAT;
        n = n * 10 + (c - '0');
    }
    args->ri[0] = n;
    return PINAX_OK;
}

/* (b buffer or fail): writes straight into a buffer the script declared. */
static pinax_err stamp(pinax_call *call, void *user, const pinax_args *args) {
    (void)user;
    pinax_mem room = pinax_call_buffer(call, args->i[0], 0);
    if (room.len < 5)
        return PINAX_ERR_FULL;
    memcpy(room.ptr, "[ok] ", 5);
    pinax_call_buffer(call, args->i[0], 5);
    return PINAX_OK;
}

int main(void) {
    static unsigned char block[1 << 20];
    pinax_config config = {.struct_size = sizeof config};
    pinax_state *st;
    if (pinax_open((pinax_mem){block, sizeof block}, &config, &st) != PINAX_OK)
        return 1;
    static const pinax_word_def words[] = {
        {"shout", "(s str gives r str)", shout, NULL, 0},
        {"digits", "(s str gives n int or fail)", digits, NULL, 0},
        {"stamp", "(b buffer or fail)", stamp, NULL, 0},
    };
    pinax_register(st, "app", words, 3);
    static const char source[] =
        "use app\n"
        "buffer line 32\n"
        "word demo (gives s str)\n"
        "  line clear\n"
        "  line app.stamp or_else (e) end\n"
        "  \"12x\" app.digits or_else (e) -1 end to n\n"
        "  cat (\"hello\" app.shout, \" \", \"42\" app.digits or_else (e) -1 end, \" \", n) line append or_else (e) end\n"
        "  line contents\n"
        "end\n";
    if (pinax_load(st, "main", (pinax_str){(const unsigned char *)source, sizeof source - 1}, PINAX_LOAD_CODE) != PINAX_OK)
        return 1;
    pinax_word w;
    pinax_str out;
    if (pinax_find(st, "demo", "(gives s str)", 0, &w) != PINAX_OK)
        return 1;
    pinax_run(st, w, &(pinax_args){.rs = &out});
    printf("%.*s\n", (int)out.len, (const char *)out.ptr);
    pinax_close(st);
    return 0;
}

출력

$ ./19-host-words
[ok] HELLO! 42 -1

19.4 적재와 한 줄

(1) pinax_load(state, 이름, 소스, 방식) 은 소스를 적재한다(5.2). 이름 은 모듈의 이름이다. 방식은 PINAX_LOAD_CODE 와 PINAX_LOAD_DATA(13.4)다. 거절하면 PINAX_ERR_SYNTAX 나 PINAX_ERR_TYPE 이고, 진단은 config.diag 에 (파일, 줄, 칸, 글)로 온다.

(1a) pinax_load_bytecode(state, 바이트) 는 처리기가 만든 바이트코드 컨테이너를 빈 상태에 적재한다. 컨테이너의 모양은 처리기가 정한다(1 (3)). 처리기는 컨테이너를 믿지 않고 검사한다: 4 (2a) 를 어길 수 있는 컨테이너는 PINAX_ERR_FORMAT 으로 거절한다. 이 구현이 거절하는 것에는 창 밖의 레지스터, 낱말 밖으로 가는 뜀, 쓰기 전에 읽는 레지스터, 예산을 치르지 않고 되돌아가는 뜀과 부름, 다른 결과를 주는 낱말로의 꼬리 부름, 실패하는 연산 뒤에 실패 낱말이 없거나 그 뒤에 아무것도 없는 것, 바꾸는 문자열 레지스터를 두 번 적은 명령이 있다.

(1b) 적재에는 예산이 없다. 그래서 처리기는 소스나 컨테이너를 그 길이에 비례하는 시간에 적재한다: 이름을 찾고, 어느 낱말이 양보하는지 정하고, 레지스터의 흐름을 검사하는 어느 단계도 길이의 제곱으로 늘지 않는다. 믿지 않는 소스를 받는 호스트는 그래서 적재 하나가 끝나지 않을 걱정 없이 소스의 길이만 제한하면 된다.

(1c) 이미 프로그램을 가진 상태에 적재하면 처리기는 새 소스가 더하는 것만 프로그램에 덧붙이고 그 새 낱말만 검사한다. 그래서 (1b)의 한계는 세션의 줄과 하나씩 적재하는 모듈에도 성립한다. 선언 표도 같다: 적재는 앞 적재들이 선언한 것을 그 자리에서 읽고 새 선언만 덧붙인다. 어느 낱말이 양보하는지 정하는 단계는 적재가 가져온 낱말만 돌고, 낱말을 redefine 하는 적재에서만 프로그램 전체를 돈다. 블록에서 프로그램이 차지하는 자리는 선언한 것에 비례한다: 덧붙일 자리가 모자라면 처리기가 그 부분을 더 큰 자리로 옮기고, 적재가 끝나면 버린 자리를 되찾는다. 다만 작업(14절)이 살아 있는 동안은 되찾지 않고, 그 작업이 끝난 뒤의 다음 적재가 되찾는다. redefine 은 새 몸을 덧붙이고 옛 코드를 그 자리에 둔다. 쓰인 자리는 pinax_stats 의 block_bytes_used 로 본다. 코드만 돌리는 세션 줄은 블록을 쓰지 않는다.

(1d) pinax_capacity(state, name, value) 는 host 로 쓴 용량(8 (4a))의 수를, 그것이 든 적재 전에 준다. name 은 그 선언의 이름이다. 처음 적재한 소스는 맨 이름, 어느 소스든 모듈.이름 으로 쓴다. 같은 이름을 다시 주면 그 수로 바뀐다. 상태는 47 바이트까지의 이름을 16 개까지 지닌다. 값은 1 부터 231 − 1 까지다. 용량은 코드의 상수가 될 수 있으므로(capacity, 적재기가 그것에 기대어 증명한 검사), 한 수로 만든 내보낸 코드(19.11)는 다른 수로 한 적재의 코드와 다를 수 있다. 그런 적재는 그것 없이 돈다.

19-capacity.c

#include "pinax.h"
#include <stdio.h>

/* The host knows at start how many requests it serves at once; the script leaves the number to it. */
int main(void) {
    static unsigned char block[1 << 20];
    pinax_config config = {.struct_size = sizeof config};
    pinax_state *st;
    if (pinax_open((pinax_mem){block, sizeof block}, &config, &st) != PINAX_OK)
        return 1;
    static const char source[] =
        "tasks handlers host 32 8192 apart\n"
        "list seen int host\n"
        "word room (gives n int) seen capacity end\n";
    const uint32_t at_once = 48;
    pinax_capacity(st, "handlers", at_once + 1); /* one more for the task that takes the requests */
    pinax_capacity(st, "seen", at_once);
    if (pinax_load(st, "main", (pinax_str){(const unsigned char *)source, sizeof source - 1}, PINAX_LOAD_CODE) != PINAX_OK) {
        pinax_str why = pinax_error_text(st);
        fprintf(stderr, "%.*s\n", (int)why.len, (const char *)why.ptr);
        return 1;
    }
    pinax_word w;
    int64_t n = 0;
    pinax_find(st, "room", "(gives n int)", 0, &w);
    pinax_run(st, w, &(pinax_args){.ri = &n});
    printf("seen holds %lld\n", (long long)n);
    pinax_close(st);
    return 0;
}

출력

$ ./19-capacity
seen holds 48

(2) pinax_eval(state, 글) 은 대화처럼 한 줄을 돌린다. 모든 줄은 한 모듈(세션)이다. 줄이 남긴 값은 pinax_eval_count 와 pinax_eval_value 로 읽는다.

(2a) 세션에는 줄에서 줄로 남는 스택이 있다. 줄은 앞 줄들이 남긴 것을 가지고 시작하고, 그 값들을 입력으로, 줄이 남기는 것을 결과로 하는 낱말로 검사된다. 세션 스택은 값을 16 개까지 담는다: int 와 그 위의 타입, bool, err, 255 바이트까지의 str, 소유하지 않은 핸들. 그보다 많이 남기거나 모음, 줄기, 소유한 값을 남기는 줄은 거절된다. 더 긴 str 을 남기는 줄은 돈 뒤에 PINAX_ERR_NO_ROOM 으로 끝난다. 거절되거나 결함으로 끝난 줄은 스택을 그 줄 앞의 모습대로 둔다. 예산이 다해 멈췄다가 버려진 줄도 그렇고, 끝까지 이어 돈 줄은 남긴 것을 남긴다. 세션 스택의 str 은 상태 안에 복사되어 다음 pinax_eval 이나 pinax_eval_clear 까지 유효하다. pinax_eval_clear(state) 는 스택을 비우고, 도구 낱말 clear 로 시작하는 줄도 그렇다. 적재와 실행은 스택을 그대로 둔다. 상과 복제는 스택을 담지 않는다.

(3) pinax_on_barrier(state, 함수, user) 는 점이 부르는 함수를 준다(10.8 (3)): 코드가 닿는 점마다, 그리고 적재한 코드와 세션 줄의 끝에서(결함으로 끝나도) 처리기는 함수(user) 를 부른다. 호스트는 그 안에서 호스트 낱말이 붙들고 있는 것을 내보내고 뒤에서 시작한 일을 기다린다. 그 안에서 상태를 부르지 않는다. 기본값 NULL 은 아무것도 끝내지 않는다. 복제는 함수를 지니고, 상은 지니지 않는다. 호스트 라이브러리의 pinax_host_register_all 은 io 가 붙들고 있는 출력을 내보내는 함수를 준다.

19.5 찾기와 돌리기

(1) pinax_find(state, 이름, 서명, 깃발, &word) 은 낱말을 찾고 그 서명이 주어진 서명과 같은지 본다. 다르면 PINAX_ERR_SIGNATURE 다. 맨 이름은 처음 적재한 소스의 낱말이고, 모듈.낱말 은 그 모듈의 낱말이다(내보내지 않았어도).

(2) pinax_run(state, word, args) 은 낱말을 돌린다. 예산이 다하면 PINAX_SUSPENDED 로 돌아오고, pinax_resume 이 멈춘 자리부터 잇는다. 예산은 결과를 바꾸지 않는다. 결함은 그 번호로 돌아오고, 상태는 다음 실행을 받는다.

(2a) 예산은 뒤로 가는 분기와 호출의 검사마다 그 비용을, 문자열 낱말이 훑은 바이트를, 그리고 run_all 이 작업에 주는 차례(14.1 (2))마다 하나를 센다. 그래서 처리기가 받는 덩이의 실행은, 작업이 제 루프 없이 생기고 끝나기를 되풀이하더라도, 모두 예산을 만난다.

(3) 멈춘 실행(PINAX_SUSPENDED, PINAX_WAITING)이 있는 동안 새 pinax_run 은 PINAX_ERR_STATE 다. 호스트는 그 실행을 끝까지 잇거나 pinax_abort 로 버린다.

(3a) pinax_abort(state) 는 멈춘 실행을 결함으로 끝난 것처럼 끝낸다: 그 실행이 연 핸들을 닫고, 작업을 비우고, 기다리던 표를 잊는다(그 표의 pinax_complete 는 PINAX_ERR_NOT_FOUND). 전역에 이미 넣은 값은 남는다. 멈춘 것이 적재 코드면 그 적재를 실패한 적재처럼 되돌린다. 그 뒤 상태는 다음 실행을 받는다. 멈춘 실행이 없으면 PINAX_ERR_STATE 다. 전역까지 실행 앞으로 되돌려야 하는 호스트는 실행 앞에 상(19.9)을 저장한다.

19-run.c

#include "pinax.h"
#include <stdio.h>
#include <string.h>

static void load(pinax_state *st, const char *text) {
    if (pinax_load(st, "main", (pinax_str){(const unsigned char *)text, strlen(text)}, PINAX_LOAD_CODE) != PINAX_OK) {
        pinax_str why = pinax_error_text(st);
        printf("refused: %.*s\n", (int)why.len, (const char *)why.ptr);
    }
}

int main(void) {
    static unsigned char block[1 << 20];
    pinax_config config = {.struct_size = sizeof config, .budget = 1000};   /* instructions per run or resume */
    pinax_state *st;
    if (pinax_open((pinax_mem){block, sizeof block}, &config, &st) != PINAX_OK)
        return 1;
    load(st,
         "word total (n int gives r int) 0 to s  1 n 1 add range do (i int) s i add to s end each  s end\n"
         "word ratio (a int, b int gives r int) a 100 mul b div end\n");

    pinax_word w;
    /* the signature asked for must be the word's */
    printf("find with another signature: %s\n",
           pinax_find(st, "total", "(gives r int)", 0, &w) == PINAX_ERR_SIGNATURE ? "PINAX_ERR_SIGNATURE" : "?");
    pinax_find(st, "total", "(n int gives r int)", 0, &w);
    int64_t n = 100000, r = 0;
    pinax_args args = {.i = &n, .ri = &r};
    int resumes = 0;
    pinax_err err = pinax_run(st, w, &args);
    while (err == PINAX_SUSPENDED) {                 /* the budget ran out: go on where it stopped */
        ++resumes;
        err = pinax_resume(st, &args);
    }
    printf("total(100000) = %lld after %s resumes\n", (long long)r, resumes > 10 ? "many" : "few");

    /* a fault comes back as the error code, with its place in the error text */
    pinax_find(st, "ratio", "(a int, b int gives r int)", 0, &w);
    int64_t ab[2] = {3, 0};
    err = pinax_run(st, w, &(pinax_args){.i = ab, .ri = &r});
    pinax_str why = pinax_error_text(st);
    printf("ratio(3, 0): %s, \"%.*s\"\n", err == PINAX_ERR_DIV_ZERO ? "PINAX_ERR_DIV_ZERO" : "?", (int)why.len, (const char *)why.ptr);

    /* the state is still usable */
    ab[1] = 4;
    err = pinax_run(st, w, &(pinax_args){.i = ab, .ri = &r});
    printf("ratio(3, 4) = %lld\n", (long long)r);

    /* one line at a time, as a session does */
    const char *line = "6 7 mul \"done\"";
    if (pinax_eval(st, (pinax_str){(const unsigned char *)line, strlen(line)}) == PINAX_OK) {
        for (uint32_t k = 0; k < pinax_eval_count(st); ++k) {
            pinax_value v;
            pinax_eval_value(st, k, &v);
            if (v.kind == PINAX_VALUE_INT)
                printf("value %u: %lld (%.*s)\n", k, (long long)v.i, (int)v.type.len, (const char *)v.type.ptr);
            else
                printf("value %u: %.*s (%.*s)\n", k, (int)v.s.len, (const char *)v.s.ptr, (int)v.type.len, (const char *)v.type.ptr);
        }
    }
    pinax_close(st);
    return 0;
}

출력

$ ./19-run
find with another signature: PINAX_ERR_SIGNATURE
total(100000) = 5000050000 after many resumes
ratio(3, 0): PINAX_ERR_DIV_ZERO, "main:2: div_zero"
ratio(3, 4) = 75
value 0: 42 (int)
value 1: done (str)

(4) 호스트 낱말 안에서도 pinax_run 을 부를 수 있다. 그때는 PINAX_FIND_NESTED 깃발로 찾은 낱말만 돌리며, 양보할 수 있는 낱말(14.2)은 그 깃발로 찾지 못한다.

(5) pinax_word_effects(state, word, &effects) 는 낱말이 스택과 지역 변수 밖에서 할 수 있는 일을, 그 낱말과 그것이 부를 수 있는 모든 낱말의 적재된 코드에서 알아내 알린다. effects.effects 에는 PINAX_EFFECT_READS_GLOBALS 와 PINAX_EFFECT_WRITES_GLOBALS(목록의 길이, 큐, 작업 집합도 전역으로 친다), PINAX_EFFECT_READS_OUTSIDE 와 PINAX_EFFECT_WRITES_OUTSIDE(호스트 낱말을 통해), PINAX_EFFECT_YIELDS, PINAX_EFFECT_FAILS, PINAX_EFFECT_ANYTHING (extern 의 C 함수)이 담긴다. module 과 module_effects 는 닿는 호스트 모듈을 이름 붙인다(n_modules 중 처음 16 개). 호스트 낱말은 등록할 때 PINAX_WORD_READS 와 PINAX_WORD_WRITES 깃발로 무엇에 닿는지 알린다. 둘 다 없으면 둘 다로, PINAX_WORD_PURE 이면 어느 것도 아닌 것으로 친다. 낱말 값을 통한 호출은 그 서명을 가진 프로그램의 모든 낱말을, 띄운 작업은 매개변수도 결과도 없는 모든 낱말을 셈에 넣는다. 호스트는 닿으면 안 되는 것에 닿는 프로그램을 거절할 수 있다.

19.6 핸들

(1) pinax_handle_type 은 핸들 타입 하나를 이름과 닫는 함수로 만든다. 스크립트는 그 이름을 서명의 타입으로 쓴다. pinax_handle_new 는 C 포인터를 핸들로 만들고, pinax_handle_get 은 핸들의 포인터를 준다(돌려준 핸들이면 NULL). pinax_handle_release 는 핸들을 돌려준다.

(2) 핸들은 번호와 세대다(8.9 와 같다). 스크립트는 핸들로 주소를 얻지 못한다. 결함으로 끝난 실행이 연 핸들은 상태가 닫는 함수를 불러 닫는다.

19-handles.c

#include "pinax.h"
#include <stdio.h>

typedef struct { int value; int open; } counter;
static counter pool_[4];
static uint32_t counter_type;

static void close_counter(void *object, void *user) {   /* called for handles the script never closed */
    (void)user;
    ((counter *)object)->open = 0;
    printf("closed by the state: counter at %d\n", ((counter *)object)->value);
}
static pinax_err c_new(pinax_call *call, void *user, const pinax_args *args) {
    (void)user;
    for (int k = 0; k < 4; ++k)
        if (!pool_[k].open) {
            pool_[k] = (counter){0, 1};
            pinax_handle h;
            pinax_err err = pinax_handle_new(pinax_call_state(call), counter_type, &pool_[k], &h);
            args->ri[0] = (int64_t)h;
            return err;
        }
    return PINAX_ERR_FULL;
}
static pinax_err c_bump(pinax_call *call, void *user, const pinax_args *args) {
    (void)user;
    counter *c = pinax_handle_get(pinax_call_state(call), counter_type, (pinax_handle)args->i[0]);
    if (!c)
        return PINAX_ERR_STALE;
    args->ri[0] = ++c->value;
    return PINAX_OK;
}
static pinax_err c_free(pinax_call *call, void *user, const pinax_args *args) {
    (void)user;
    counter *c = pinax_handle_get(pinax_call_state(call), counter_type, (pinax_handle)args->i[0]);
    if (c) {
        printf("freed by the script: counter at %d\n", c->value);
        c->open = 0;
    }
    return pinax_handle_release(pinax_call_state(call), counter_type, (pinax_handle)args->i[0]);
}

int main(void) {
    static unsigned char block[1 << 20];
    pinax_config config = {.struct_size = sizeof config};
    pinax_state *st;
    if (pinax_open((pinax_mem){block, sizeof block}, &config, &st) != PINAX_OK)
        return 1;
    pinax_handle_type(st, "counter", close_counter, NULL, &counter_type);
    static const pinax_word_def words[] = {
        {"new", "(gives owned counter or fail)", c_new, NULL, 0},
        {"bump", "(c counter gives n int)", c_bump, NULL, 0},
        {"free", "(c owned counter)", c_free, NULL, 0},
    };
    pinax_register(st, "ctr", words, 3);
    static const char source[] =
        "use ctr\n"
        "word twice (gives n int or fail)\n"
        "  ctr.new or_fail to c\n"
        "  c ctr.bump drop  c ctr.bump to n\n"
        "  c ctr.free\n"
        "  n\n"
        "end\n"
        "word broken (gives n int or fail)\n"
        "  ctr.new or_fail to c\n"
        "  c ctr.bump 0 div to n\n"            /* a fault while the counter is open */
        "  c ctr.free\n"
        "  n\n"
        "end\n";
    if (pinax_load(st, "main", (pinax_str){(const unsigned char *)source, sizeof source - 1}, PINAX_LOAD_CODE) != PINAX_OK)
        return 1;
    pinax_word w;
    int64_t n = 0;
    pinax_find(st, "twice", "(gives n int or fail)", 0, &w);
    pinax_run(st, w, &(pinax_args){.ri = &n});
    printf("twice = %lld\n", (long long)n);
    pinax_find(st, "broken", "(gives n int or fail)", 0, &w);
    pinax_err err = pinax_run(st, w, &(pinax_args){.ri = &n});
    printf("broken: %s\n", err == PINAX_ERR_DIV_ZERO ? "PINAX_ERR_DIV_ZERO" : "?");
    pinax_close(st);
    return 0;
}

출력

$ ./19-handles
freed by the script: counter at 2
twice = 2
closed by the state: counter at 1
broken: PINAX_ERR_DIV_ZERO

19.7 묶기

(1) pinax_bind(state, 전역, 메모리, 바이트) 는 bound 로 선언한 전역(8.12)의 저장소를 호스트 메모리로 바꾼다. 크기는 선언과 같아야 한다: 배열은 칸 수 곱하기 칸의 바이트(좁은 정수는 폭, 다른 값은 8; 8.4). 스크립트와 호스트는 복사 없이 같은 바이트를 읽고 쓴다. pinax_global_find 는 선언한 전역의 자리와 크기를 알려 준다.

19-bind.c

#include "pinax.h"
#include <stdio.h>

int main(void) {
    static unsigned char block[1 << 20];
    pinax_config config = {.struct_size = sizeof config};
    pinax_state *st;
    if (pinax_open((pinax_mem){block, sizeof block}, &config, &st) != PINAX_OK)
        return 1;
    static const char source[] =
        "bound array samples int 5\n"
        "bound global scale int\n"
        "word scaled_sum (gives n int) samples sum scale mul end\n"
        "word clear_first () 0 0 set samples end\n";
    if (pinax_load(st, "main", (pinax_str){(const unsigned char *)source, sizeof source - 1}, PINAX_LOAD_CODE) != PINAX_OK)
        return 1;
    static int64_t samples[5] = {1, 2, 3, 4, 5};    /* host memory the script reads and writes in place */
    static int64_t scale = 10;
    pinax_bind(st, "samples", samples, sizeof samples);
    pinax_bind(st, "scale", &scale, sizeof scale);
    pinax_word w;
    int64_t n = 0;
    pinax_find(st, "scaled_sum", "(gives n int)", 0, &w);
    pinax_run(st, w, &(pinax_args){.ri = &n});
    printf("scaled_sum = %lld\n", (long long)n);
    samples[4] = 105;                                /* the host changes it; no copy to refresh */
    pinax_run(st, w, &(pinax_args){.ri = &n});
    printf("after the host wrote samples[4]: %lld\n", (long long)n);
    pinax_find(st, "clear_first", "()", 0, &w);
    pinax_run(st, w, NULL);
    printf("after the script wrote cell 0: samples[0] = %lld\n", (long long)samples[0]);
    pinax_close(st);
    return 0;
}

출력

$ ./19-bind
scaled_sum = 150
after the host wrote samples[4]: 1150
after the script wrote cell 0: samples[0] = 0

(1a) pinax_share(state, global, source, from) 은 state 에서 shared 로 선언한 전역(8.13)에 source 의 전역 from 의 저장소를 준다. 복사하지 않는다. 종류, 타입, 용량이 같아야 하며, 아니면 PINAX_ERR_SIGNATURE 다. 찾아보기는 해시 열쇠(19.2)도 같아야 하고, 그 표를 같은 source 에서 먼저 받아야 한다. 목록은 그때의 길이를 쓴다. 표를 다시 받으면 그 표의 찾아보기도 다시 준다.

(1b) 저장소를 빌려준 source 는 얼어붙는다: 그 뒤의 pinax_run, pinax_load, pinax_eval, pinax_bind 는 PINAX_ERR_STATE 다. 그래서 바이트가 바뀌지 않고, 여러 스레드의 상태가 한꺼번에 읽어도 된다. source 는 빌린 상태들이 쓰는 동안 열려 있어야 한다. 공유 전역을 다 받기 전에는 실행이 PINAX_ERR_NOT_FOUND 로 거절된다. 상을 열거나 복제한 뒤에는 다시 준다(19.9).

19-share.c

#include "pinax.h"
#include <stdio.h>
#include <string.h>

/* One state holds the data; two others read it in place. Nothing is copied. */
static const char data[] =
    "record city\n  name str(12)\n  people int\nend\n"
    "list cities city 8\n"
    "index by_name cities city.name\n"
    "row cities (\"Seoul\", 9411)\n"
    "row cities (\"Busan\", 3317)\n"
    "row cities (\"Incheon\", 3001)\n";
static const char reader[] =
    "record city\n  name str(12)\n  people int\nend\n"
    "shared list cities city 8\n"
    "shared index by_name cities city.name\n"
    "word people_of (name str gives n int or fail) name by_name lookup or_fail city.people end\n"
    "word total (gives n int)\n"
    "  0 to t  0 to i\n"
    "  loop i cities len lt while  t i cities idx city.people add to t  i 1 add to i end\n"
    "  t\n"
    "end\n";

static pinax_state *open_in(unsigned char *block, size_t bytes) {
    pinax_config config = {.struct_size = sizeof config};
    pinax_state *st;
    return pinax_open((pinax_mem){block, bytes}, &config, &st) == PINAX_OK ? st : NULL;
}

static int64_t call(pinax_state *st, const char *word, const char *signature, const char *name, pinax_err *err) {
    pinax_word w;
    int64_t out = 0;
    pinax_str in = {(const unsigned char *)name, name ? strlen(name) : 0};
    pinax_args args = {.s = &in, .ri = &out};
    *err = pinax_find(st, word, signature, 0, &w);
    if (*err == PINAX_OK)
        *err = pinax_run(st, w, &args);
    return out;
}

int main(void) {
    static unsigned char blocks[3][1024 * 1024];
    pinax_state *source = open_in(blocks[0], sizeof blocks[0]);
    pinax_state *a = open_in(blocks[1], sizeof blocks[1]), *b = open_in(blocks[2], sizeof blocks[2]);
    if (!source || !a || !b || pinax_load(source, "data", (pinax_str){(const unsigned char *)data, strlen(data)},
                                          PINAX_LOAD_DATA) != PINAX_OK)
        return 1;
    pinax_err err;
    pinax_state *readers[2] = {a, b};
    for (int k = 0; k < 2; ++k) {
        if (pinax_load(readers[k], "main", (pinax_str){(const unsigned char *)reader, strlen(reader)},
                       PINAX_LOAD_CODE) != PINAX_OK)
            return 1;
    }
    call(a, "total", "(gives n int)", NULL, &err);
    printf("before sharing: %#x (not found)\n", (unsigned)err);

    for (int k = 0; k < 2; ++k) /* the table first, then its index */
        if (pinax_share(readers[k], "cities", source, "cities") != PINAX_OK ||
            pinax_share(readers[k], "by_name", source, "by_name") != PINAX_OK)
            return 1;
    printf("a: Busan has %lld\n", (long long)call(a, "people_of", "(name str gives n int or fail)", "Busan", &err));
    printf("b: total %lld\n", (long long)call(b, "total", "(gives n int)", NULL, &err));

    pinax_global mine, theirs;
    pinax_global_find(source, "cities", &theirs);
    pinax_global_find(b, "cities", &mine);
    printf("same bytes: %s\n", mine.ptr == theirs.ptr ? "yes" : "no");
    err = pinax_load(source, "more", (pinax_str){(const unsigned char *)"global x int\n", 13}, PINAX_LOAD_CODE);
    printf("the source loads again: %#x (state)\n", (unsigned)err);
    pinax_close(a);
    pinax_close(b);
    pinax_close(source);
    return 0;
}

출력

$ ./19-share
before sharing: 0x204 (not found)
a: Busan has 3317
b: total 15729
same bytes: yes
the source loads again: 0x207 (state)

(1c) 여러 프로세스는 파일로 공유한다. pinax_share_save(source, out, &written) 은 source 의 공유할 수 있는 전역(스칼라, 배열, 목록, 버퍼, 찾아보기; 스스로 shared 로 선언한 것은 빼고)을 주소 없는 파일 하나로 쓴다. 길이는 pinax_share_size 다. 저장은 복사이므로 source 는 얼지 않는다. 호스트 라이브러리의 pinax_map_file(path, &bytes,
&map)
은 파일을 읽기 전용으로, 같은 파일을 사상한 프로세스들이 같은 쪽을 쓰도록 사상한다. pinax_share_file(state,
global, bytes, from)
은 (1a) 와 같은 검사로 파일의 전역 from 을 준다. 찾아보기의 해시 열쇠는 파일의 것과 같아야 하고, 그 표는 같은 파일에서 먼저 받아야 한다. 파일의 머리, 전역마다의 크기와 길이, 찾아보기의 칸은 검사하며 맞지 않으면 PINAX_ERR_FORMAT 이다. 그 밖의 바이트는 상(19.9)처럼 믿는다. 다만 str(N) 칸의 길이 바이트가 N 을 넘어도 N 바이트보다 많이 읽지 않는다. bytes 는 16바이트 경계에서 시작하고, 그것을 읽는 상태들보다 오래 있어야 한다. pinax_unmap_file 은 그 상태들을 닫은 뒤에 부른다.

19-share-file.c

#include "pinax.h"
#include "pinax_host.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* One program writes the data as a share file; any number of processes map it and read it in
   place. Here the same program maps it twice, as two processes would. */
static const char data[] =
    "record city\n  name str(12)\n  people int\nend\n"
    "list cities city 8\n"
    "index by_name cities city.name\n"
    "row cities (\"Seoul\", 9411)\n"
    "row cities (\"Busan\", 3317)\n";
static const char reader[] =
    "record city\n  name str(12)\n  people int\nend\n"
    "shared list cities city 8\n"
    "shared index by_name cities city.name\n"
    "word people_of (name str gives n int or fail) name by_name lookup or_fail city.people end\n";

static unsigned char blocks[3][1024 * 1024];

static int write_file(const char *path) {
    pinax_config config = {.struct_size = sizeof config};
    pinax_state *source;
    if (pinax_open((pinax_mem){blocks[0], sizeof blocks[0]}, &config, &source) != PINAX_OK ||
        pinax_load(source, "data", (pinax_str){(const unsigned char *)data, strlen(data)}, PINAX_LOAD_DATA) != PINAX_OK)
        return 1;
    size_t size = pinax_share_size(source), written = 0;
    unsigned char *bytes = malloc(size);
    if (!bytes || pinax_share_save(source, (pinax_mem){bytes, size}, &written) != PINAX_OK)
        return 1;
    FILE *f = fopen(path, "wb");
    int bad = !f || fwrite(bytes, 1, written, f) != written;
    bad |= f && fclose(f) != 0;
    printf("wrote %zu bytes\n", written);
    free(bytes);
    pinax_close(source); /* the file needs nothing of it */
    return bad;
}

static void read_file(const char *path, unsigned char *block, size_t bytes, const char *name) {
    pinax_config config = {.struct_size = sizeof config};
    pinax_state *st;
    pinax_str file;
    pinax_mapping *map;
    pinax_word w;
    int64_t out = 0;
    pinax_str in = {(const unsigned char *)name, strlen(name)};
    pinax_args args = {.s = &in, .ri = &out};
    if (pinax_map_file(path, &file, &map) != PINAX_OK)
        return;
    if (pinax_open((pinax_mem){block, bytes}, &config, &st) == PINAX_OK &&
        pinax_load(st, "main", (pinax_str){(const unsigned char *)reader, strlen(reader)}, PINAX_LOAD_CODE) == PINAX_OK &&
        pinax_share_file(st, "cities", file, "cities") == PINAX_OK &&      /* the table first */
        pinax_share_file(st, "by_name", file, "by_name") == PINAX_OK &&    /* then its index */
        pinax_find(st, "people_of", "(name str gives n int or fail)", 0, &w) == PINAX_OK &&
        pinax_run(st, w, &args) == PINAX_OK)
        printf("%s has %lld\n", name, (long long)out);
    pinax_close(st);
    pinax_unmap_file(map); /* after the state that read it */
}

int main(void) {
    const char *path = "cities.pnxs";
    if (write_file(path) != 0)
        return 1;
    read_file(path, blocks[1], sizeof blocks[1], "Seoul");
    read_file(path, blocks[2], sizeof blocks[2], "Busan");
    remove(path);
    return 0;
}

출력

$ ./19-share-file
wrote 528 bytes
Seoul has 9411
Busan has 3317

(1d) 실행이 끝나지 않은 동안 — 예산이 다 되어 멈췄거나, 호스트 작업을 기다리거나, 호스트 낱말이 도는 동안 — pinax_bind, pinax_share, pinax_share_file 은 PINAX_ERR_STATE 다. 그 실행이 든 뷰가 바꾸기 전의 저장소를 가리킬 수 있기 때문이다.

(2) pinax_global_find 가 주는 number 는 모음 매개변수(9.9)가 있는 낱말이 그 인자로 받는 것이다. i 에 넣는 정수다. 종류나 원소 크기가 다른 모음, 또는 없는 전역의 번호는 낱말이 그것을 쓰는 자리에서 결함 PINAX_ERR_STATE 다.

19.8 나중에 끝나는 호스트 낱말

(1) PINAX_WORD_AWAITABLE 로 등록한 호스트 낱말은 일을 시작하고 pinax_call_pending 으로 표를 받은 뒤 PINAX_PENDING 을 돌려줄 수 있다. 부른 작업은 기다리고 다른 작업이 돈다. 돌 것이 없으면 pinax_run 이나 pinax_resume 이 PINAX_WAITING 을 돌려준다. 호스트는 pinax_complete(state, 표, 결과, 값) 로 일을 끝내고 pinax_resume 을 부른다.

19-await.c

#include "pinax.h"
#include <stdio.h>

/* A host word that finishes later: it starts the work, keeps a token and returns PINAX_PENDING. */
static pinax_token waiting[4];
static int64_t asked[4];
static int n_waiting;
static pinax_err fetch(pinax_call *call, void *user, const pinax_args *args) {
    (void)user;
    asked[n_waiting] = args->i[0];
    waiting[n_waiting++] = pinax_call_pending(call);
    return PINAX_PENDING;
}

int main(void) {
    static unsigned char block[1 << 20];
    pinax_config config = {.struct_size = sizeof config};
    pinax_state *st;
    if (pinax_open((pinax_mem){block, sizeof block}, &config, &st) != PINAX_OK)
        return 1;
    static const pinax_word_def words[] = {{"fetch", "(n int gives r int or fail)", fetch, NULL, PINAX_WORD_AWAITABLE}};
    pinax_register(st, "net", words, 1);
    static const char source[] =
        "use net\n"
        "tasks workers 2\n"
        "list got int 4\n"
        "word both (gives r int)\n"
        "  do 1 net.fetch or_else (e) -1 end got push or_else (e) end end workers spawn or_else (e) 0 return end drop\n"
        "  do 2 net.fetch or_else (e) -1 end got push or_else (e) end end workers spawn or_else (e) 0 return end drop\n"
        "  workers run_all\n"
        "  got sum\n"
        "end\n";
    if (pinax_load(st, "main", (pinax_str){(const unsigned char *)source, sizeof source - 1}, PINAX_LOAD_CODE) != PINAX_OK)
        return 1;
    pinax_word w;
    int64_t r = 0;
    pinax_args args = {.ri = &r};
    pinax_find(st, "both", "(gives r int)", 0, &w);
    pinax_err err = pinax_run(st, w, &args);
    printf("run: %s, %d operations started\n", err == PINAX_WAITING ? "PINAX_WAITING" : "?", n_waiting);
    /* the host finishes them in its own time, here the second first */
    for (int k = n_waiting - 1; k >= 0; --k) {
        int64_t result = asked[k] * 100;
        pinax_complete(st, waiting[k], PINAX_OK, &(pinax_args){.ri = &result});
    }
    err = pinax_resume(st, &args);
    printf("resume: %s, both = %lld\n", err == PINAX_OK ? "PINAX_OK" : "?", (long long)r);
    pinax_close(st);
    return 0;
}

출력

$ ./19-await
run: PINAX_WAITING, 2 operations started
resume: PINAX_OK, both = 300

(2) 호스트 루프(17.4)가 돌리는 상태에서는 호스트 낱말이 pinax_call_pending 대신 pinax_loop_pending 으로 표를 받고, 어느 스레드에서든 pinax_loop_complete 로 일을 끝낸다. 루프는 그 상태의 스레드에서 pinax_complete 를 부르고 실행을 잇는다.

19.9 상과 복제

(1) 상(image)은 상태 전체다: 코드, 선언, 전역, 칸, 구역, 멈춘 실행과 작업. pinax_image_save 가 쓰고 pinax_image_open 이 연다. 상이 덩이의 처음에 놓여 있으면 복사 없이 그 자리에서 연다. 다른 형식이나 다른 빌드의 메모리 배치로 만든 상은 PINAX_ERR_FORMAT 이다.

(1a) 상은 호스트가 보증하는 자료다(4 (2a)). 처리기는 머리와 형식, 배치, 그리고 안의 주소가 저장했던 덩이 안에 있는지를 검사하지만, 바이트를 하나하나 검사하지는 않는다. 이 호스트나 믿을 수 있는 호스트가 저장한 상만 연다. 덩이 밖을 가리키던 글(호스트의 글)과 마지막 패턴 맞음은 상을 건너지 않는다.

(2) 연 상태에는 호스트 낱말, 핸들 타입, extern 함수를 이름으로 다시 준다. 다 주기 전에는 실행이 PINAX_ERR_NOT_FOUND 로 거절된다. 핸들은 닫힌 채(닫는 함수 없이), 묶은 전역은 자기 저장소로 돌아와 있다. pinax_clone 은 한 번에 저장하고 다른 덩이에서 연다.

(2a) 복제는 pinax_image_size(source) 바이트 이상의 덩이에서 열린다. 더 작은 덩이는 PINAX_ERR_NO_ROOM 이다.

(2b) pinax_clone_with(source, block, flags, out) 는 깃발을 받는 pinax_clone 이다. PINAX_CLONE_KEEP_HOSTS 를 주면 복제는 이 프로세스에서 원본이 받은 것을 같은 user 포인터와 함께 지킨다. 호스트 낱말, 핸들 타입, extern 함수와 해결 함수, 내보낸 코드(19.11), 작업 결함 함수(19.10 (3a))다. 그래서 바로 돈다. 묶인 전역은 다시 묶고 핸들은 넘어가지 않는 것은 다른 복제와 같다. 모르는 깃발은 PINAX_ERR_ARGUMENT 다.

19-image.c

#include "pinax.h"
#include <stdio.h>
#include <stdlib.h>

static pinax_err twice(pinax_call *call, void *user, const pinax_args *args) {
    (void)call; (void)user;
    if (args->i[0] > INT64_MAX / 2 || args->i[0] < INT64_MIN / 2)
        return PINAX_ERR_OVERFLOW;
    args->ri[0] = args->i[0] * 2;
    return PINAX_OK;
}
static const pinax_word_def words[] = {{"twice", "(n int gives r int)", twice, NULL, 0}};

static int64_t call_next(pinax_state *st) {
    pinax_word w;
    int64_t r = 0;
    if (pinax_find(st, "next", "(gives r int)", 0, &w) != PINAX_OK)
        return -1;
    pinax_err err = pinax_run(st, w, &(pinax_args){.ri = &r});
    return err == PINAX_OK ? r : -(int64_t)err;
}

int main(void) {
    static _Alignas(16) unsigned char a[1 << 20], b[1 << 20], c[1 << 20];
    pinax_config config = {.struct_size = sizeof config};
    pinax_state *st;
    if (pinax_open((pinax_mem){a, sizeof a}, &config, &st) != PINAX_OK)
        return 1;
    pinax_register(st, "host", words, 1);
    static const char source[] =
        "use host\n"
        "global calls int\n"
        "word next (gives r int) calls 1 add set calls  calls host.twice end\n";
    if (pinax_load(st, "main", (pinax_str){(const unsigned char *)source, sizeof source - 1}, PINAX_LOAD_CODE) != PINAX_OK)
        return 1;
    call_next(st);
    printf("in the first state: %lld\n", (long long)call_next(st));

    /* save the whole state: code, globals, everything */
    size_t size = pinax_image_size(st), written = 0;
    unsigned char *image = malloc(size);
    pinax_image_save(st, (pinax_mem){image, size}, &written);

    /* open it in another block; host words are given again by name */
    pinax_state *copy;
    if (pinax_image_open((pinax_mem){b, sizeof b}, (pinax_str){image, written}, &config, &copy) != PINAX_OK)
        return 1;
    printf("before the host word is given again: %s\n", call_next(copy) == -PINAX_ERR_NOT_FOUND ? "PINAX_ERR_NOT_FOUND" : "?");
    pinax_register(copy, "host", words, 1);
    printf("the copy goes on: %lld\n", (long long)call_next(copy));
    printf("the first state is its own: %lld\n", (long long)call_next(st));

    /* a clone is the same in one call */
    pinax_state *twin;
    if (pinax_clone(copy, (pinax_mem){c, sizeof c}, &twin) != PINAX_OK)
        return 1;
    pinax_register(twin, "host", words, 1);
    printf("the clone of the copy: %lld\n", (long long)call_next(twin));
    free(image);
    pinax_close(twin);
    pinax_close(copy);
    pinax_close(st);
    return 0;
}

출력

$ ./19-image
in the first state: 4
before the host word is given again: PINAX_ERR_NOT_FOUND
the copy goes on: 6
the first state is its own: 6
the clone of the copy: 8

19.10 재기와 결함의 지역 변수

(1) pinax_stats_get 은 칸, 구역, 핸들, 호출 깊이의 가장 높았던 값과 돈 명령의 수를 준다. 덩이와 용량을 정할 때 쓴다.

(2) pinax_fault_local 은 결함으로 끝난 실행에서 결함이 난 낱말의 지역 변수를 그때의 값으로 준다(16 개까지, 글은 처음 32 바이트이고, 상태가 보증할 수 있는 메모리에 있을 때만 보인다). pnx 가 결함 아래에 보이는 것이 이것이다(11.3).

(3) pinax_task_faults 는 상태를 연 뒤로 apart 묶음(14.4 (3a))의 작업 몇 개가 결함으로 끝났는지와, 마지막 것이 어디서 왜 났는지를 결함의 자리 꼴(module:line: error)로 준다. 그 결함이 난 실행 뒤에는 pinax_fault_local 이 다음 실행이 끝날 때까지 그 작업의 지역 변수를 읽는다.

(3a) pinax_on_task_fault(state, fn, user) 를 부르면 상태는 그런 결함마다 한 번, 결함이 난 그때, 그 작업의 자리가 다시 쓰이기 전에 fn(user, state, fault) 를 부른다. fault 는 결함, 작업의 꼬리표(14.5), 같은 꼴의 자리를 준다. 한 실행에서 여러 작업이 결함을 내면 하나씩 알린다. fn 안에서 pinax_fault_local 은 그 작업의 지역 변수를 읽는다. 실행이 한창이므로 fn 은 상태를 읽는 함수만 부를 수 있다: pinax_fault_local, pinax_task_faults, pinax_error_text. NULL 은 그 함수를 뗀다. 상과 복제는 호스트 낱말을 잊듯 이것도 잊는다(19.9). 복제가 그것을 지키면 예외다(19.9 (2b)).

19.11 내보낸 코드

(1) pnx export-c(20 (3))는 프로그램의 코드를 C 로 쓴다. 호스트는 그 파일을 같은 빌드의 pinax_native.h 와 함께 컴파일해 pinax_native 표 하나를 얻는다. 표는 내보낼 때의 프로그램, 곧 그 코드와 낱말을 덮고, 그 양을 code_words 와 words 로 센다. pinax_native_attach(state, table) 은 상태의 프로그램이 내보낸 그것으로 시작하는 첫 실행부터 그 코드를 표로 돌리게 한다. 같은 코드, 같은 낱말 시작, 같은 빌드여야 한다. 적재 전에 주면 적재 자신의 코드도 그것으로 돈다. NULL 은 표를 거둔다.

(1a) 그 뒤에 적재한 것(세션의 줄, 나중에 적재한 모듈)은 해석으로 돌고, 표는 계속 쓰인다. 표가 덮는 코드로 들어가는 호출, 반환, 작업 전환은 거기서 다시 표로 돈다. 표가 덮는 낱말 하나를 같은 서명으로 다시 정의하면(20.4 (2a)) 표가 덮는 프로그램이 바뀌므로, 그때부터 표를 쓰지 않는다. 다른 서명으로 다시 정의하면 표가 덮는 코드는 그대로이고 표도 계속 쓰인다.

(2) 프로그램과 호스트가 보는 것은 해석할 때와 같다. 결과, 자리와 지역 변수가 있는 결함, 실패, 예산(실행이 같은 CHECK 에서 멈춘다), 기다림과 작업이 같다. 내보낸 코드는 적재기가 울릴 수 없다고 증명한 검사(20 (1b))를 하지 않는다. PINAX_CHECK_PROOFS 로 지은 빌드는 그 검사를 하고, 울리면 프로그램을 멈춘다.

(3) 상은 표를 지니지 않는다. 다시 붙인다. 복제도 그렇다. PINAX_CLONE_KEEP_HOSTS 로 만든 복제만 표를 지닌다(19.9 (2b)). 실행이 멈추거나 기다리는 동안 붙이면 PINAX_ERR_STATE 다. struct_size 가 다른 표, code_words 와 words 가운데 하나만 0 인 표는 PINAX_ERR_ARGUMENT 다. 둘 다 0 이면 길이가 어떻든 프로그램 전체의 표다.

(4) 표는 상처럼 호스트가 보증하는 자료다(4 (2a)). 그 해시만 검사하고 기계어는 검사하지 않는다. 내보낸 코드에서 4 (2a)의 보장은 적재기의 증명이 옳은 만큼 성립한다.