aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-18 16:05:34 +0100
committereunhhu <[email protected]>2026-10-18 16:05:34 +0100
commita9adaf2a1a62e5dd37f0067653b35e9cfa2c1860 (patch)
tree9a95a7611334ec3ba9d25c9a0c3c0b63cdcb3093
parentc788046717f466c6681376207f368d52854cfdee (diff)
downloadpure-a9adaf2a1a62e5dd37f0067653b35e9cfa2c1860.tar.gz
pure-a9adaf2a1a62e5dd37f0067653b35e9cfa2c1860.zip
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)
-rw-r--r--core/module-jit.c4
-rw-r--r--core/module-run.h2
-rw-r--r--core/module.c507
-rw-r--r--core/module.h50
-rw-r--r--core/test-module.c188
-rw-r--r--docs/design/008-module-format.md35
6 files changed, 740 insertions, 46 deletions
diff --git a/core/module-jit.c b/core/module-jit.c
index 0254430..ae0f24c 100644
--- a/core/module-jit.c
+++ b/core/module-jit.c
@@ -5750,7 +5750,7 @@ static bool in_code(const PureModule *m, const PureOp *op) {
}
}
-/* Whether a block is worth code: it has a loop, and its loops' blocks have
+/* Whether a block is worth code: it has a loop and no call, and its loops' blocks have
* code for every instruction. Each step a loop calls gives back what the
* code keeps in registers (read again after it), and costs more than the
* executor's own dispatch: a loop of steps (TPC-C's lines, 123) runs as
@@ -5763,6 +5763,8 @@ static bool worth(const PureModule *m, PureBlock block) {
int input = tier_input(m, block);
for (uint32_t pc = block.start; pc < block.start + block.length; pc = after(&m->code[pc], pc)) {
const PureOp *op = &m->code[pc];
+ if (op->op == PURE_OP_CALL) return false; /* the executor's frames (module.c's run_calls) */
+ if ((op->op == PURE_OP_LOCAL_LOAD || op->op == PURE_OP_LOCAL_STORE) && (op->arg >> 8 & 0xFF) == 4) return false; /* a list */
if (op->op == PURE_OP_EACH || op->op == PURE_OP_LOOP) {
loops = true;
uint32_t ends = pc + 1 + (op->arg >> 16);
diff --git a/core/module-run.h b/core/module-run.h
index dc4a023..4ae94a4 100644
--- a/core/module-run.h
+++ b/core/module-run.h
@@ -145,6 +145,8 @@ static inline uint32_t after(const PureOp *op, uint32_t pc) {
* loop ended). STEP_BATCH: step leaves a LOOP planned PLAN_BATCH to its
* caller (module.c). */
enum { STEP_SKIP = 4, STEP_DONE = 5, STEP_BATCH = 6 };
+/* STEP_CALL: step leaves a CALL to its caller (module.c's run_calls), and STEP_BACK a RETURN (027's 2c). */
+enum { STEP_CALL = 7, STEP_BACK = 8 };
int pure_run_step(Frame *, uint32_t pc);
int pure_run_each(Frame *, uint32_t pc);
int pure_run_loop(Frame *, uint32_t pc);
diff --git a/core/module.c b/core/module.c
index db0703c..fec9dd9 100644
--- a/core/module.c
+++ b/core/module.c
@@ -9,6 +9,7 @@
#include <stdlib.h>
#include <string.h>
+enum { FUNCTION_BYTES = 12, SIGNATURE_BYTES = 4 };
enum { STATE_BYTES = 16, COLLECTION_BYTES = 12, INPUT_BYTES = 16, FIELD_BYTES = 8,
CONSTANT_BYTES = 8, RECORD_BYTES = 8, RECORD_FIELD_BYTES = 16, VIEW_BYTES = 16,
HANDLER_BYTES = 12, OP_BYTES = 16 };
@@ -23,8 +24,9 @@ enum { FROM_PAYLOAD, FROM_DOCUMENT, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_AR
/* IN_ARGUMENTS: fields read where nothing is done with them (a view's arguments, a message's fields in a fold). */
/* IN_LOCALS: a handler's locals, read in its walks and the folds in them. */
/* IN_COMPUTE: what a view's body may do too (018): loops, locals, and changes to an event's lists alone. */
+/* IN_FUNCTION: a function's code (027's 2c), a handler's but for a message. */
enum { IN_VIEW = 1, IN_HANDLER = 2, IN_MESSAGE = 4, IN_KEYED = 8, IN_TEXT = 16, IN_ROW = 32, IN_OUTER = 64, IN_ARGUMENTS = 128,
- IN_LOCALS = 256, IN_COMPUTE = 512 };
+ IN_LOCALS = 256, IN_COMPUTE = 512, IN_FUNCTION = 1024 };
enum { READ = IN_VIEW | IN_HANDLER };
/* What an argument names: nothing (it is 0), a state of one type (A_STATE +
* the type), a list and one of its record's fields, a message field, the
@@ -34,18 +36,20 @@ enum { READ = IN_VIEW | IN_HANDLER };
* (A_TYPE: 0 a number, 1 a bool, 2 an i64), how I64_BITS computes (A_BITS),
* what a position is (A_POSITION: 0 a number, 1 an i64), what CONCAT joins
* (A_JOINED: 0 texts, 1 bytes), how NUM_TEXT writes (A_FORMAT) or the value
- * held by name (A_HELD). */
+ * held by name (A_HELD), or a function (A_FUNCTION: CALL's, and RETURN's own). */
enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_I64 + 1, A_FIELD, A_COLLECTION, A_CONSTANT, A_UNIT,
A_ROW, A_TARGET, A_DEPTH, A_FOLD, A_RELATION, A_KEYED, A_OUTPUT, A_OUTER, A_EACH, A_LOCAL, A_VALUE, A_TYPE,
- A_LOOP, A_BITS, A_POSITION, A_JOINED, A_FORMAT, A_HELD };
+ A_LOOP, A_BITS, A_POSITION, A_JOINED, A_FORMAT, A_HELD, A_FUNCTION };
/* A fold's block is verified as a column that leaves one bool (count, first)
* or a value and whether to include it (a bool): a number (FOLD_TWO) or an
* i64 (FOLD_I64). A table's filter is verified as a column that leaves a
* bool: these stand for a view type. A walk's block (EACH) is verified as
* its handler's code that may read the item it runs for (WALK). In a view's
* body a loop's block leaves nothing either (STEPS), and a walk's reads its
- * item (STEPS_WALK); neither changes a state. */
-enum { FOLD_ONE = 100, FOLD_TWO, FOLD_I64, FILTER, WALK, STEPS, STEPS_WALK };
+ * item (STEPS_WALK); neither changes a state. A function's block begins with
+ * its parameters on the stack and ends at RETURN alone (FUNCTION); the loops'
+ * blocks in it are a handler's code. */
+enum { FOLD_ONE = 100, FOLD_TWO, FOLD_I64, FILTER, WALK, STEPS, STEPS_WALK, FUNCTION };
/* The text or list an opcode works on: its cursor and index operands belong
* to it, and so does a cursor or index it pushes. O_ANY: no rule. O_OPERAND:
* it pushes a position in its operand's text or list. O_ARG: the argument.
@@ -149,6 +153,11 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
/* APPEND_IN also takes as many values as the record of the list in the field has fields. */
[PURE_OP_APPEND_IN] = {1, {T_INDEX}, 0, IN_HANDLER | IN_COMPUTE, A_ITEM, O_LIST},
[PURE_OP_NUM_PARSE] = {1, {T_TEXT}, T_NUM, READ, A_NONE, O_ANY},
+ /* CALL takes its function's parameters and gives its results; RETURN takes its function's results (verify_code). */
+ [PURE_OP_CALL] = {0, {0}, 0, IN_HANDLER, A_FUNCTION, O_ANY},
+ [PURE_OP_RETURN] = {0, {0}, 0, IN_FUNCTION, A_FUNCTION, O_ANY},
+ [PURE_OP_CHAR_AT] = {2, {T_TEXT, T_I64}, T_I64, READ, A_NONE, O_ANY},
+ [PURE_OP_CHAR] = {1, {T_I64}, T_TEXT, READ, A_NONE, O_ANY},
};
#undef NUMS
#undef COMPARE
@@ -362,17 +371,26 @@ static bool bound_list(const PureModule *m, uint32_t list) {
return event_list_state(m, list) && m->state_flags[list] & PURE_LIST_BOUND >> 8;
}
-/* The stack type of a record field (the module's numbering). */
-static unsigned char record_type(const PureModule *m, uint32_t field) {
+/* The stack type of a record field's type (PureRecordType), and of a record field (the module's numbering). */
+static unsigned char record_type_of(uint8_t type) {
static const unsigned char types[] = {[PURE_RECORD_NUMBER] = T_NUM, [PURE_RECORD_TEXT] = T_TEXT,
[PURE_RECORD_BOOL] = T_BOOL, [PURE_RECORD_I64] = T_I64, [PURE_RECORD_LIST] = T_LIST};
- return types[m->record_field_type[field]];
+ return types[type];
+}
+static unsigned char record_type(const PureModule *m, uint32_t field) { return record_type_of(m->record_field_type[field]); }
+
+/* The stack type of a function's signature item (a record field's type, or PURE_SIGNATURE_BYTES). */
+static unsigned char signature_stack_type(const PureModule *m, uint32_t item) {
+ return m->signature_type[item] == PURE_SIGNATURE_BYTES ? T_BYTES : record_type_of(m->signature_type[item]);
}
/* The record of the list value an instruction takes as its operand k (bottom 0). */
static unsigned list_wanted(const PureModule *m, const PureOp *op, unsigned k) {
uint32_t arg = op->arg;
switch (op->op) {
+ case PURE_OP_CALL: return m->signature_record[m->function[arg].first + k];
+ case PURE_OP_LOCAL_STORE: return arg >> 16;
+ case PURE_OP_RETURN: return m->signature_record[m->function[arg].first + m->function[arg].params + k];
case PURE_OP_LIST_BIND: return m->state_ref[arg];
case PURE_OP_LIST_APPEND: return nested_record(m, m->record[m->state_ref[arg]].first + k);
case PURE_OP_LIST_INSERT: return nested_record(m, m->record[m->state_ref[arg]].first + k - 1); /* the index first */
@@ -399,10 +417,10 @@ static bool text_field(const PureModule *m, uint32_t list, uint32_t field) {
return m->record_field_type[m->record[m->state_ref[list]].first + field] == PURE_RECORD_TEXT;
}
-/* What a local holds (its argument's bits 8-15): a number, an index, a bool or an i64. */
+/* What a local holds (its argument's bits 8-15): a number, an index, a bool, an i64 or a list (027's 2c). */
static unsigned char local_type(uint32_t arg) {
- static const unsigned char types[] = {T_NUM, T_INDEX, T_BOOL, T_I64};
- return types[arg >> 8 & 3];
+ static const unsigned char types[] = {T_NUM, T_INDEX, T_BOOL, T_I64, T_LIST};
+ return (arg >> 8 & 0xFF) <= 4 ? types[arg >> 8 & 0xFF] : T_NUM;
}
PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg, const PureInputShape *shape, int row,
@@ -426,15 +444,17 @@ PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg
return (kind == A_FOLD ? how <= PURE_FOLD_DISTINCT && (valued || !(flags & PURE_FOLD_INTEGER)) : flags <= PURE_EACH_SLICED) &&
state_of(m, list, PURE_STATE_LIST) && arg >> 16 && (!sliced || key == T_NUM || key == T_I64);
}
- case A_LOCAL: /* a slot, and a number, an index of a list, a bool or an i64 */
- return (arg & 0xFF) < PURE_MODULE_LOCALS && (arg >> 8 & 0xFF) <= 3 &&
- ((arg >> 8 & 0xFF) == 1 ? state_of(m, arg >> 16, PURE_STATE_LIST) : !(arg >> 16));
+ case A_LOCAL: /* a slot, and a number, an index of a list, a bool, an i64 or a list of a record */
+ return (arg & 0xFF) < PURE_MODULE_LOCALS && (arg >> 8 & 0xFF) <= 4 &&
+ ((arg >> 8 & 0xFF) == 1 ? state_of(m, arg >> 16, PURE_STATE_LIST) :
+ (arg >> 8 & 0xFF) == 4 ? (arg >> 16) < m->records : !(arg >> 16));
case A_VALUE: return state_of(m, arg, PURE_STATE_NUM) || state_of(m, arg, PURE_STATE_BOOL) || state_of(m, arg, PURE_STATE_I64);
case A_LOOP: return (arg & 0xFF) < PURE_MODULE_LOCALS && !(arg & 0xFE00); /* a slot, and whether i64s */
case A_TYPE: return arg <= 2;
case A_BITS: return arg <= PURE_BITS_RIGHT;
case A_POSITION: case A_JOINED: return arg <= 1;
case A_HELD: return (arg & ~(uint32_t)PURE_HELD_APPEND) < PURE_MODULE_HELD;
+ case A_FUNCTION: return arg < m->functions;
case A_FORMAT: { /* a type; a width, zeros, a form fitting it and digits for FIXED alone */
uint32_t type = arg & 3, form = arg >> PURE_FORMAT_FORM & 7, digits = arg >> PURE_FORMAT_DIGITS & 31;
return type <= 2 && !(arg & 0xFC) && !(arg >> 25) && form <= PURE_FORMAT_FIXED && (type != 1 || !form) &&
@@ -468,17 +488,18 @@ static bool same_stack(const Join *join, int height, const unsigned char *types,
* into it. Returns why the block is refused, with at moved to the
* instruction when one is at fault, or NULL. */
PURE_COLD static const char *verify_block(const PureModule *, PureBlock, int view, int row, int outer, uint32_t event,
- bool locals, unsigned nest, uint32_t walked, uint32_t *at);
+ bool locals, unsigned nest, uint32_t walked, int fn, uint32_t *at);
/* target marks, per instruction of the block, a branch into it with an empty stack. */
/* outer: the row of the column a fold's block runs in (-1: none). */
/* locals: the block is a handler's, or runs in one (a walk, a fold): it reads the handler's locals. */
/* nest: the loops (LOOP, EACH) the block runs in; walked: a bit for each list the walks among them walk. */
+/* fn: the function whose code the block is (its own block, FUNCTION, or a loop's in it), or -1. */
PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, int view, int row, int outer,
- uint32_t event, bool locals, unsigned nest, uint32_t walked, uint32_t *at,
+ uint32_t event, bool locals, unsigned nest, uint32_t walked, int fn, uint32_t *at,
unsigned char *target) {
#define REFUSE(why) do { *at = pc; return why; } while (0)
- bool steps = view == STEPS || view == STEPS_WALK, handler = view < 0 || view == WALK || steps;
+ bool steps = view == STEPS || view == STEPS_WALK, handler = view < 0 || view == WALK || steps || view == FUNCTION;
bool folding = view == FOLD_ONE || view == FOLD_TWO || view == FOLD_I64;
/* A view outside a table computes (locals: its code reads them): a body, and the loops in it. */
bool body = steps || (!handler && !folding && view != FILTER && row < 0 && locals);
@@ -494,11 +515,17 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
if (shape && (!handler || steps) && (shape->kind == PURE_KIND_MESSAGE || shape->kind == PURE_KIND_ARGUMENTS))
where |= IN_ARGUMENTS;
if (locals) where |= IN_LOCALS;
+ if (fn >= 0) where |= IN_FUNCTION;
unsigned char types[PURE_MODULE_STACK], owners[PURE_MODULE_STACK];
Join joins[PURE_MODULE_JOINS];
unsigned open = 0;
int height = 0;
bool reachable = true;
+ if (view == FUNCTION) /* its parameters */
+ for (const PureFunction *f = &m->function[fn]; height < f->params; ++height) {
+ types[height] = signature_stack_type(m, f->first + (uint32_t)height);
+ owners[height] = (unsigned char)(types[height] == T_LIST ? m->signature_record[f->first + height] : 0);
+ }
for (uint32_t i = 0; i <= block.length; ++i) {
uint32_t pc = block.start + i;
/* Branches here brought an empty stack (target) or the stack of a join. */
@@ -524,6 +551,8 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
if (op->op >= PURE_OP_COUNT) REFUSE("unknown opcode");
unsigned allowed = signature[op->op].where, kind = signature[op->op].arg, of = signature[op->op].of;
if (!(allowed & where)) {
+ if (allowed & IN_FUNCTION) REFUSE("return outside a function");
+ if (op->op == PURE_OP_CALL) REFUSE("call outside a handler or a function");
if (allowed & IN_ROW) REFUSE("row outside a column");
if (allowed & IN_OUTER) REFUSE("outer row outside a fold in a column");
if (allowed & IN_LOCALS) REFUSE("local outside a handler");
@@ -595,13 +624,14 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
uint32_t list = arg >> 8 & 0xFF;
/* A fold in a walk reads the walk's item as a fold in a column reads its row; a loop's block is its block's code. */
const char *why = kind == A_EACH ? verify_block(m, (PureBlock){pc + 1, skip}, body ? STEPS_WALK : WALK, (int)list, -1,
- event, locals, nest + 1, walked | UINT32_C(1) << (list & 31), &inner)
- : kind == A_LOOP ? verify_block(m, (PureBlock){pc + 1, skip}, body && !steps ? STEPS : view, row,
- outer, event, locals, nest + 1, walked, &inner)
+ event, locals, nest + 1, walked | UINT32_C(1) << (list & 31), fn, &inner)
+ : kind == A_LOOP ? verify_block(m, (PureBlock){pc + 1, skip}, body && !steps ? STEPS :
+ view == FUNCTION ? -1 : view, row, outer, event, locals, nest + 1,
+ walked, fn, &inner)
: verify_block(m, (PureBlock){pc + 1, skip},
how == PURE_FOLD_COUNT || how == PURE_FOLD_FIRST ? FOLD_ONE :
arg & PURE_FOLD_INTEGER ? FOLD_I64 : FOLD_TWO,
- (int)list, row, event, locals, nest, walked, &inner);
+ (int)list, row, event, locals, nest, walked, -1, &inner);
if (why) { *at = inner; return why; }
for (uint32_t k = i + 1; k <= i + skip; ++k) {
const char *into = kind == A_FOLD ? "branch into a fold" : kind == A_EACH ? "branch into an each" :
@@ -654,7 +684,17 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
} else if (kind == A_LOOP) {
need[0] = need[1] = arg & PURE_LOOP_INTEGER ? T_I64 : T_NUM; /* low and high */
pops = 2;
+ } else if (kind == A_FUNCTION) { /* a function's parameters, or the results its RETURN gives */
+ const PureFunction *f = &m->function[arg];
+ if (op->op == PURE_OP_RETURN && (int)arg != fn) REFUSE("return from another function");
+ uint32_t first = f->first + (op->op == PURE_OP_RETURN ? f->params : 0);
+ pops = op->op == PURE_OP_RETURN ? f->results : f->params;
+ for (unsigned k = 0; k < pops; ++k) need[k] = signature_stack_type(m, first + k);
}
+ /* A loop's block begins at the bottom of its run's stack: its operands are all the stack holds. */
+ if ((kind == A_LOOP || kind == A_EACH) && height != (int)pops) REFUSE("loop above values");
+ /* What a walk keeps to, a call keeps to as well: a call changes no list a walk around it walks. */
+ if (op->op == PURE_OP_CALL && m->function[arg].changes & walked) REFUSE("call that changes a list walked");
if (height < (int)pops) REFUSE("stack underflow");
height -= (int)pops;
for (unsigned k = 0; k < pops; ++k)
@@ -686,11 +726,18 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
if (integer) out = T_I64;
if ((op->op == PURE_OP_CONCAT && arg) || (op->op == PURE_OP_BYTES_LOAD && (arg & 0xFF) < PURE_HELD_TEXT)) out = T_BYTES;
else if (op->op == PURE_OP_BYTES_LOAD) out = T_TEXT;
+ if (op->op == PURE_OP_CALL) /* its results */
+ for (uint32_t k = 0, first = m->function[arg].first + m->function[arg].params; k < m->function[arg].results; ++k) {
+ if (height == PURE_MODULE_STACK) REFUSE("stack overflow");
+ types[height] = signature_stack_type(m, first + k);
+ owners[height] = (unsigned char)(types[height] == T_LIST ? m->signature_record[first + k] : 0);
+ ++height;
+ }
if (out) {
if (height == PURE_MODULE_STACK) REFUSE("stack overflow");
owners[height] = (unsigned char)(out != T_CURSOR && out != T_INDEX ? 0 : of == O_OPERAND ? owners[height] : owner);
if (out == T_LIST) /* the record of its items */
- owners[height] = (unsigned char)(op->op == PURE_OP_LIST_VALUE ? m->state_ref[arg] :
+ owners[height] = (unsigned char)(op->op == PURE_OP_LIST_VALUE ? m->state_ref[arg] : op->op == PURE_OP_LOCAL_LOAD ? arg >> 16 :
nested_record(m, m->record[m->state_ref[list_of(arg)]].first + item_field(arg)));
types[height++] = out;
}
@@ -716,9 +763,12 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
target[block.length] = 1;
reachable = false;
}
+ if (op->op == PURE_OP_RETURN) reachable = false; /* the rest of its stack let go */
i += skip;
}
- if (!reachable) return "block end is unreachable";
+ if (view == FUNCTION) return reachable ? "function ends without return" : NULL;
+ if (!reachable && fn < 0) return "block end is unreachable";
+ if (!reachable) return NULL; /* a loop's block in a function that returns */
if (view == FILTER) return height != 1 || types[0] != T_BOOL ? "filter must leave one bool" : NULL;
if (view == FOLD_ONE) return height != 1 || types[0] != T_BOOL ? "fold block must leave one bool" : NULL;
if (view == FOLD_TWO)
@@ -733,11 +783,11 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
}
PURE_COLD static const char *verify_block(const PureModule *m, PureBlock block, int view, int row, int outer,
- uint32_t event, bool locals, unsigned nest, uint32_t walked, uint32_t *at) {
+ uint32_t event, bool locals, unsigned nest, uint32_t walked, int fn, uint32_t *at) {
if (block.start > m->code_count || block.length > m->code_count - block.start) return "code range outside module";
unsigned char *target = calloc((size_t)block.length + 1, 1);
if (!target) return "out of memory";
- const char *why = verify_code(m, block, view, row, outer, event, locals, nest, walked, at, target);
+ const char *why = verify_code(m, block, view, row, outer, event, locals, nest, walked, fn, at, target);
free(target);
return why;
}
@@ -752,7 +802,7 @@ PURE_COLD static bool has_lanes(const PureModule *m, const PureOp *op, int input
}
case PURE_OP_CONST: return op->arg != 2;
case PURE_OP_LOAD: return m->state_type[op->arg] != PURE_STATE_I64;
- case PURE_OP_LOCAL_LOAD: return local_type(op->arg) != T_I64;
+ case PURE_OP_LOCAL_LOAD: return local_type(op->arg) != T_I64 && local_type(op->arg) != T_LIST;
case PURE_OP_ADD: case PURE_OP_SUB: case PURE_OP_MUL: case PURE_OP_DIV: case PURE_OP_NEG:
case PURE_OP_LT: case PURE_OP_LE: case PURE_OP_GT: case PURE_OP_GE: case PURE_OP_EQ: case PURE_OP_NE:
case PURE_OP_AND: case PURE_OP_OR: case PURE_OP_NOT: case PURE_OP_MIN: case PURE_OP_MAX: case PURE_OP_CLAMP:
@@ -842,7 +892,7 @@ PURE_COLD static bool batch_block(const PureModule *m, uint32_t start, uint32_t
if (op->arg > arms) arms = op->arg;
break;
case PURE_OP_LOCAL_STORE:
- if (local_type(op->arg) == T_I64) return false;
+ if (local_type(op->arg) == T_I64 || local_type(op->arg) == T_LIST) return false;
if (at >= arms) sure[local / 64] |= bit;
break;
case PURE_OP_LOCAL_LOAD:
@@ -1451,6 +1501,46 @@ PURE_COLD static void range_code(PureModule *m, PureBlock block, int input) {
}
}
+/* What a function's code names (027's 2c), read before it is verified (its arguments bounded here): the event's lists,
+ * the registers and the values held by name each call has its own of, the lists not an event's it changes, its locals
+ * and how deep its loops nest; the functions it calls (calls, a bit each). */
+PURE_COLD static void function_names(const PureModule *m, PureFunction *f, uint64_t *calls) {
+ uint32_t end = f->code.start + f->code.length, ends[PURE_MODULE_NESTING];
+ unsigned depth = 0;
+ if (f->code.start > m->code_count || f->code.length > m->code_count - f->code.start) return; /* refused as it is verified */
+ for (uint32_t pc = f->code.start; pc < end; ++pc) {
+ const PureOp *op = &m->code[pc];
+ uint32_t arg = op->arg, list = UINT32_MAX;
+ while (depth && pc >= ends[depth - 1]) --depth;
+ switch (op->op) {
+ case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: case PURE_OP_LIST_REMOVE:
+ list = arg;
+ if (state_of(m, list, PURE_STATE_LIST) && !event_list_state(m, list)) f->changes |= UINT32_C(1) << list;
+ break;
+ case PURE_OP_LIST_COUNT: case PURE_OP_LIST_VALUE: case PURE_OP_LIST_BIND: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST:
+ case PURE_OP_INDEX_AT: case PURE_OP_BYTES_OF: case PURE_OP_ROW: case PURE_OP_OUTER_ROW: case PURE_OP_LIST_FIND:
+ case PURE_OP_LIST_HAS:
+ list = arg;
+ break;
+ case PURE_OP_ITEM_GET: case PURE_OP_ITEM_SET: case PURE_OP_APPEND_IN: list = list_of(arg); break;
+ case PURE_OP_EACH: case PURE_OP_FOLD: case PURE_OP_LOOP: case PURE_OP_LOCAL_LOAD: case PURE_OP_LOCAL_STORE:
+ if (op->op == PURE_OP_EACH || op->op == PURE_OP_FOLD) list = arg >> 8 & 0xFF;
+ if ((op->op == PURE_OP_EACH || op->op == PURE_OP_LOOP) && depth < PURE_MODULE_NESTING) {
+ ends[depth++] = pc + 1 + (arg >> 16);
+ if (depth > f->nesting) f->nesting = (uint8_t)depth;
+ }
+ if (op->op == PURE_OP_EACH || op->op == PURE_OP_FOLD) break;
+ if ((arg & 0xFF) < PURE_MODULE_LOCALS && (arg & 0xFF) >= f->locals) f->locals = (uint8_t)((arg & 0xFF) + 1);
+ if (op->op != PURE_OP_LOOP && (arg >> 8 & 0xFF) == 1) list = arg >> 16; /* an index's list */
+ break;
+ case PURE_OP_BYTES_LOAD: case PURE_OP_BYTES_STORE: f->held |= UINT32_C(1) << (arg & 31); break;
+ case PURE_OP_CALL: if (arg < PURE_MODULE_FUNCTIONS) *calls |= UINT64_C(1) << arg; break;
+ default: break;
+ }
+ if (event_list_state(m, list)) *(bound_list(m, list) ? &f->registers : &f->lists) |= UINT32_C(1) << list;
+ }
+}
+
PURE_COLD PureResult pure_module_read(const char *path, PureModule *out, char *error, size_t error_size) {
FILE *file = fopen(path, "rb");
unsigned char *bytes = NULL;
@@ -1479,7 +1569,8 @@ PURE_COLD PureResult pure_module_read(const char *path, PureModule *out, char *e
void pure_module_release(PureModule *m) {
pure_jit_free(m->jit);
m->jit = NULL;
- m->batches = false;
+ m->batches = m->calls = false;
+ m->runner = 0;
free(m->height);
m->height = NULL;
free(m->reuse_from), free(m->reuse_length);
@@ -1516,21 +1607,31 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
out->records = (uint16_t)read16(p + 30);
uint32_t constant_bytes = read32(p + 32);
out->record_fields = (uint16_t)read16(p + 36);
- if (read16(p + 38)) REFUSE("reserved header bytes", 38);
+ out->functions = (uint16_t)read16(p + 38);
if (out->states > PURE_MODULE_STATES || out->inputs > PURE_MODULE_INPUTS ||
out->views > PURE_MODULE_VIEWS || out->handlers > PURE_MODULE_HANDLERS ||
out->code_count > PURE_MODULE_CODE || strings > PURE_MODULE_STRINGS ||
out->fields > PURE_MODULE_FIELDS || out->collections > PURE_MODULE_COLLECTIONS ||
out->constants > PURE_MODULE_CONSTANTS || constant_bytes > PURE_MODULE_CONSTANT_BYTES ||
- out->records > PURE_MODULE_RECORDS || out->record_fields > PURE_MODULE_RECORD_FIELDS) {
+ out->records > PURE_MODULE_RECORDS || out->record_fields > PURE_MODULE_RECORD_FIELDS ||
+ out->functions > PURE_MODULE_FUNCTIONS) {
result = PURE_LIMIT;
REFUSE("module exceeds a limit", 8);
}
- size_t expected = PURE_MODULE_HEADER + (size_t)out->states * STATE_BYTES +
+ size_t functions_at = PURE_MODULE_HEADER + (size_t)out->states * STATE_BYTES +
(size_t)out->collections * COLLECTION_BYTES + (size_t)out->inputs * INPUT_BYTES +
(size_t)out->fields * FIELD_BYTES + (size_t)out->constants * CONSTANT_BYTES +
(size_t)out->records * RECORD_BYTES + (size_t)out->record_fields * RECORD_FIELD_BYTES +
- (size_t)out->views * VIEW_BYTES + (size_t)out->handlers * HANDLER_BYTES +
+ (size_t)out->views * VIEW_BYTES + (size_t)out->handlers * HANDLER_BYTES;
+ size_t signatures = 0; /* the functions' parameters' and results' items, in their order */
+ if (size < functions_at + (size_t)out->functions * FUNCTION_BYTES) REFUSE("module size", functions_at);
+ for (uint32_t i = 0; i < out->functions; ++i) signatures += (size_t)p[functions_at + i * FUNCTION_BYTES + 8] +
+ p[functions_at + i * FUNCTION_BYTES + 9];
+ if (signatures > PURE_MODULE_SIGNATURES) {
+ result = PURE_LIMIT;
+ REFUSE("module exceeds a limit", functions_at);
+ }
+ size_t expected = functions_at + (size_t)out->functions * FUNCTION_BYTES + signatures * SIGNATURE_BYTES +
(size_t)out->code_count * OP_BYTES + strings + constant_bytes;
if (size != expected) REFUSE("module size", expected);
/* The code, the constants' offsets and lengths, then their bytes, in one block. */
@@ -1766,6 +1867,21 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
(input >= 0 && (out->input[input].kind == PURE_KIND_OUTPUT || out->input[input].kind == PURE_KIND_ARGUMENTS)))
REFUSE("handler event", i);
}
+ /* Functions (027's 2c): a block and the counts of its parameters and results, whose types follow every function's. */
+ for (uint32_t i = 0, item = 0; i < out->functions; ++i, at += FUNCTION_BYTES) {
+ out->function[i] = (PureFunction){.code = {read32(at), read32(at + 4)}, .first = (uint16_t)item, .params = at[8],
+ .results = at[9]};
+ if (read16(at + 10)) REFUSE("function padding", i);
+ item += (uint32_t)at[8] + at[9];
+ }
+ for (uint32_t k = 0; k < signatures; ++k, at += SIGNATURE_BYTES) {
+ out->signature_type[k] = at[0];
+ out->signature_record[k] = (uint16_t)read16(at + 2);
+ if (at[1] || at[0] > PURE_SIGNATURE_BYTES ||
+ (at[0] == PURE_RECORD_LIST ? out->signature_record[k] >= out->records : out->signature_record[k] != 0))
+ REFUSE("function signature", k);
+ }
+ out->calls = out->functions > 0;
for (uint32_t i = 0; i < out->code_count; ++i, at += OP_BYTES) {
if (at[1] || at[2] || at[3]) REFUSE("instruction padding", i);
out->code[i] = (PureOp){.op = at[0], .arg = read32(at + 4), .value = read_double(at + 8)};
@@ -1794,7 +1910,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
bool computes = !table && !out->view_table[i]; /* a view outside a table may have a body */
if ((!table || out->view_code[i].length) &&
(why = verify_block(out, out->view_code[i], table ? FILTER : out->view_type[i],
- table || out->view_table[i] ? out->view_list[i] : -1, -1, event, computes, 0, 0, &bad)))
+ table || out->view_table[i] ? out->view_list[i] : -1, -1, event, computes, 0, 0, -1, &bad)))
goto refused;
if (computes && !locals_agree(out, out->view_code[i])) { why = "local used as two things"; goto refused; }
for (uint32_t pc = out->view_code[i].start; computes && pc < out->view_code[i].start + out->view_code[i].length; ++pc) {
@@ -1807,9 +1923,29 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
}
if (out->view_lists[i]) out->view_body[i] = true;
}
+ /* What each function names, and the lists it changes with those the functions it calls change (until none adds). */
+ uint64_t calls[PURE_MODULE_FUNCTIONS] = {0};
+ for (uint32_t i = 0; i < out->functions; ++i) function_names(out, &out->function[i], &calls[i]);
+ for (bool more = out->functions > 0; more;) {
+ more = false;
+ for (uint32_t i = 0; i < out->functions; ++i)
+ for (uint64_t left = calls[i]; left; left &= left - 1) {
+ uint32_t was = out->function[i].changes;
+ out->function[i].changes |= out->function[__builtin_ctzll(left)].changes;
+ more = more || out->function[i].changes != was;
+ }
+ }
+ for (uint32_t i = 0; i < out->functions; ++i) {
+ bad = i;
+ PureFunction *f = &out->function[i];
+ if ((why = verify_block(out, f->code, FUNCTION, -1, -1, 0, true, 0, 0, (int)i, &bad))) goto refused;
+ if (!locals_agree(out, f->code)) { why = "local used as two things"; goto refused; }
+ for (uint32_t pc = f->code.start; pc < f->code.start + f->code.length; ++pc) /* its frame's stack: as the verifier went */
+ if (out->height[pc] >= f->height) f->height = (uint8_t)(out->height[pc] + 1);
+ }
for (uint32_t i = 0; i < out->handlers; ++i) {
bad = i;
- if ((why = verify_block(out, out->handler[i].code, -1, -1, -1, out->handler[i].event, true, 0, 0, &bad))) goto refused;
+ if ((why = verify_block(out, out->handler[i].code, -1, -1, -1, out->handler[i].event, true, 0, 0, -1, &bad))) goto refused;
if (!locals_agree(out, out->handler[i].code)) { why = "local used as two things"; goto refused; }
}
for (uint32_t i = 0; i < out->views; ++i) {
@@ -1907,6 +2043,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
result = PURE_ALLOCATION;
REFUSE("out of memory", 0);
}
+ out->runner = out->calls ? 2 : out->batches ? 1 : 0;
out->jit = pure_jit_make(out); /* none: the executor runs it all */
return PURE_OK;
refused:
@@ -2226,9 +2363,25 @@ static bool copy_out(void *context, PureSpan span) {
}
/* BYTES_STORE: the value held by name slot becomes v, or v after it (append); what it holds counts in the room.
- * 1 where that passes the room (MEMORY), 2 out of memory, else 0. */
+ * 1 where that passes the room (MEMORY), 2 out of memory, else 0. A value of the payload, the arguments or the constants
+ * lasts the event: it is held as it is (by_ref), nothing copied, until something is appended to it. */
+static int hold_copy(PureApp *app, PureArena *arena, unsigned slot, PureBytes v, bool append);
static int hold(PureApp *app, PureArena *arena, unsigned slot, PureBytes v, bool append) {
__typeof__(app->held[0]) *h = &app->held[slot];
+ if (!append && (v.source == FROM_PAYLOAD || v.source == FROM_ARGUMENTS || v.source == FROM_CONSTANT)) {
+ if (!h->by_ref) (void)grows(app, (Held){0, h->size}, (Held){0, 0});
+ h->ref = v, h->size = (size_t)v.length, h->by_ref = true;
+ return 0;
+ }
+ if (h->by_ref) { /* what it held as it was: copied first when v follows it */
+ h->by_ref = false, h->size = 0;
+ int held = append ? hold_copy(app, arena, slot, h->ref, false) : 0;
+ if (held) return held;
+ }
+ return hold_copy(app, arena, slot, v, append);
+}
+static int hold_copy(PureApp *app, PureArena *arena, unsigned slot, PureBytes v, bool append) {
+ __typeof__(app->held[0]) *h = &app->held[slot];
size_t mark = arena->used, before = h->size, at = append ? h->size : 0, size = at + (size_t)v.length;
if (v.source == FROM_HELD && v.owner == slot && v.length) { /* a part of itself: by way of the arena */
if (append_value(app, arena, v) != PURE_OK) return 2;
@@ -3753,7 +3906,8 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
NEXT;
}
case PURE_OP_BYTES_LOAD:
- stack[sp++].bytes = (PureBytes){FROM_HELD, (uint16_t)(arg & 0xFF), {0, 0}, 0, app->held[arg & 0xFF].size};
+ stack[sp++].bytes = app->held[arg & 0xFF].by_ref ? app->held[arg & 0xFF].ref
+ : (PureBytes){FROM_HELD, (uint16_t)(arg & 0xFF), {0, 0}, 0, app->held[arg & 0xFF].size};
NEXT;
case PURE_OP_BYTES_STORE: {
int held = hold(app, arena, arg & 0xFF, stack[--sp].bytes, arg & PURE_HELD_APPEND);
@@ -3823,6 +3977,38 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
if (!grows(app, before, (Held){0, pure_list_size(in)})) { kind = PURE_FAULT_MEMORY; FAILED; }
NEXT;
}
+ case PURE_OP_CALL: case PURE_OP_RETURN: /* run_calls' (the frames are its): no other run meets one */
+ frame->fault_pc = pc;
+ return !batches ? STEP_FAULT : op->op == PURE_OP_CALL ? STEP_CALL : STEP_BACK;
+ case PURE_OP_CHAR_AT: { /* the character beginning at a byte position of a text (UTF-8: whole from there) */
+ int64_t at = stack[--sp].i64;
+ PureBytes v = stack[sp - 1].bytes;
+ if (at < 0 || (uint64_t)at >= v.length) { kind = PURE_FAULT_LIMIT; FAILED; }
+ unsigned char b[4] = {0};
+ Copy into = {b};
+ v.offset += (uint64_t)at;
+ v.length = v.length - (uint64_t)at < 4 ? v.length - (uint64_t)at : 4;
+ if ((status = visit_value(app, arena, v, copy_out, &into)) != PURE_OK) STOPPED(status);
+ if ((b[0] & 0xC0) == 0x80) { kind = PURE_FAULT_LIMIT; FAILED; } /* within a character */
+ stack[sp - 1].i64 = b[0] < 0x80 ? b[0] : b[0] < 0xE0 ? (b[0] & 0x1F) << 6 | (b[1] & 0x3F) :
+ b[0] < 0xF0 ? (b[0] & 0x0F) << 12 | (b[1] & 0x3F) << 6 | (b[2] & 0x3F) :
+ (b[0] & 0x07) << 18 | (b[1] & 0x3F) << 12 | (b[2] & 0x3F) << 6 | (b[3] & 0x3F);
+ NEXT;
+ }
+ case PURE_OP_CHAR: { /* the text of a code point */
+ int64_t c = top->i64;
+ if (c < 0 || c > 0x10FFFF) { kind = PURE_FAULT_LIMIT; FAILED; }
+ if (c >= 0xD800 && c < 0xE000) { kind = PURE_FAULT_ENCODING; FAILED; }
+ unsigned char b[4];
+ size_t n = c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;
+ if (n == 1) b[0] = (unsigned char)c;
+ else {
+ for (size_t k = n - 1; k > 0; --k, c >>= 6) b[k] = (unsigned char)(0x80 | (c & 0x3F));
+ b[0] = (unsigned char)((0xF00 >> n & 0xF0) | c); /* 110x, 1110, 11110 */
+ }
+ if ((status = keep_bytes(arena, b, n, &top->bytes)) != PURE_OK) STOPPED(status);
+ NEXT;
+ }
default: break;
}
double b = stack[--sp].num, a = stack[sp - 1].num, value;
@@ -3956,6 +4142,245 @@ static __attribute__((noinline)) PureResult run_compiled(PureCompiled code, cons
return finish(&frame, code(&frame), result);
}
+/* ---- Calls (027's 2c). A call's frame, in the app's blocks of frames (they never move: a loop points into its frame):
+ * the frame below, where the caller was (what follows its CALL, where its code ends, its loops, stack, locals and walk),
+ * where the blocks stood before it and its bytes (counted in the event's room); then what it keeps of the caller's own
+ * (the lists the function's code names, its held values) and the callee's stack, locals and loops. */
+typedef struct Call {
+ struct Call *below;
+ uint32_t back, end, function;
+ unsigned depth, block;
+ size_t sp, used, bytes, row;
+ Slot *stack;
+ Loop *loops;
+ PureValue *locals;
+ Walk *walk;
+} Call;
+typedef __typeof__(((PureApp *)0)->held[0]) Kept;
+
+/* 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) {
+ __typeof__(app->calls) *c = &app->calls;
+ *block = c->at, *used = c->used;
+ if (c->count && c->sizes[c->at] - c->used >= bytes) {
+ void *room = c->blocks[c->at] + c->used;
+ c->used += bytes;
+ return room;
+ }
+ unsigned next = c->count ? c->at + 1 : 0; /* the blocks after the one in use are free */
+ if (next < c->count && c->sizes[next] < bytes) free(c->blocks[next]), c->blocks[next] = NULL;
+ if (next == c->count) {
+ unsigned char **blocks = realloc(c->blocks, (c->count + 1) * sizeof(*blocks));
+ if (blocks) c->blocks = blocks;
+ size_t *sizes = blocks ? realloc(c->sizes, (c->count + 1) * sizeof(*sizes)) : NULL;
+ if (!sizes) return NULL;
+ c->sizes = sizes;
+ c->blocks[c->count++] = NULL;
+ }
+ if (!c->blocks[next]) {
+ size_t size = (size_t)64 << 10 << (next < 8 ? next : 8); /* 64 KB, twice as much each block on to 16 MB */
+ if (size < bytes) size = bytes;
+ if (!(c->blocks[next] = malloc(size))) return NULL;
+ c->sizes[next] = size;
+ }
+ c->at = next, c->used = bytes;
+ return c->blocks[next];
+}
+
+/* An empty list of an event's list state for a call: one a call let go, or a new one (NULL: out of memory). */
+static PureList *spare_list(PureApp *app, uint32_t state) {
+ __typeof__(app->spare[0]) *spare = &app->spare[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. */
+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 (spare->count == spare->capacity) {
+ unsigned capacity = spare->capacity ? 2 * spare->capacity : 4;
+ PureList **lists = realloc(spare->lists, capacity * sizeof(*lists));
+ if (!lists) { pure_list_free(list); return; }
+ spare->lists = lists, spare->capacity = capacity;
+ }
+ spare->lists[spare->count++] = list;
+}
+
+static void drop_spares(PureApp *app) {
+ for (unsigned i = 0; i < PURE_MODULE_STATES; ++i) {
+ for (unsigned k = 0; k < app->spare[i].count; ++k) pure_list_free(app->spare[i].lists[k]);
+ free(app->spare[i].lists);
+ app->spare[i].lists = NULL, app->spare[i].count = app->spare[i].capacity = 0;
+ }
+}
+
+/* The first count of the lists and registers a function's code names given back what a call kept of them (its own
+ * lists let go). */
+static void restore_lists(PureApp *app, const PureFunction *f, PureList **kept, unsigned count) {
+ unsigned k = 0;
+ for (uint32_t left = f->lists | f->registers; left && k < count; left &= left - 1, ++k) {
+ uint32_t state = (uint32_t)__builtin_ctz(left);
+ if (!(f->registers >> state & 1)) let_list_go(app, state, app->lists[state]);
+ app->lists[state] = kept[k];
+ }
+}
+
+/* A frame closed: the caller's lists, registers and held values given back (the callee's let go), its locals and walk,
+ * the frame's room in the blocks and in the event's room. The frame below. */
+static Call *give_back(PureApp *app, Call *call, Event *ev) {
+ const PureFunction *f = &app->module->function[call->function];
+ unsigned keeps = (unsigned)__builtin_popcount(f->lists | f->registers), j = 0;
+ PureList **kept = (PureList **)(call + 1);
+ Kept *held = (Kept *)(kept + keeps);
+ restore_lists(app, f, kept, keeps);
+ for (uint32_t left = f->held; left; left &= left - 1, ++j) {
+ Kept *h = &app->held[__builtin_ctz(left)];
+ if (!h->by_ref) (void)grows(app, (Held){0, h->size}, (Held){0, 0});
+ free(h->data);
+ *h = held[j];
+ }
+ ev->locals = call->locals, ev->walk = call->walk, ev->row = call->row;
+ (void)grows(app, (Held){0, call->bytes}, (Held){0, 0});
+ app->calls.at = call->block, app->calls.used = call->used;
+ return call->below;
+}
+
+/* A bytes or text value a call would lose (held by name in a slot the callee has its own of): a copy in the arena. */
+static bool kept_apart(PureApp *app, PureArena *arena, const PureFunction *f, uint8_t type, PureBytes *v) {
+ if ((type != PURE_RECORD_TEXT && type != PURE_SIGNATURE_BYTES) || v->source != FROM_HELD || !(f->held >> v->owner & 1))
+ return true;
+ size_t start = arena->used;
+ if (append_value(app, arena, *v) != PURE_OK) return false;
+ *v = (PureBytes){FROM_ARENA, 0, {0, 0}, start, v->length};
+ return true;
+}
+
+/* CALL (just before *pc): its function's frame open (*top), its block next. STEP_NEXT, or a fault (WORK, MEMORY) or a
+ * stop out of memory, nothing opened. */
+static __attribute__((noinline, cold)) int enter_call(Frame *frame, Call **top, uint32_t *pc) {
+ const PureModule *m = frame->m;
+ PureApp *app = frame->app;
+ Event *ev = frame->ev;
+ uint32_t function = m->code[*pc - 1].arg;
+ const PureFunction *f = &m->function[function];
+ 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;
+ 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; }
+ unsigned block;
+ size_t used, base = frame->sp - f->params;
+ Call *call = frame_room(app, bytes, &block, &used);
+ if (!call) { frame->status = PURE_ALLOCATION; return STEP_RETURN; }
+ PureList **kept = (PureList **)(call + 1);
+ Kept *held = (Kept *)(kept + keeps);
+ Slot *stack = (Slot *)(held + holds);
+ PureValue *locals = (PureValue *)(stack + f->height);
+ bool whole = true;
+ unsigned k = 0;
+ for (unsigned i = 0; whole && i < f->params; ++i) { /* its arguments, its stack as it begins */
+ stack[i] = frame->stack[base + i];
+ whole = kept_apart(app, frame->arena, f, m->signature_type[f->first + i], &stack[i].bytes);
+ }
+ for (uint32_t left = f->lists | f->registers; whole && left; left &= left - 1, ++k) { /* its own lists, registers */
+ uint32_t state = (uint32_t)__builtin_ctz(left);
+ PureList *own = f->registers >> state & 1 ? (app->unbound[state] ? app->unbound[state] : app->lists[state])
+ : spare_list(app, state);
+ if (!own) {
+ restore_lists(app, f, kept, k);
+ whole = false;
+ break;
+ }
+ kept[k] = app->lists[state], app->lists[state] = own;
+ }
+ if (!whole) {
+ app->calls.at = block, app->calls.used = used;
+ frame->status = PURE_ALLOCATION;
+ return STEP_RETURN;
+ }
+ unsigned j = 0;
+ for (uint32_t left = f->held; left; left &= left - 1, ++j) { /* its held values, empty */
+ Kept *h = &app->held[__builtin_ctz(left)];
+ held[j] = *h;
+ *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,
+ .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;
+ frame->end = f->code.start + f->code.length;
+ ev->locals = locals, ev->walk = NULL;
+ *top = call;
+ *pc = f->code.start;
+ return STEP_NEXT;
+}
+
+/* RETURN (just before *pc): its results in the place of the call's arguments, the frame closed, what follows the CALL
+ * next. STEP_NEXT, or a stop out of memory (the frame still open). */
+static __attribute__((noinline, cold)) int leave_call(Frame *frame, Call **top, uint32_t *pc) {
+ const PureModule *m = frame->m;
+ Call *call = *top;
+ const PureFunction *f = &m->function[call->function];
+ Slot *results = frame->stack + frame->sp - f->results;
+ for (unsigned i = 0; i < f->results; ++i)
+ if (!kept_apart(frame->app, frame->arena, f, m->signature_type[f->first + f->params + i], &results[i].bytes)) {
+ frame->status = PURE_ALLOCATION;
+ return STEP_RETURN;
+ }
+ for (unsigned i = 0; i < f->results; ++i) call->stack[call->sp + i] = results[i];
+ frame->stack = call->stack, frame->sp = call->sp + f->results, frame->loops = call->loops, frame->depth = call->depth;
+ frame->end = call->end;
+ *pc = call->back;
+ *top = give_back(frame->app, call, frame->ev);
+ return STEP_NEXT;
+}
+
+/* Every frame still open closed (a run stopped in a call). */
+static __attribute__((noinline, cold)) void unwind_calls(Frame *frame, Call *top) {
+ while (top) top = give_back(frame->app, top, frame->ev);
+}
+
+/* The executor's run of a block of a module with functions (027's 2c): run_batches' steps and batches, and calls: a
+ * CALL opens its function's frame and its block runs on in this loop, a RETURN closes it. Its own copy of the steps,
+ * as run_batches is. */
+static __attribute__((noinline, aligned(64))) PureResult run_calls(const PureModule *m, PureValue *values, PureBlock block, Event *ev,
+ Slot *result) {
+ Slot stack[PURE_MODULE_STACK];
+ Loop loops[PURE_MODULE_NESTING];
+ Frame frame;
+ open_frame(&frame, m, values, block, ev, stack, loops);
+ Call *top = NULL;
+ int how = STEP_NEXT;
+ for (uint32_t pc = block.start;;) {
+ if (pc >= frame.end) {
+ if (!frame.depth || (how = iterate(&frame, &pc)) != STEP_NEXT) break;
+ continue;
+ }
+ if ((how = step(&frame, &m->code[pc], pc, &pc, true)) == STEP_NEXT) continue;
+ if (how != STEP_CALL && how != STEP_BACK && how != STEP_BATCH) break;
+ Frame apart = frame; /* the frame's copy (no call sees the frame itself: its fields stay in registers) */
+ if (how == STEP_CALL) how = enter_call(&apart, &top, &pc);
+ else if (how == STEP_BACK) how = leave_call(&apart, &top, &pc);
+ else {
+ uint32_t past = 0;
+ how = open_batches(&apart, pc - 1, &past);
+ pc = past;
+ }
+ frame = apart;
+ if (how != STEP_NEXT) break;
+ }
+ if (top) {
+ Frame apart = frame;
+ unwind_calls(&apart, top);
+ frame = apart;
+ }
+ return finish(&frame, how, result);
+}
+
/* The executor's run of a block of a module with loops planned PLAN_BATCH:
* run's, those loops in batches (module-batch.c). Its own copy of the
* steps: in run's, a batch's way among them would cost every instruction
@@ -3987,7 +4412,7 @@ static __attribute__((noinline, aligned(64))) PureResult run_batches(const PureM
static __attribute__((aligned(64))) PureResult run(const PureModule *m, PureValue *values, PureBlock block, Event *ev, Slot *result) {
PureCompiled code = m->jit ? pure_jit_code(m->jit, block) : NULL;
if (code) return run_compiled(code, m, values, block, ev, result);
- if (m->batches) return run_batches(m, values, block, ev, result);
+ if (m->runner) return m->runner == 2 ? run_calls(m, values, block, ev, result) : run_batches(m, values, block, ev, result);
Slot stack[PURE_MODULE_STACK];
Loop loops[PURE_MODULE_NESTING];
Frame frame;
@@ -4246,6 +4671,9 @@ void pure_app_stop(PureApp *app) {
}
pure_retired_free(&app->retired);
for (unsigned i = 0; i < PURE_MODULE_HELD; ++i) free(app->held[i].data);
+ for (unsigned i = 0; i < app->calls.count; ++i) free(app->calls.blocks[i]);
+ free(app->calls.blocks), free(app->calls.sizes);
+ drop_spares(app);
drop_nested(app);
free(app->nested.list);
free(app->nested.shared);
@@ -4385,7 +4813,7 @@ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) {
* holds for it the default room, or the budget when that is larger. */
app->grown = app->spent = 0;
app->room = app->memory > PURE_GROWTH_DEFAULT ? app->memory : PURE_GROWTH_DEFAULT;
- for (unsigned i = 0; m->held && i < PURE_MODULE_HELD; ++i) app->held[i].size = 0;
+ for (unsigned i = 0; m->held && i < PURE_MODULE_HELD; ++i) app->held[i].size = 0, app->held[i].by_ref = false;
if (app->nested.count) unbind(app), drop_nested(app); /* a view's body bound some on the app itself */
Mark mark;
mark_event(app, &mark);
@@ -4734,7 +5162,7 @@ static PureResult evaluate(PureApp *app, uint32_t view, size_t row, Slot *out) {
if (!own_lists(app, m->view_lists[view])) return PURE_ALLOCATION;
app->grown = app->spent = 0;
app->room = app->memory > PURE_GROWTH_DEFAULT ? app->memory : PURE_GROWTH_DEFAULT;
- for (unsigned i = 0; m->held && i < PURE_MODULE_HELD; ++i) app->held[i].size = 0;
+ for (unsigned i = 0; m->held && i < PURE_MODULE_HELD; ++i) app->held[i].size = 0, app->held[i].by_ref = false;
unbind(app);
if (app->nested.count) drop_nested(app);
}
@@ -5429,6 +5857,7 @@ PURE_COLD PureResult pure_app_start(PureApp *app, const PureModule *module, Pure
PURE_COLD PureResult pure_app_replace(PureApp *app, const PureModule *module) {
unbind(app); /* lists are laid out again: none bound, the event's in records let go */
drop_nested(app);
+ drop_spares(app);
pure_app_finish(app);
if (app->open || app->pending_count) {
PureResult saved = pure_app_save(app); /* the burst belongs to the module that made it */
diff --git a/core/module.h b/core/module.h
index 52cd4d8..9ce354f 100644
--- a/core/module.h
+++ b/core/module.h
@@ -46,6 +46,7 @@ typedef enum {
PURE_OP_I64_BITS, PURE_OP_BYTE_AT, PURE_OP_BYTES_OF, PURE_OP_INDEX_AT,
PURE_OP_BYTES_SLICE, PURE_OP_BYTE, PURE_OP_BYTES_LOAD, PURE_OP_BYTES_STORE,
PURE_OP_LIST_VALUE, PURE_OP_LIST_BIND, PURE_OP_APPEND_IN, PURE_OP_NUM_PARSE,
+ PURE_OP_CALL, PURE_OP_RETURN, PURE_OP_CHAR_AT, PURE_OP_CHAR,
PURE_OP_COUNT
} PureOpcode;
/* A bool is its own type (docs/design/016-bool-and-i64.md), held as a
@@ -110,10 +111,31 @@ enum { PURE_MODULE_HELD = 32, PURE_HELD_TEXT = 16, PURE_HELD_APPEND = 1 << 8 };
typedef enum { PURE_FORMAT_PLAIN, PURE_FORMAT_DECIMAL, PURE_FORMAT_HEX, PURE_FORMAT_HEX_UPPER, PURE_FORMAT_BINARY,
PURE_FORMAT_OCTAL, PURE_FORMAT_FIXED } PureFormat;
enum { PURE_FORMAT_WIDTH = 8, PURE_FORMAT_ZEROS = 1 << 16, PURE_FORMAT_FORM = 17, PURE_FORMAT_DIGITS = 20 };
+/* 027's stage 2c: functions and characters. A function (PureFunction) is a
+ * block CALL runs (its argument the function's number): the block begins with
+ * the call's arguments as its stack, its parameters' types, and RETURN ends
+ * it with its results on top (the rest of its stack let go), which take the
+ * arguments' place on the caller's stack. Each call has its own stack and
+ * locals (0 as it begins), and its own of what the function's code names:
+ * values held by name (empty), an event's lists (empty) and registers (bound
+ * to none); the caller's are as they were when the call returns. A function
+ * runs as a handler's code does but for a message (no field, document or
+ * reply); a handler and the functions run CALL (not a view), and a walk around
+ * a call keeps to what the callee changes as it keeps to its own block's
+ * changes. Calls nest as deep as the event's room allows: each counts its
+ * frame's bytes there (MEMORY past it) and its block's length as work.
+ * Outside a function, LOOP and EACH take their operands from a stack that
+ * holds nothing else: a loop's block begins at the bottom of its run's stack.
+ * CHAR_AT gives the character at a byte position of a text (an i64: its code
+ * point), failing as LIMIT where no character begins there; CHAR makes the
+ * 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 };
/* 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) and an index's
- * list (bits 16-31); a handler uses a slot for one of them. A handler, the
+ * 0-7), what it holds (bits 8-15: 0 a number, 1 an index, 2 a bool, 3 an i64,
+ * 4 a list value, 027's 2c) and an index's list or a list's record (bits
+ * 16-31); a handler uses a slot for one of them. A handler, the
* walks in it and the folds in those read locals; the handler and its walks
* store them. */
enum { PURE_MODULE_LOCALS = 64 };
@@ -256,6 +278,17 @@ const char *pure_fault_name(PureFault);
typedef struct { union { double value; int64_t integer; }; uint32_t arg; uint8_t op, plan, shape, range; } PureOp;
typedef struct { uint32_t start, length; } PureBlock;
typedef struct { uint32_t event; PureBlock code; } PureHandler;
+/* A function's block and its parameters' and results' types, from first among the module's signature items: a record
+ * field's type (PureRecordType: a list's with its record) or PURE_SIGNATURE_BYTES. Set at load: the event's lists,
+ * registers and held values each call has its own of (a bit each), the lists not an event's it changes (or a function
+ * it calls does), and its frame: its stack's height at most, its locals and its loops. */
+typedef struct {
+ PureBlock code;
+ uint16_t first;
+ uint8_t params, results;
+ uint32_t lists, registers, held, changes;
+ uint8_t height, locals, nesting;
+} PureFunction;
typedef struct { char name[PURE_MODULE_NAME]; uint8_t type; uint16_t rows; uint32_t offset; } PureField;
typedef struct {
uint8_t kind;
@@ -315,6 +348,7 @@ typedef struct {
uint32_t batch_plan_count; /* in their loops' order */
struct PureJit *jit; /* its blocks compiled (module-jit.c, docs/design/021), or NULL */
bool batches; /* a loop of it is planned to run in batches (module-batch.c, docs/design/024) */
+ uint8_t runner; /* which executor runs its blocks: 0 run's own steps, 1 with batches, 2 with calls (module.c) */
bool held; /* it holds bytes or texts by name (BYTES_STORE): events and views empty them as they begin */
PureInputShape input[PURE_MODULE_INPUTS];
PureField field[PURE_MODULE_FIELDS];
@@ -347,6 +381,11 @@ typedef struct {
PureValue record_field_default[PURE_MODULE_RECORD_FIELDS]; /* a number (a bool's 0 or 1), an i64, a text constant's index
* or a list's record (LIST, its i64) */
bool record_nested[PURE_MODULE_RECORDS]; /* a record with a LIST field (set at load) */
+ uint16_t functions;
+ PureFunction function[PURE_MODULE_FUNCTIONS];
+ uint8_t signature_type[PURE_MODULE_SIGNATURES];
+ uint16_t signature_record[PURE_MODULE_SIGNATURES];
+ bool calls; /* it has functions: its blocks run in the executor that calls (module.c) */
} PureModule;
/* Whether a state of m is transient (PURE_STATE_TRANSIENT). */
@@ -540,11 +579,16 @@ typedef struct {
PureRetired retired; /* list items let go since it was made */
bool reading; /* a reader: its states are a snapshot's */
/* The values an event or a view's body holds by name (BYTES_STORE): emptied as it begins. */
- struct { unsigned char *data; size_t size, capacity; } held[PURE_MODULE_HELD];
+ /* A value of the event's payload, arguments or constants is held as it is (by_ref: ref, nothing copied). */
+ struct { unsigned char *data; size_t size, capacity; PureBytes ref; bool by_ref; } held[PURE_MODULE_HELD];
/* The lists in records an event or a view's body made (LIST fields: a value is a number here, 1 on), how many
* values share each, and the bound lists' own (a register's list while another is bound to it). */
struct { PureList **list; uint32_t *shared; size_t count, capacity; } nested;
PureList *unbound[PURE_MODULE_STATES];
+ /* Calls (027's 2c): the frames of those open, in blocks that never move (code points into a frame), the block in
+ * use and how much of it; and the event's lists calls let go, emptied, each state's for its next calls. */
+ struct { unsigned char **blocks; size_t *sizes; unsigned count, at; size_t used; } calls;
+ struct { PureList **lists; unsigned count, capacity; } spare[PURE_MODULE_STATES];
} PureApp;
PureResult pure_app_start(PureApp *, const PureModule *, PureData data);
diff --git a/core/test-module.c b/core/test-module.c
index f45cada..893aeab 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -62,12 +62,18 @@ typedef struct { const char *name; const RF *fields; size_t nfields; } R;
/* A further view: a table (type 2, start = list state) or a column of the
* view numbered table - 1. */
typedef struct { const char *name; uint32_t type, table, start, length; } W;
+/* A function (027's 2c): its block, and its parameters' then its results' types (a record field's: 0 a number, 1 a
+ * text, 2 a bool, 3 an i64, 4 a list whose record is beside it; 5 bytes). */
+typedef struct { uint32_t start, length; uint8_t params, results, types[16]; uint16_t records[16]; } FN;
typedef struct {
const S *s; size_t ns; const I *in; size_t ni; const V *v; size_t nv;
const H *h; size_t nh; const O *o; size_t no; const char *const *c; size_t nc;
const T *t; size_t nt; const char *const *k; size_t nk; const V *tv; size_t ntv;
const R *r; size_t nr; const W *w; size_t nw;
} Spec;
+/* The functions the next build writes (load_functions sets them a moment). */
+static const FN *spec_fn;
+static size_t spec_nfn;
static unsigned char *put32(unsigned char *p, uint32_t v) {
for (unsigned i = 0; i < 4; ++i) p[i] = (unsigned char)(v >> (8 * i));
@@ -125,7 +131,7 @@ static size_t build(unsigned char *out, Spec spec) {
put32(p + 16, (uint32_t)spec.no);
put32(p + 20, (uint32_t)used);
p[24] = (unsigned char)fields; p[26] = (unsigned char)spec.nc; p[28] = (unsigned char)spec.nk;
- p[30] = (unsigned char)spec.nr; p[36] = (unsigned char)record_fields;
+ p[30] = (unsigned char)spec.nr; p[36] = (unsigned char)record_fields; p[38] = (unsigned char)spec_nfn;
put32(p + 32, (uint32_t)blob_used);
p += 40;
size_t n = 0;
@@ -203,6 +209,16 @@ static size_t build(unsigned char *out, Spec spec) {
p = put32(p, w->length);
}
for (size_t i = 0; i < spec.nh; ++i) { p = put32(p, spec.h[i].event); p = put32(p, spec.h[i].start); p = put32(p, spec.h[i].length); }
+ for (size_t i = 0; i < spec_nfn; ++i) {
+ p = put32(p, spec_fn[i].start);
+ p = put32(p, spec_fn[i].length);
+ *p++ = spec_fn[i].params; *p++ = spec_fn[i].results; *p++ = 0; *p++ = 0;
+ }
+ for (size_t i = 0; i < spec_nfn; ++i)
+ for (unsigned k = 0; k < (unsigned)spec_fn[i].params + spec_fn[i].results; ++k) {
+ *p++ = spec_fn[i].types[k]; *p++ = 0;
+ *p++ = (unsigned char)spec_fn[i].records[k]; *p++ = (unsigned char)(spec_fn[i].records[k] >> 8);
+ }
for (size_t i = 0; i < spec.no; ++i) {
*p++ = spec.o[i].op; *p++ = 0; *p++ = 0; *p++ = 0;
p = put32(p, spec.o[i].arg);
@@ -222,6 +238,13 @@ static PureResult load(Spec spec, PureModule *out) {
return pure_module_load(bytes, size, out, error, sizeof(error));
}
+static PureResult load_functions(Spec spec, const FN *fns, size_t count, PureModule *out) {
+ spec_fn = fns, spec_nfn = count;
+ PureResult result = load(spec, out);
+ spec_fn = NULL, spec_nfn = 0;
+ return result;
+}
+
#define LEN(a) (sizeof(a) / sizeof(*(a)))
#define SPEC(s, in, v, h, o) ((Spec){s, LEN(s), in, LEN(in), v, LEN(v), h, LEN(h), o, LEN(o), NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0})
@@ -6318,6 +6341,168 @@ static void lists_in_records(void) {
printf("{\"lists_in_records\":{\"rounds\":3,\"refused\":%zu}}\n", LEN(refused) + 1);
}
+/* Functions and characters (027's 2c): a function calls itself; each call has its own locals, lists, registers and
+ * held values (a caller's as they were when it returns); a walk goes on around a call; calls count as work and memory;
+ * the verifier keeps to a function's types and to what a walk around a call keeps to. */
+enum { CALL = PURE_OP_CALL, RET = PURE_OP_RETURN, CHAT = PURE_OP_CHAR_AT, CHAR_ = PURE_OP_CHAR, FN_TEXT = 1, FN_I64 = 3,
+ FN_LIST = 4 };
+#define I64_LOCAL(slot) ((uint32_t)(slot) | 3u << 8)
+#define WALK_OF(list, length) ((uint32_t)(list) << 8 | (uint32_t)(length) << 16)
+#define LIST_LOCAL(slot, record) ((uint32_t)(slot) | 4u << 8 | (uint32_t)(record) << 16)
+static void functions_and_characters(void) {
+ static const S nums[] = {{"n", 0}};
+ static const RF leaf_fields[] = {{"v", 3, 0}};
+ static const RF node_fields[] = {{"v", 3, 0}, {"kids", 4, 1}}; /* kids: a list of Node */
+ static const R records[] = {{"Leaf", leaf_fields, 1}, {"Node", node_fields, 2}};
+ enum { IV = 1, SAID, OWN, NODES, KIDS, R_, STORED, NONE };
+ static const T others[] = {{"i", PURE_STATE_I64, 0}, {"said", 1, 0}, {"let.own", 3 | (uint32_t)PURE_LIST_ROWS << 8, 0},
+ {"let.nodes", 3 | (uint32_t)PURE_LIST_ROWS << 8, 1}, {"let.kids", 3 | (uint32_t)PURE_LIST_ROWS << 8, 1},
+ {"let.r", 3 | (uint32_t)(PURE_LIST_ROWS | PURE_LIST_BOUND) << 8, 1}, {"stored", 3, 0},
+ {"let.none", 3 | (uint32_t)PURE_LIST_ROWS << 8, 1}};
+ static const char *const constants[] = {"", "a\xc3\xa9\xe2\x82\xac\xf0\x9f\x98\x80"}; /* a รฉ โ‚ฌ ๐Ÿ˜€ */
+ static const I inputs[] = {KEY("fib", "KeyF"), KEY("tree", "KeyT"), KEY("own", "KeyO"), KEY("held", "KeyH"),
+ KEY("chars", "KeyC"), KEY("deep", "KeyD")};
+ enum { LS_ = PURE_OP_LOCAL_STORE, LL_ = PURE_OP_LOCAL_LOAD };
+ const O code[] = {
+ /* 0 fib(n): n < 2 ? n : fib(n - 1) + fib(n - 2) */
+ {LS_, I64_LOCAL(0), 0}, {LL_, I64_LOCAL(0), 0}, IC(2), {ICMP, PURE_LESS, 0}, {JZ, 7, 0},
+ {LL_, I64_LOCAL(0), 0}, {RET, 0, 0},
+ {LL_, I64_LOCAL(0), 0}, IC(1), {ISUB, 0, 0}, {CALL, 0, 0}, {LL_, I64_LOCAL(0), 0}, IC(2), {ISUB, 0, 0}, {CALL, 0, 0},
+ {IADD, 0, 0}, {RET, 0, 0},
+ /* 17 sum(xs): the v of every node in xs and under it, by a walk of its register that calls itself */
+ {BIND, R_, 0}, IC(0), {LS_, I64_LOCAL(0), 0}, {EACH, WALK_OF(R_, 9), 0},
+ {LL_, I64_LOCAL(0), 0}, {ROW, R_, 0}, {IG, ITEM(R_, 0), 0}, {IADD, 0, 0}, {ROW, R_, 0}, {IG, ITEM(R_, 1), 0}, {CALL, 1, 0},
+ {IADD, 0, 0}, {LS_, I64_LOCAL(0), 0},
+ {LL_, I64_LOCAL(0), 0}, {RET, 1, 0},
+ /* 32 own(n): own.append(n); n == 0 ? own.count : own(n - 1) * 10 + own.count (each call's own list holds one) */
+ {LS_, I64_LOCAL(0), 0}, {LL_, I64_LOCAL(0), 0}, {LA, OWN, 0}, {LL_, I64_LOCAL(0), 0}, IC(0), {ICMP, PURE_EQUAL, 0},
+ {JZ, 42, 0}, {LC, OWN, 0}, {IFROM, 0, 0}, {RET, 2, 0},
+ {LL_, I64_LOCAL(0), 0}, IC(1), {ISUB, 0, 0}, {CALL, 2, 0}, IC(10), {IMUL, 0, 0}, {LC, OWN, 0}, {IFROM, 0, 0}, {IADD, 0, 0},
+ {RET, 2, 0},
+ /* 52 held(t, k): s = t + "k"; k == 0 ? s : held(s, k - 1) + s (each call's s its own) */
+ {LS_, I64_LOCAL(0), 0}, {LL_, I64_LOCAL(0), 0}, {NTX, 2, 0}, {CAT, 0, 0}, {HST, 16, 0},
+ {LL_, I64_LOCAL(0), 0}, IC(0), {ICMP, PURE_EQUAL, 0}, {JZ, 63, 0}, {HLD, 16, 0}, {RET, 3, 0},
+ {HLD, 16, 0}, {LL_, I64_LOCAL(0), 0}, IC(1), {ISUB, 0, 0}, {CALL, 3, 0}, {HLD, 16, 0}, {CAT, 0, 0}, {RET, 3, 0},
+ /* 71 deep(n): deep(n + 1), never returning */
+ IC(1), {IADD, 0, 0}, {CALL, 4, 0}, {RET, 4, 0},
+ /* 75 handler fib: i = fib(20) */
+ IC(20), {CALL, 0, 0}, {ST, IV, 0},
+ /* 78 handler tree: kids = [4, 1 [2 [4], 3]]; i = sum(kids) + r[0].v, r bound by the handler around the calls */
+ IC(4), {LV, NONE, 0}, {LA, KIDS, 0}, IC(2), {LV, KIDS, 0}, {LA, NODES, 0}, IC(3), {LV, NONE, 0}, {LA, NODES, 0},
+ {LV, NONE, 0}, {CALL, 1, 0}, {ST, IV, 0}, /* sum of nothing: 0 */
+ IC(1), {LV, NODES, 0}, {LA, KIDS, 0}, {LV, KIDS, 0}, {BIND, R_, 0},
+ {LV, KIDS, 0}, {LS_, LIST_LOCAL(0, 1), 0}, {LL_, LIST_LOCAL(0, 1), 0}, {CALL, 1, 0}, /* by way of a local */
+ {XF, R_, 0}, {IG, ITEM(R_, 0), 0}, {IADD, 0, 0}, {ST, IV, 0}, /* 14 + 4 */
+ /* 103 handler own: own = [7]; i = own(3); n = own.count */
+ IC(7), {LA, OWN, 0}, IC(3), {CALL, 2, 0}, {ST, IV, 0}, {LC, OWN, 0}, {ST, 0, 0},
+ /* 110 handler held: said = held("", 2) */
+ {TK, 0, 0}, IC(2), {CALL, 3, 0}, {PURE_OP_TEXT_SET, SAID, 0},
+ /* 114 handler chars: i = the code points at 0, 1, 3 and 6, packed; said = char(...) joined, the constant again */
+ {TK, 1, 0}, IC(0), {CHAT, 0, 0}, {TK, 1, 0}, IC(1), {CHAT, 0, 0}, {IADD, 0, 0}, {TK, 1, 0}, IC(3), {CHAT, 0, 0},
+ {IADD, 0, 0}, {TK, 1, 0}, IC(6), {CHAT, 0, 0}, {IADD, 0, 0}, {ST, IV, 0},
+ IC(0x61), {CHAR_, 0, 0}, IC(0xE9), {CHAR_, 0, 0}, {CAT, 0, 0}, IC(0x20AC), {CHAR_, 0, 0}, {CAT, 0, 0}, IC(0x1F600), {CHAR_, 0, 0},
+ {CAT, 0, 0}, {PURE_OP_TEXT_SET, SAID, 0},
+ /* 142 handler deep: own = [5]; deep(0) */
+ IC(5), {LA, OWN, 0}, IC(0), {CALL, 4, 0}, {ST, IV, 0},
+ };
+ static const FN fns[] = {{0, 17, 1, 1, {FN_I64, FN_I64}, {0}}, {17, 15, 1, 1, {FN_LIST, FN_I64}, {1, 0}},
+ {32, 20, 1, 1, {FN_I64, FN_I64}, {0}}, {52, 19, 2, 1, {FN_TEXT, FN_I64, FN_TEXT}, {0}},
+ {71, 4, 1, 1, {FN_I64, FN_I64}, {0}}};
+ static const H handlers[] = {{1, 75, 3}, {3, 78, 25}, {5, 103, 7}, {7, 110, 4}, {9, 114, 28}, {11, 142, 5}};
+ CHECK(LEN(code) == 147);
+ Spec spec = {nums, 1, inputs, LEN(inputs), NULL, 0, handlers, LEN(handlers), code, LEN(code), NULL, 0,
+ others, LEN(others), constants, LEN(constants), NULL, 0, records, 2, NULL, 0};
+ if (load_functions(spec, fns, LEN(fns), &module_b) != PURE_OK) { fprintf(stderr, "functions: %s\n", error); abort(); }
+ CHECK(module_b.function[2].lists == UINT32_C(1) << OWN && module_b.function[1].registers == UINT32_C(1) << R_ &&
+ module_b.function[3].held == UINT32_C(1) << 16 && module_b.runner == 2);
+ PureApp app;
+ CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK);
+ for (int round = 0; round < 3; ++round) { /* the frames' room used again, each event alike */
+ CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 6765);
+ CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 18);
+ CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 1111 && app.values[0].num == 1);
+ CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && !strcmp(state_text(&app, SAID), "210212"));
+ CHECK(pure_app_input(&app, 4, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 0x61 + 0xE9 + 0x20AC + 0x1F600 &&
+ !strcmp(state_text(&app, SAID), constants[1]));
+ }
+ /* A call without end: the room runs out (MEMORY) where the work does not; with a smaller work budget, WORK. Either
+ * way nothing changed, and the lists are the app's own again. */
+ uint64_t before = (uint64_t)app.values[IV].i64;
+ CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_BUDGET && app.fault == PURE_FAULT_MEMORY);
+ CHECK((uint64_t)app.values[IV].i64 == before && pure_list_count(app.lists[OWN]) == 0 && app.calls.at == 0 && !app.calls.used);
+ pure_app_work(&app, 100000);
+ CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_LIMIT && app.fault == PURE_FAULT_WORK);
+ CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 1111 && app.values[0].num == 1);
+ pure_app_stop(&app);
+ /* Characters that fail: within one (รฉ at 2), past the end, a surrogate, past U+10FFFF, below 0. */
+ const O chars_bad[][3] = {{{TK, 1, 0}, IC(2), {CHAT, 0, 0}}, {{TK, 1, 0}, IC(10), {CHAT, 0, 0}},
+ {IC(0xD800), {CHAR_, 0, 0}, {PURE_OP_TEXT_SET, SAID, 0}}, {IC(0x110000), {CHAR_, 0, 0}, {PURE_OP_TEXT_SET, SAID, 0}},
+ {IC(-1), {CHAR_, 0, 0}, {PURE_OP_TEXT_SET, SAID, 0}}};
+ static const PureFault chars_fault[] = {PURE_FAULT_LIMIT, PURE_FAULT_LIMIT, PURE_FAULT_ENCODING, PURE_FAULT_LIMIT, PURE_FAULT_LIMIT};
+ for (size_t k = 0; k < LEN(chars_bad); ++k) {
+ O one[4] = {chars_bad[k][0], chars_bad[k][1], chars_bad[k][2], {ST, IV, 0}};
+ H go = {1, 0, chars_bad[k][2].op == CHAT ? 4 : 3};
+ Spec bad = {nums, 1, inputs, 1, NULL, 0, &go, 1, one, LEN(one), NULL, 0, others, LEN(others), constants, LEN(constants),
+ NULL, 0, records, 2, NULL, 0};
+ if (load(bad, &module_a) != PURE_OK) { fprintf(stderr, "chars %zu: %s\n", k, error); abort(); }
+ CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
+ CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) != PURE_OK && app.fault == chars_fault[k]);
+ pure_app_stop(&app);
+ }
+ /* Refused: no return; a result of another type; code after a return; RETURN outside a function; a call with an
+ * argument of another type, in a view, or changing a list a walk around it walks; a message's field in a function;
+ * a loop above values; a signature's type or record. */
+ const struct { O code[6]; uint32_t count; FN fn; const char *why; } refused[] = {
+ {{IC(1), {ST, IV, 0}}, 2, {0, 2, 0, 0, {0}, {0}}, "function ends without return"},
+ {{{C, 0, 1}, {RET, 0, 0}}, 2, {0, 2, 0, 1, {FN_I64}, {0}}, "operand type"},
+ {{{RET, 0, 0}, {C, 0, 0}}, 2, {0, 2, 0, 0, {0}, {0}}, "unreachable instruction"},
+ {{{C, 0, 1}, {CALL, 0, 0}, {RET, 0, 0}}, 3, {0, 3, 1, 0, {FN_I64}, {0}}, "operand type"},
+ {{IC(1), {LA, STORED, 0}, {RET, 0, 0}}, 3, {0, 3, 0, 0, {0}, {0}}, NULL},
+ {{{PURE_OP_FIELD, 0, 0}, {RET, 0, 0}}, 2, {0, 2, 0, 0, {0}, {0}}, "message operation outside a message handler"},
+ {{IC(0), IC(1), IC(0), IC(1), {PURE_OP_LOOP, PURE_LOOP_INTEGER, 0}, {RET, 0, 0}}, 6,
+ {0, 6, 0, 0, {0}, {0}}, "loop above values"},
+ {{{RET, 0, 0}}, 1, {0, 1, 0, 0, {6}, {0}}, "function signature"},
+ };
+ for (size_t k = 0; k < LEN(refused); ++k) {
+ FN fn = refused[k].fn;
+ if (k == 7) fn.params = 1;
+ O all[12];
+ uint32_t n = refused[k].count;
+ memcpy(all, refused[k].code, n * sizeof(O));
+ H go = {1, n, 1};
+ all[n] = (O){C, 0, 0}; /* a handler that stores nothing: C, ST */
+ all[n + 1] = (O){ST, 0, 0};
+ go.length = 2;
+ if (k == 4) { /* the call in a walk of the list it appends to */
+ all[n] = (O){EACH, WALK_OF(STORED, 1), 0}, all[n + 1] = (O){CALL, 0, 0};
+ }
+ Spec bad = {nums, 1, inputs, 1, NULL, 0, &go, 1, all, n + 2, NULL, 0, others, LEN(others), constants, LEN(constants),
+ NULL, 0, records, 2, NULL, 0};
+ const char *why = refused[k].why ? refused[k].why : "call that changes a list walked";
+ if (load_functions(bad, &fn, 1, &module_a) == PURE_OK || !strstr(error, why)) {
+ fprintf(stderr, "functions: refused %zu: %s\n", k, error);
+ abort();
+ }
+ }
+ static const O in_view[] = {{CALL, 0, 0}, {RET, 0, 0}, {RET, 0, 0}};
+ static const V call_view[] = {{"v", 0, 1}};
+ static const FN nothing = {1, 1, 0, 0, {0}, {0}};
+ Spec viewed = {nums, 1, inputs, 1, call_view, 1, NULL, 0, in_view, 2, NULL, 0, others, LEN(others), constants, LEN(constants),
+ NULL, 0, records, 2, NULL, 0};
+ CHECK(load_functions(viewed, &nothing, 1, &module_a) != PURE_OK && strstr(error, "call outside a handler or a function"));
+ static const H returning = {1, 2, 1};
+ Spec handler_return = {nums, 1, inputs, 1, NULL, 0, &returning, 1, in_view, 3, NULL, 0, others,
+ LEN(others), constants, LEN(constants), NULL, 0, records, 2, NULL, 0};
+ CHECK(load_functions(handler_return, &nothing, 1, &module_a) != PURE_OK && strstr(error, "return outside a function"));
+ /* A handler's loop above values: its block would begin above them (once past the run's stack). */
+ const O above[] = {IC(0), IC(0), IC(1), {PURE_OP_LOOP, PURE_LOOP_INTEGER, 0}, {ST, IV, 0}};
+ static const H whole = {1, 0, 5};
+ Spec looped = {nums, 1, inputs, 1, NULL, 0, &whole, 1, above, LEN(above), NULL, 0, others, LEN(others), constants,
+ LEN(constants), NULL, 0, records, 2, NULL, 0};
+ CHECK(load(looped, &module_a) != PURE_OK && strstr(error, "loop above values"));
+ printf("{\"functions_and_characters\":{\"rounds\":3,\"refused\":%zu}}\n", LEN(refused) + 3);
+}
+
/* A timer's tick sees the time the host set; floor and a floored modulo
* take it apart. now is for handlers only. */
enum { NOW = PURE_OP_NOW, FLOOR = PURE_OP_FLOOR, MOD = PURE_OP_MOD };
@@ -6694,6 +6879,7 @@ int main(void) {
bits_and_bytes();
formats_and_held();
lists_in_records();
+ functions_and_characters();
memory_budget();
work_budget();
fold_mutations();
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 ํ…์ŠคํŠธ๋„ ์กฐ๊ฐ ํŠธ๋ฆฌ๋กœ ํŽธ์ง‘ํ•œ๋‹ค. ์ปค์„œ๋Š” ๋ฐ”์ดํŠธ
์œ„์น˜์ด๋ฉฐ ํ•ญ์ƒ ๊ธ€์ž ๊ฒฝ๊ณ„์— ์žˆ๋‹ค. ๋„ฃ๊ธฐ๋Š” ๊ทธ ์œ„์น˜ ์ดํ›„์˜ ์ปค์„œ๋ฅผ ๋’ค๋กœ(์‚ฝ์ž… ์œ„์น˜์˜ ์ปค์„œ ํฌํ•จ, ๋งˆํฌ๋Š” ์ œ์ž๋ฆฌ) ๋ฐ€๊ณ , ์ง€์šฐ๊ธฐ๋Š” ๋ฒ”์œ„