The runtime's twin emitters share their bodies, and six dead symbols go
This commit is contained in:
commit
26439dc22e
@ -653,8 +653,6 @@ let stamp_of prog =
|
|||||||
| st -> Printf.sprintf "%s:%d:%f" path st.Unix.st_size st.Unix.st_mtime
|
| st -> Printf.sprintf "%s:%d:%f" path st.Unix.st_size st.Unix.st_mtime
|
||||||
| exception Unix.Unix_error _ -> path
|
| exception Unix.Unix_error _ -> path
|
||||||
|
|
||||||
let clang_stamp = lazy (stamp_of clang)
|
|
||||||
|
|
||||||
(* Compile one C translation unit to an object file, reusing a cached one when
|
(* Compile one C translation unit to an object file, reusing a cached one when
|
||||||
the source text, the compiler and the flags are all unchanged. The key has
|
the source text, the compiler and the flags are all unchanged. The key has
|
||||||
to carry [opt] and [target]: the acceptance table builds the same programs
|
to carry [opt] and [target]: the acceptance table builds the same programs
|
||||||
|
|||||||
12
lib/dev.ml
12
lib/dev.ml
@ -3395,17 +3395,17 @@ let ignore_sigpipe () =
|
|||||||
and this is a *daemon* whose client died.
|
and this is a *daemon* whose client died.
|
||||||
|
|
||||||
What makes this answerable without a new protocol is that the connection is
|
What makes this answerable without a new protocol is that the connection is
|
||||||
per session and not per request. [emacs/flan-dev.el] opens one
|
per session and not per request. [emacs/flan.el] opens one
|
||||||
[make-network-process] in [flan-dev--open], keeps it in
|
[make-network-process] in [flan--open], keeps it in [flan--connection], and
|
||||||
[flan-dev--connection], and reopens only when the process is dead; every
|
reopens only when the process is dead; every site that tears it down
|
||||||
site that tears it down deliberately sends [close] first. [serve] mirrors
|
deliberately sends [close] first. [serve] mirrors
|
||||||
that shape — it loops on the one fd until EOF — so while an editor is
|
that shape — it loops on the one fd until EOF — so while an editor is
|
||||||
attached this loop is not even cycling. An editor left open and idle
|
attached this loop is not even cycling. An editor left open and idle
|
||||||
overnight is therefore *attached* overnight, and the grace below can never
|
overnight is therefore *attached* overnight, and the grace below can never
|
||||||
accumulate under it. That is structural, not a number chosen to outlast
|
accumulate under it. That is structural, not a number chosen to outlast
|
||||||
human patience, and it is why no PID handshake and no heartbeat op is
|
human patience, and it is why no PID handshake and no heartbeat op is
|
||||||
needed: holding the socket open is the heartbeat, and a client that only
|
needed: holding the socket open is the heartbeat, and a client that only
|
||||||
ever polls — or one that has [flan-dev-poll-interval] set to nil and polls
|
ever polls — or one that has [flan-poll-interval] set to nil and polls
|
||||||
never — is indistinguishable from any other attached client.
|
never — is indistinguishable from any other attached client.
|
||||||
|
|
||||||
So the signal is client *absence*, and the only absence this is allowed to
|
So the signal is client *absence*, and the only absence this is allowed to
|
||||||
@ -3456,7 +3456,7 @@ let client_grace () =
|
|||||||
window would kill the session, which is the bug this whole change exists to
|
window would kill the session, which is the bug this whole change exists to
|
||||||
remove, reintroduced one line further out. The grace does not reopen that
|
remove, reintroduced one line further out. The grace does not reopen that
|
||||||
hole: it reads the client, not the program, and all of an Emacs's windows
|
hole: it reads the client, not the program, and all of an Emacs's windows
|
||||||
share the one [flan-dev--connection], so closing one of them changes
|
share the one [flan--connection], so closing one of them changes
|
||||||
nothing this loop can see. *)
|
nothing this loop can see. *)
|
||||||
let accept_loop ?grace t ls =
|
let accept_loop ?grace t ls =
|
||||||
let grace = match grace with Some g -> g | None -> client_grace () in
|
let grace = match grace with Some g -> g | None -> client_grace () in
|
||||||
|
|||||||
@ -57,15 +57,11 @@ let tag_of_int = function
|
|||||||
dynload_stubs.c, one field at a time. Everything allocated here is owned by
|
dynload_stubs.c, one field at a time. Everything allocated here is owned by
|
||||||
[Dynload] and released together after the call. *)
|
[Dynload] and released together after the call. *)
|
||||||
|
|
||||||
let rec marshal (f : Form.t) : Dynload.addr =
|
|
||||||
let p = Dynload.take form_size in
|
|
||||||
write p f;
|
|
||||||
p
|
|
||||||
|
|
||||||
(* Into an existing 24 bytes, which is what an argument array needs: the macro
|
(* Into an existing 24 bytes, which is what an argument array needs: the macro
|
||||||
takes a [Form] slice, and a slice is contiguous elements and not an array of
|
takes a [Form] slice, and a slice is contiguous elements and not an array of
|
||||||
pointers. *)
|
pointers — so this writes *into* memory the caller took, and every caller
|
||||||
and write p (f : Form.t) =
|
here takes it as part of an array. *)
|
||||||
|
let rec write p (f : Form.t) =
|
||||||
let tag t = Dynload.poke_i32 p 0 (tag_int t) in
|
let tag t = Dynload.poke_i32 p 0 (tag_int t) in
|
||||||
let str t s =
|
let str t s =
|
||||||
tag t;
|
tag t;
|
||||||
|
|||||||
@ -656,13 +656,6 @@ let elem_ty loc (t : Types.t) =
|
|||||||
| Types.String -> Types.Int Types.U8
|
| Types.String -> Types.Int Types.U8
|
||||||
| t -> at loc "indexing %s is not in the JS dialect" (Types.to_string t)
|
| t -> at loc "indexing %s is not in the JS dialect" (Types.to_string t)
|
||||||
|
|
||||||
let is_bytes (t : Types.t) =
|
|
||||||
match t with
|
|
||||||
| Types.String -> true
|
|
||||||
| Types.Slice (Types.Int Types.U8) | Types.Array (_, Types.Int Types.U8) ->
|
|
||||||
true
|
|
||||||
| _ -> false
|
|
||||||
|
|
||||||
let rec value f (e : Tast.expr) : string =
|
let rec value f (e : Tast.expr) : string =
|
||||||
refuse_ty e.Tast.loc e.Tast.ty;
|
refuse_ty e.Tast.loc e.Tast.ty;
|
||||||
match e.Tast.e with
|
match e.Tast.e with
|
||||||
|
|||||||
@ -203,80 +203,109 @@ void flan_dev_emit(const uint8_t *bytes, int64_t len) {
|
|||||||
result_len += n;
|
result_len += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void emit_cstr(const char *s) {
|
/* ── Rendering a scalar, into whichever buffer is being written ──────── */
|
||||||
flan_dev_emit((const uint8_t *)s, (int64_t)strlen(s));
|
|
||||||
|
/* This file has two buffers a rendered value can go into: the result buffer
|
||||||
|
* above, which an evaluated expression's rendering streams into for the REPL
|
||||||
|
* to read back, and the current watch slot further down. What they hold is
|
||||||
|
* the same four renderings — an i64, a u64, an f64, a quoted string — and the
|
||||||
|
* only thing that differed was which of [flan_dev_emit] and
|
||||||
|
* [flan_dev_watch_emit] the bytes went to. So that is the parameter, and the
|
||||||
|
* eight exported entry points are eight one-line calls into these four.
|
||||||
|
*
|
||||||
|
* The exports stay eight. The compiler emits the result four by name — see
|
||||||
|
* [Session.externs] — and the watch four are exported C, which a program can
|
||||||
|
* reach through declare-c and which the [(watch ...)] arm described further
|
||||||
|
* down would be pointed at. They are ABI, and ABI does not collapse just
|
||||||
|
* because the bodies did. */
|
||||||
|
typedef void (*sink)(const uint8_t *, int64_t);
|
||||||
|
|
||||||
|
static void put(sink out, const char *s) {
|
||||||
|
out((const uint8_t *)s, (int64_t)strlen(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Rendered in C so that u64 is not a lie: the language's own i64->bytes is
|
/* Rendered in C so that u64 is not a lie: the language's own i64->bytes is
|
||||||
* signed, and anything past 2^63 would come back negative. */
|
* signed, and anything past 2^63 would come back negative. */
|
||||||
void flan_dev_emit_u64(uint64_t x) {
|
static void put_u64(sink out, uint64_t x) {
|
||||||
char buf[32];
|
char buf[32];
|
||||||
snprintf(buf, sizeof buf, "%llu", (unsigned long long)x);
|
snprintf(buf, sizeof buf, "%llu", (unsigned long long)x);
|
||||||
emit_cstr(buf);
|
put(out, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
void flan_dev_emit_i64(int64_t x) {
|
static void put_i64(sink out, int64_t x) {
|
||||||
char buf[32];
|
char buf[32];
|
||||||
snprintf(buf, sizeof buf, "%lld", (long long)x);
|
snprintf(buf, sizeof buf, "%lld", (long long)x);
|
||||||
emit_cstr(buf);
|
put(out, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Unsigned NaN, for flan_f64_to_bytes's reason and one of its own: the REPL
|
/* Unsigned NaN, and the rule is flan_rt.c's rather than a second statement of
|
||||||
* and println must not disagree about what a value looks like, which is the
|
* it: [flan_f64_format] is what println renders through, and the REPL, a watch
|
||||||
* rule the escape table below is already held to. A NaN's sign bit is decided
|
* row and println must not disagree about what one value looks like. A NaN's
|
||||||
* by whether the value was folded or computed, so showing it makes the printed
|
* sign bit is decided by whether the value was folded or computed, so showing
|
||||||
* form depend on the backend and the optimisation level rather than on the
|
* it would make the printed form depend on the backend and the optimisation
|
||||||
* number. */
|
* level rather than on the number. */
|
||||||
void flan_dev_emit_f64(double x) {
|
extern int flan_f64_format(double x, char *buf, size_t cap);
|
||||||
|
|
||||||
|
static void put_f64(sink out, double x) {
|
||||||
char buf[64];
|
char buf[64];
|
||||||
if (x != x) snprintf(buf, sizeof buf, "nan");
|
flan_f64_format(x, buf, sizeof buf);
|
||||||
else snprintf(buf, sizeof buf, "%g", x);
|
put(out, buf);
|
||||||
emit_cstr(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Quoted and escaped, in C, because doing it in the generated IR would be a
|
/* Quoted and escaped, in C, because doing it in the generated IR would be a
|
||||||
* loop per string and the language has no allocator to build the result in.
|
* loop per string and the language has no allocator to build the result in.
|
||||||
* A string whose content is not escaped does not round-trip and reads as a
|
* A string whose content is not escaped does not round-trip and reads as a
|
||||||
* framing bug rather than as the value it is. */
|
* framing bug rather than as the value it is — and a newline in a watched
|
||||||
void flan_dev_emit_str(const uint8_t *bytes, int64_t len) {
|
* string would become a second row of the table on the wire.
|
||||||
|
*
|
||||||
|
* The table is flan_rt.c's [flan_escape_char], which is also what println's
|
||||||
|
* [flan_escape_bytes] escapes through. One table, three framings of it. */
|
||||||
|
extern int flan_escape_char(unsigned char c, char *out);
|
||||||
|
|
||||||
|
static void put_str(sink out, const uint8_t *bytes, int64_t len) {
|
||||||
size_t n = len < 0 ? 0 : (size_t)len;
|
size_t n = len < 0 ? 0 : (size_t)len;
|
||||||
emit_cstr("\"");
|
put(out, "\"");
|
||||||
for (size_t i = 0; i < n; i++) {
|
for (size_t i = 0; i < n; i++) {
|
||||||
unsigned char c = bytes[i];
|
char e[4];
|
||||||
switch (c) {
|
int k = flan_escape_char(bytes[i], e);
|
||||||
case '"': emit_cstr("\\\""); break;
|
out((const uint8_t *)e, (int64_t)k);
|
||||||
case '\\': emit_cstr("\\\\"); break;
|
|
||||||
case '\n': emit_cstr("\\n"); break;
|
|
||||||
case '\t': emit_cstr("\\t"); break;
|
|
||||||
case '\r': emit_cstr("\\r"); break;
|
|
||||||
default:
|
|
||||||
if (c < 0x20) {
|
|
||||||
char buf[8];
|
|
||||||
snprintf(buf, sizeof buf, "\\x%02x", c);
|
|
||||||
emit_cstr(buf);
|
|
||||||
} else {
|
|
||||||
flan_dev_emit(&c, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
emit_cstr("\"");
|
put(out, "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Closing a value: the ellipsis a full buffer earns, and the counter back to
|
||||||
|
* even. Shared by [flan_dev_result_end] and [flan_dev_watch_end], which is
|
||||||
|
* the same seqlock twice over two different buffers.
|
||||||
|
*
|
||||||
|
* Room for the ellipsis is made rather than assumed: the buffer is full by
|
||||||
|
* definition when [full] is set.
|
||||||
|
*
|
||||||
|
* The counter goes last, and back to even, so a reader that sees the new
|
||||||
|
* generation sees the whole value. [| 1] first for the same reason [begin]
|
||||||
|
* sets rather than increments: this must land on an even count whatever state
|
||||||
|
* an abandoned write left behind. */
|
||||||
|
static void end_value(char *buf, size_t *len, size_t cap, int full,
|
||||||
|
uint64_t *gen) {
|
||||||
|
if (full) {
|
||||||
|
const char *ell = "...";
|
||||||
|
size_t k = strlen(ell);
|
||||||
|
if (*len > cap - k) *len = cap - k;
|
||||||
|
memcpy(buf + *len, ell, k);
|
||||||
|
*len += k;
|
||||||
|
}
|
||||||
|
__atomic_store_n(gen, (*gen | 1) + 1, __ATOMIC_RELEASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void flan_dev_emit_u64(uint64_t x) { put_u64(flan_dev_emit, x); }
|
||||||
|
void flan_dev_emit_i64(int64_t x) { put_i64(flan_dev_emit, x); }
|
||||||
|
void flan_dev_emit_f64(double x) { put_f64(flan_dev_emit, x); }
|
||||||
|
|
||||||
|
void flan_dev_emit_str(const uint8_t *bytes, int64_t len) {
|
||||||
|
put_str(flan_dev_emit, bytes, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void flan_dev_result_end(void) {
|
void flan_dev_result_end(void) {
|
||||||
if (result_full) {
|
end_value(result, &result_len, RESULT_MAX, result_full, &generation);
|
||||||
/* Room is made for it rather than assumed: the buffer is full by
|
|
||||||
* definition when this fires. */
|
|
||||||
const char *ell = "...";
|
|
||||||
size_t k = strlen(ell);
|
|
||||||
if (result_len > RESULT_MAX - k) result_len = RESULT_MAX - k;
|
|
||||||
memcpy(result + result_len, ell, k);
|
|
||||||
result_len += k;
|
|
||||||
}
|
|
||||||
/* Last, and back to even, so a reader that sees the new generation sees the
|
|
||||||
* whole value. [| 1] first for the same reason [begin] sets rather than
|
|
||||||
* increments: this must land on an even count whatever state an abandoned
|
|
||||||
* write left behind. */
|
|
||||||
__atomic_store_n(&generation, (generation | 1) + 1, __ATOMIC_RELEASE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Copy the current value out, with the counter that says which one it is.
|
/* Copy the current value out, with the counter that says which one it is.
|
||||||
@ -444,10 +473,6 @@ void flan_dev_watch_enable(int on) {
|
|||||||
__atomic_store_n(&watch_on, on ? 1 : 0, __ATOMIC_RELAXED);
|
__atomic_store_n(&watch_on, on ? 1 : 0, __ATOMIC_RELAXED);
|
||||||
}
|
}
|
||||||
|
|
||||||
int flan_dev_watch_enabled(void) {
|
|
||||||
return __atomic_load_n(&watch_on, __ATOMIC_RELAXED);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The slot a name owns, or NULL if the table is full.
|
/* The slot a name owns, or NULL if the table is full.
|
||||||
*
|
*
|
||||||
* Linear, because the table is 64 long and a hash would need a policy for
|
* Linear, because the table is 64 long and a hash would need a policy for
|
||||||
@ -523,76 +548,27 @@ void flan_dev_watch_emit(const uint8_t *bytes, int64_t len) {
|
|||||||
s->len += (uint32_t)n;
|
s->len += (uint32_t)n;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void watch_cstr(const char *str) {
|
/* The four renderings, aimed at the watch slot instead of the result buffer.
|
||||||
flan_dev_watch_emit((const uint8_t *)str, (int64_t)strlen(str));
|
* The bodies are [put_i64] and friends above — see the note there for why the
|
||||||
}
|
* entry points stay four while the rendering is one. */
|
||||||
|
void flan_dev_watch_emit_i64(int64_t x) { put_i64(flan_dev_watch_emit, x); }
|
||||||
|
void flan_dev_watch_emit_u64(uint64_t x) { put_u64(flan_dev_watch_emit, x); }
|
||||||
|
void flan_dev_watch_emit_f64(double x) { put_f64(flan_dev_watch_emit, x); }
|
||||||
|
|
||||||
void flan_dev_watch_emit_i64(int64_t x) {
|
|
||||||
char buf[32];
|
|
||||||
snprintf(buf, sizeof buf, "%lld", (long long)x);
|
|
||||||
watch_cstr(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
void flan_dev_watch_emit_u64(uint64_t x) {
|
|
||||||
char buf[32];
|
|
||||||
snprintf(buf, sizeof buf, "%llu", (unsigned long long)x);
|
|
||||||
watch_cstr(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Unsigned NaN, the same rule [flan_dev_emit_f64] states: a watch row and a
|
|
||||||
* REPL answer for one value must read the same. */
|
|
||||||
void flan_dev_watch_emit_f64(double x) {
|
|
||||||
char buf[64];
|
|
||||||
if (x != x) snprintf(buf, sizeof buf, "nan");
|
|
||||||
else snprintf(buf, sizeof buf, "%g", x);
|
|
||||||
watch_cstr(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Quoted and escaped, for [flan_dev_emit_str]'s reason and one more: a string
|
|
||||||
* whose content is not escaped does not round-trip, and a newline in one would
|
|
||||||
* become a second row of the table on the wire rather than part of a value. */
|
|
||||||
void flan_dev_watch_emit_str(const uint8_t *bytes, int64_t len) {
|
void flan_dev_watch_emit_str(const uint8_t *bytes, int64_t len) {
|
||||||
size_t n = len < 0 ? 0 : (size_t)len;
|
put_str(flan_dev_watch_emit, bytes, len);
|
||||||
watch_cstr("\"");
|
|
||||||
for (size_t i = 0; i < n; i++) {
|
|
||||||
unsigned char c = bytes[i];
|
|
||||||
switch (c) {
|
|
||||||
case '"': watch_cstr("\\\""); break;
|
|
||||||
case '\\': watch_cstr("\\\\"); break;
|
|
||||||
case '\n': watch_cstr("\\n"); break;
|
|
||||||
case '\t': watch_cstr("\\t"); break;
|
|
||||||
case '\r': watch_cstr("\\r"); break;
|
|
||||||
default:
|
|
||||||
if (c < 0x20) {
|
|
||||||
char buf[8];
|
|
||||||
snprintf(buf, sizeof buf, "\\x%02x", c);
|
|
||||||
watch_cstr(buf);
|
|
||||||
} else {
|
|
||||||
flan_dev_watch_emit(&c, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
watch_cstr("\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void flan_dev_watch_end(void) {
|
void flan_dev_watch_end(void) {
|
||||||
watch_slot *s = watch_cur;
|
watch_slot *s = watch_cur;
|
||||||
watch_cur = NULL;
|
watch_cur = NULL;
|
||||||
if (s == NULL) return;
|
if (s == NULL) return;
|
||||||
if (s->full) {
|
/* [len] widened and narrowed around the shared close, which counts in
|
||||||
/* Room is made for it rather than assumed: the value is full by
|
* size_t because the result buffer does; a slot's own length is 32 bits
|
||||||
* definition when this fires. */
|
* and [WATCH_VAL] is 192, so neither conversion can lose anything. */
|
||||||
const char *ell = "...";
|
size_t len = s->len;
|
||||||
size_t k = strlen(ell);
|
end_value(s->val, &len, WATCH_VAL, s->full, &s->gen);
|
||||||
if (s->len > WATCH_VAL - k) s->len = (uint32_t)(WATCH_VAL - k);
|
s->len = (uint32_t)len;
|
||||||
memcpy(s->val + s->len, ell, k);
|
|
||||||
s->len += (uint32_t)k;
|
|
||||||
}
|
|
||||||
/* Last, and back to even, so a reader that sees the new generation sees the
|
|
||||||
* whole value. [| 1] first for the same reason begin sets rather than
|
|
||||||
* increments: this must land on an even count whatever an abandoned write
|
|
||||||
* left behind. */
|
|
||||||
__atomic_store_n(&s->gen, (s->gen | 1) + 1, __ATOMIC_RELEASE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Watching one scalar, with no compiler change ───────────────────── */
|
/* ── Watching one scalar, with no compiler change ───────────────────── */
|
||||||
@ -630,13 +606,6 @@ int32_t flan_dev_watch_i64(const char *name, int64_t x) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t flan_dev_watch_u64(const char *name, uint64_t x) {
|
|
||||||
if (!flan_dev_watch_begin(name)) return 0;
|
|
||||||
flan_dev_watch_emit_u64(x);
|
|
||||||
flan_dev_watch_end();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t flan_dev_watch_f64(const char *name, double x) {
|
int32_t flan_dev_watch_f64(const char *name, double x) {
|
||||||
if (!flan_dev_watch_begin(name)) return 0;
|
if (!flan_dev_watch_begin(name)) return 0;
|
||||||
flan_dev_watch_emit_f64(x);
|
flan_dev_watch_emit_f64(x);
|
||||||
|
|||||||
@ -457,11 +457,18 @@ static void emit(const char *s) {
|
|||||||
static void emit_n(const uint8_t *p, int64_t n) { flan_write_stdout(p, n); }
|
static void emit_n(const uint8_t *p, int64_t n) { flan_write_stdout(p, n); }
|
||||||
|
|
||||||
/* A text inside a structure, quoted and escaped. The same table as
|
/* A text inside a structure, quoted and escaped. The same table as
|
||||||
* flan_rt.c's [flan_escape_bytes] and flan_dev.c's [flan_dev_emit_str], and
|
* flan_rt.c's [flan_escape_char], which is where the typed side's printers —
|
||||||
* for the same reason those two are the same as each other: three printers
|
* [flan_escape_bytes] and flan_dev.c's emitters — now share their one copy of
|
||||||
* that disagree about what a string looks like is three wire formats. If that
|
* it: printers that disagree about what a string looks like are that many
|
||||||
* table changes, change this one. Streamed rather than built, so there is no
|
* wire formats.
|
||||||
* buffer to overrun and no length to cap. */
|
*
|
||||||
|
* This copy is deliberate, and the argument for it is docs/SPIKE-DUPLICITY.md
|
||||||
|
* §9's: the dyn printer lives inside the runtime that owns the storage it
|
||||||
|
* walks, which is why it prints a dyn vec structurally where the typed
|
||||||
|
* printer answers <vec>. The whole printer is this side's; the table it
|
||||||
|
* shares with the other side is the part that must not drift. If that table
|
||||||
|
* changes, change this one. Streamed rather than built, so there is no buffer
|
||||||
|
* to overrun and no length to cap. */
|
||||||
static void emit_escaped(const uint8_t *p, int64_t n) {
|
static void emit_escaped(const uint8_t *p, int64_t n) {
|
||||||
int64_t i;
|
int64_t i;
|
||||||
emit("\"");
|
emit("\"");
|
||||||
|
|||||||
@ -3,12 +3,12 @@
|
|||||||
* This header is not compiled into a program. The build embeds the runtime's
|
* This header is not compiled into a program. The build embeds the runtime's
|
||||||
* .c files as strings and hands each one to clang on its own, with no include
|
* .c files as strings and hands each one to clang on its own, with no include
|
||||||
* path (see [Build.compile_c]), so runtime/flan_dyn.c declares everything it
|
* path (see [Build.compile_c]), so runtime/flan_dyn.c declares everything it
|
||||||
* defines and this file declares it a second time. That is the same standing
|
* defines and this file declares it a second time. That second copy is not
|
||||||
* arrangement flan_escape_bytes and flan_dev_emit_str already live under —
|
* held honest by hand the way a repeated escape table would be: it is made
|
||||||
* "if either table changes, change both" — and it is made mechanical rather
|
* mechanical, because test/dyn_ops.c includes this header and names every
|
||||||
* than hopeful: test/dyn_ops.c includes this header and names every function
|
* function below, so a signature that drifts from the implementation is a
|
||||||
* below, so a signature that drifts from the implementation is a link error in
|
* link error in `dune test` rather than a surprise at someone else's call
|
||||||
* `dune test` rather than a surprise at someone else's call site.
|
* site.
|
||||||
*
|
*
|
||||||
* Who reads it: the compiler lane, which emits calls to these names, and the
|
* Who reads it: the compiler lane, which emits calls to these names, and the
|
||||||
* C tests. The whole of the boundary is here. What is behind it — the value
|
* C tests. The whole of the boundary is here. What is behind it — the value
|
||||||
|
|||||||
@ -134,10 +134,9 @@ void *flan_restart_frame(int32_t i) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Aim the transfer channel at a frame obtained earlier. The same store
|
/* Aim the transfer channel at a frame obtained earlier. The same store an
|
||||||
* [flan_break_resume] makes and the same one an invoke-restart makes — this
|
* invoke-restart makes — this only spells it without a lookup, for a caller
|
||||||
* only spells it without a lookup, for a caller that did its looking up when
|
* that did its looking up when the stack was worth reading. */
|
||||||
* the stack was worth reading. */
|
|
||||||
void flan_restart_take(void *frame, void *xfer) { *(void **)xfer = frame; }
|
void flan_restart_take(void *frame, void *xfer) { *(void **)xfer = frame; }
|
||||||
|
|
||||||
/* [T] and string are both ptr+len — see Emit.ll. */
|
/* [T] and string are both ptr+len — see Emit.ll. */
|
||||||
@ -340,10 +339,19 @@ int64_t flan_bytes_to_i64(const uint8_t *p, int64_t n) {
|
|||||||
*
|
*
|
||||||
* The test is x != x rather than isnan, which keeps math.h out of this file
|
* The test is x != x rather than isnan, which keeps math.h out of this file
|
||||||
* and is the same comparison the prelude uses. An infinity still prints signed:
|
* and is the same comparison the prelude uses. An infinity still prints signed:
|
||||||
* there the sign is the value. */
|
* there the sign is the value.
|
||||||
|
*
|
||||||
|
* Spelled as a format into a caller's buffer rather than inline here, because
|
||||||
|
* the rule has a second reader: flan_dev.c's REPL emitter and its watch table
|
||||||
|
* render a f64 for an editor to show, and a build where the inspector said
|
||||||
|
* "-nan" and println said "nan" would be the same disagreement one layer out.
|
||||||
|
* That file calls this; there is one copy of the rule. */
|
||||||
|
int flan_f64_format(double x, char *buf, size_t cap) {
|
||||||
|
return (x != x) ? snprintf(buf, cap, "nan") : snprintf(buf, cap, "%g", x);
|
||||||
|
}
|
||||||
|
|
||||||
void flan_f64_to_bytes(double x, uint8_t *buf, flan_slice *out) {
|
void flan_f64_to_bytes(double x, uint8_t *buf, flan_slice *out) {
|
||||||
int n = (x != x) ? snprintf((char *)buf, FLAN_NUM_BYTES, "nan")
|
int n = flan_f64_format(x, (char *)buf, FLAN_NUM_BYTES);
|
||||||
: snprintf((char *)buf, FLAN_NUM_BYTES, "%g", x);
|
|
||||||
out->ptr = buf;
|
out->ptr = buf;
|
||||||
out->len = fit(n);
|
out->len = fit(n);
|
||||||
}
|
}
|
||||||
@ -364,15 +372,45 @@ void flan_u64_to_bytes(uint64_t x, uint8_t *buf, flan_slice *out) {
|
|||||||
out->len = fit(n);
|
out->len = fit(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The escape table itself: what one byte reads as inside a quoted string,
|
||||||
|
* written into [out] and returning how many bytes that took. Never more than
|
||||||
|
* four, which is what every caller's headroom is sized from.
|
||||||
|
*
|
||||||
|
* A table rather than a printer, because the callers frame the same mapping
|
||||||
|
* differently and that framing is the part that is genuinely theirs: the one
|
||||||
|
* below builds a capped slice to hand back, and flan_dev.c's streams into a
|
||||||
|
* fixed buffer it does not own the end of. What they must not differ about is
|
||||||
|
* this switch — the REPL and println disagreeing about what a struct looks
|
||||||
|
* like is two wire formats — so this switch exists once and they share it.
|
||||||
|
*
|
||||||
|
* flan_dyn.c keeps its own copy of the table on purpose; see the comment
|
||||||
|
* there and docs/SPIKE-DUPLICITY.md §9. */
|
||||||
|
int flan_escape_char(unsigned char c, char *out) {
|
||||||
|
switch (c) {
|
||||||
|
case '"': out[0] = '\\'; out[1] = '"'; return 2;
|
||||||
|
case '\\': out[0] = '\\'; out[1] = '\\'; return 2;
|
||||||
|
case '\n': out[0] = '\\'; out[1] = 'n'; return 2;
|
||||||
|
case '\t': out[0] = '\\'; out[1] = 't'; return 2;
|
||||||
|
case '\r': out[0] = '\\'; out[1] = 'r'; return 2;
|
||||||
|
default:
|
||||||
|
if (c < 0x20) {
|
||||||
|
out[0] = '\\';
|
||||||
|
out[1] = 'x';
|
||||||
|
out[2] = "0123456789abcdef"[c >> 4];
|
||||||
|
out[3] = "0123456789abcdef"[c & 0xf];
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
out[0] = (char)c;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* A string *inside* a printed structure, quoted and escaped, so that the run
|
/* A string *inside* a printed structure, quoted and escaped, so that the run
|
||||||
* of bytes can be told from the punctuation around it — (S {:name "a b"}) has
|
* of bytes can be told from the punctuation around it — (S {:name "a b"}) has
|
||||||
* two fields if the quotes are missing and one if they are there.
|
* two fields if the quotes are missing and one if they are there.
|
||||||
*
|
*
|
||||||
* This is the same escape table as flan_dev_emit_str in flan_dev.c, and
|
* The table is [flan_escape_char] above, which is also what flan_dev.c's
|
||||||
* deliberately so: the REPL and println must not disagree about what a struct
|
* emitters use; this function is the framing and nothing else.
|
||||||
* looks like. It cannot be the *same function* because the dev one streams
|
|
||||||
* into the result buffer and this one has to hand back a slice; if either
|
|
||||||
* table changes, change both.
|
|
||||||
*
|
*
|
||||||
* Its own buffer, not `scratch`: escaping is the one conversion whose output
|
* Its own buffer, not `scratch`: escaping is the one conversion whose output
|
||||||
* is not a bounded handful of characters. Over-long input is truncated with an
|
* is not a bounded handful of characters. Over-long input is truncated with an
|
||||||
@ -394,20 +432,10 @@ void flan_escape_bytes(const uint8_t *p, int64_t n, flan_slice *out) {
|
|||||||
escaped[w++] = '"';
|
escaped[w++] = '"';
|
||||||
for (size_t i = 0; i < len; i++) {
|
for (size_t i = 0; i < len; i++) {
|
||||||
if (w + 5 + 4 >= ESCAPE_MAX) { cut = 1; break; }
|
if (w + 5 + 4 >= ESCAPE_MAX) { cut = 1; break; }
|
||||||
unsigned char c = p[i];
|
char e[4];
|
||||||
switch (c) {
|
int k = flan_escape_char(p[i], e);
|
||||||
case '"': escaped[w++] = '\\'; escaped[w++] = '"'; break;
|
memcpy(escaped + w, e, (size_t)k);
|
||||||
case '\\': escaped[w++] = '\\'; escaped[w++] = '\\'; break;
|
w += (size_t)k;
|
||||||
case '\n': escaped[w++] = '\\'; escaped[w++] = 'n'; break;
|
|
||||||
case '\t': escaped[w++] = '\\'; escaped[w++] = 't'; break;
|
|
||||||
case '\r': escaped[w++] = '\\'; escaped[w++] = 'r'; break;
|
|
||||||
default:
|
|
||||||
if (c < 0x20) {
|
|
||||||
w += (size_t)snprintf(escaped + w, 5, "\\x%02x", c);
|
|
||||||
} else {
|
|
||||||
escaped[w++] = (char)c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (cut) { escaped[w++] = '.'; escaped[w++] = '.'; escaped[w++] = '.'; }
|
if (cut) { escaped[w++] = '.'; escaped[w++] = '.'; escaped[w++] = '.'; }
|
||||||
escaped[w++] = '"';
|
escaped[w++] = '"';
|
||||||
@ -603,16 +631,6 @@ static uint32_t flan_name_id(const uint8_t *s, int64_t n) {
|
|||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* What the break loop calls to resume: look a restart up by the name someone
|
|
||||||
* typed and aim the channel at it. 0 if no frame offers it, and then the loop
|
|
||||||
* says so rather than resuming into nothing. */
|
|
||||||
int32_t flan_break_resume(const uint8_t *name, int64_t namelen, void *xfer) {
|
|
||||||
void *r = flan_find_restart(flan_name_id(name, namelen));
|
|
||||||
if (r == NULL) return 0;
|
|
||||||
*(void **)xfer = r;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void flan_error(uint32_t type_id, void *condition, void *xfer,
|
void flan_error(uint32_t type_id, void *condition, void *xfer,
|
||||||
const uint8_t *name, int64_t namelen) {
|
const uint8_t *name, int64_t namelen) {
|
||||||
flan_signal(type_id, condition, xfer);
|
flan_signal(type_id, condition, xfer);
|
||||||
|
|||||||
3
vendor/agent/flan_agent.c
vendored
3
vendor/agent/flan_agent.c
vendored
@ -85,7 +85,6 @@ uint64_t flan_dev_result_cap(void);
|
|||||||
* [enable] — the table is written only while somebody is reading it, so
|
* [enable] — the table is written only while somebody is reading it, so
|
||||||
* opening and closing a watch buffer is a message that arrives here. */
|
* opening and closing a watch buffer is a message that arrives here. */
|
||||||
void flan_dev_watch_enable(int on);
|
void flan_dev_watch_enable(int on);
|
||||||
int flan_dev_watch_enabled(void);
|
|
||||||
/* And [reset], which opens a new accumulation window for the numeric slots.
|
/* And [reset], which opens a new accumulation window for the numeric slots.
|
||||||
* It moves one counter and touches no slot, so the game thread stays the only
|
* It moves one counter and touches no slot, so the game thread stays the only
|
||||||
* writer of the table. */
|
* writer of the table. */
|
||||||
@ -310,8 +309,6 @@ extern void (*flan_break_hook)(const uint8_t *name, int64_t namelen,
|
|||||||
* flan_rt.c, which carries the argument for why they are two hooks and not
|
* flan_rt.c, which carries the argument for why they are two hooks and not
|
||||||
* one. This end of it is [trap_stop] below. */
|
* one. This end of it is [trap_stop] below. */
|
||||||
extern void (*flan_trap_hook)(const uint8_t *name, int64_t namelen);
|
extern void (*flan_trap_hook)(const uint8_t *name, int64_t namelen);
|
||||||
extern int32_t flan_break_resume(const uint8_t *name, int64_t namelen,
|
|
||||||
void *xfer);
|
|
||||||
extern int32_t flan_restart_count(void);
|
extern int32_t flan_restart_count(void);
|
||||||
/* The shadow stack (runtime/flan_dev.c). The compiler pushes a frame per Flan
|
/* The shadow stack (runtime/flan_dev.c). The compiler pushes a frame per Flan
|
||||||
* call in a dev build; these read one, and only ever on the thread that owns
|
* call in a dev build; these read one, and only ever on the thread that owns
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user