A dyn trap that says where: the nine trapping operators take a site

The dyn arithmetic and ordering entry points printed their sentence with no
file, no line and no column, which in a dynamic-first language is the type
error arriving from nowhere. flan_rt.c's bounds and arithmetic traps have
taken an emitter-threaded (loc, loclen) pair since they were written, and
[flan_dyn_cast_kind] is the fresh precedent on the dyn side; this is the same
pair, threaded through [arith], [want_nums] and [order] to the five
arithmetic and four ordering entry points. [eq] never traps and takes none.

The three trap printers take the pair and print nothing for a NULL loc, so
every other call site in the file — and test/dyn_ops.c, which calls the
runtime directly and has no source position — keeps its sentence byte for
byte. [trap_oom] is left alone: it is reached from [gc_alloc], which has no
site to be given and would have had to grow one on every allocation path in
the file for no reader's benefit.
This commit is contained in:
Joseph Ferano 2026-09-20 17:36:10 +07:00
parent ac2af7c537
commit 6d82221978
7 changed files with 234 additions and 108 deletions

View File

@ -4358,7 +4358,11 @@ and dyn_fold ctx ~want loc name first rest =
no_dyn_yet loc ~into:false Types.Dyn no_dyn_yet loc ~into:false Types.Dyn
(Printf.sprintf " — %s has no dyn form" name) (Printf.sprintf " — %s has no dyn form" name)
in in
let apply acc b = rt loc Types.Dyn sym [ acc; box loc b ] in (* The site travels with the operands. A dyn arithmetic trap is this
language's type error, and until now it printed with no file, no line and
no column [here loc] is the same string literal [cast_dyn] hands the
runtime, and the runtime prints it as a GNU prefix. *)
let apply acc b = rt loc Types.Dyn sym [ acc; box loc b; here loc ] in
let acc = let acc =
match first with match first with
| [ a; b ] -> apply (box loc a) b | [ a; b ] -> apply (box loc a) b
@ -4688,7 +4692,13 @@ and named_call ctx ~want loc name args =
| "<" -> "flan_dyn_lt" | "<=" -> "flan_dyn_le" | "<" -> "flan_dyn_lt" | "<=" -> "flan_dyn_le"
| ">" -> "flan_dyn_gt" | _ -> "flan_dyn_ge" | ">" -> "flan_dyn_gt" | _ -> "flan_dyn_ge"
in in
let cmp = unbox loc Types.Bool (rt loc Types.Dyn sym [ box loc a; box loc b ]) in (* [eq] never traps and takes no site; the four orderings do, and get
one, for the reason [dyn_fold] gives. *)
let site = if String.equal sym "flan_dyn_eq" then [] else [ here loc ] in
let cmp =
unbox loc Types.Bool
(rt loc Types.Dyn sym ([ box loc a; box loc b ] @ site))
in
(* [!=] has no entry point of its own: there is one structural equality (* [!=] has no entry point of its own: there is one structural equality
and the negation is an [i1] flip the backend folds away. *) and the negation is an [i1] flip the backend folds away. *)
let r = let r =

View File

@ -3625,15 +3625,19 @@ declare i64 @flan_dyn_kw(ptr, i64)
declare i64 @flan_dyn_map_get(i64, i64) declare i64 @flan_dyn_map_get(i64, i64)
declare void @flan_dyn_map_set(i64, i64, i64) declare void @flan_dyn_map_set(i64, i64, i64)
declare i64 @flan_dyn_map_contains(i64, i64) declare i64 @flan_dyn_map_contains(i64, i64)
declare i64 @flan_dyn_add(i64, i64) ; The nine that trap carry the site as ptr+len, the way the bounds and
declare i64 @flan_dyn_sub(i64, i64) ; arithmetic traps in flan_rt.c do: a dyn type error IS the type error in a
declare i64 @flan_dyn_mul(i64, i64) ; dynamic program, and it used to print with no file and no line. [eq] never
declare i64 @flan_dyn_div(i64, i64) ; traps, so it has nowhere to put one.
declare i64 @flan_dyn_rem(i64, i64) declare i64 @flan_dyn_add(i64, i64, ptr, i64)
declare i64 @flan_dyn_lt(i64, i64) declare i64 @flan_dyn_sub(i64, i64, ptr, i64)
declare i64 @flan_dyn_le(i64, i64) declare i64 @flan_dyn_mul(i64, i64, ptr, i64)
declare i64 @flan_dyn_gt(i64, i64) declare i64 @flan_dyn_div(i64, i64, ptr, i64)
declare i64 @flan_dyn_ge(i64, i64) declare i64 @flan_dyn_rem(i64, i64, ptr, i64)
declare i64 @flan_dyn_lt(i64, i64, ptr, i64)
declare i64 @flan_dyn_le(i64, i64, ptr, i64)
declare i64 @flan_dyn_gt(i64, i64, ptr, i64)
declare i64 @flan_dyn_ge(i64, i64, ptr, i64)
declare i64 @flan_dyn_eq(i64, i64) declare i64 @flan_dyn_eq(i64, i64)
declare i64 @flan_dyn_len(i64) declare i64 @flan_dyn_len(i64)
declare i64 @flan_dyn_at(i64, i64) declare i64 @flan_dyn_at(i64, i64)

View File

@ -759,22 +759,42 @@ static void say(char *buf, int64_t cap, flan_dyn v) {
* which without reading the sentence twice and because the break loop lists * which without reading the sentence twice and because the break loop lists
* them by name. */ * them by name. */
static _Noreturn void trap2(const char *name, int64_t namelen, const char *op, /* Where the operation was written, printed as flan_rt.c's traps print it: the
* GNU "file:line:col: " prefix, so `next-error` walks to the dyn failure the
* same way it walks to a bounds failure. The pair is what an emitted string
* literal already is a pointer and a length, not a C string and the
* emitter hands it over exactly as [flan_dyn_cast_kind]'s site does.
*
* A NULL [loc] prints nothing at all and the sentence after it is byte for
* byte the one this file printed before: the entry points that have not been
* given a site yet (every one but the five arithmetic and four ordering ones)
* pass NULL, and so does test/dyn_ops.c, which calls the runtime directly and
* has no source position to offer. */
static void trap_where(const uint8_t *loc, int64_t loclen) {
if (loc != NULL && loclen > 0)
fprintf(stderr, "%.*s: ", (int)loclen, (const char *)loc);
}
static _Noreturn void trap2(const uint8_t *loc, int64_t loclen,
const char *name, int64_t namelen, const char *op,
const char *why, flan_dyn a, flan_dyn b) { const char *why, flan_dyn a, flan_dyn b) {
char sa[SAY_MAX], sb[SAY_MAX]; char sa[SAY_MAX], sb[SAY_MAX];
say(sa, SAY_MAX, a); say(sa, SAY_MAX, a);
say(sb, SAY_MAX, b); say(sb, SAY_MAX, b);
fflush(stdout); fflush(stdout);
trap_where(loc, loclen);
fprintf(stderr, "dyn %s: %s and %s, and %s — (%s %s %s)\n", op, tag_of(a), fprintf(stderr, "dyn %s: %s and %s, and %s — (%s %s %s)\n", op, tag_of(a),
tag_of(b), why, op, sa, sb); tag_of(b), why, op, sa, sb);
flan_trap((const uint8_t *)name, namelen); flan_trap((const uint8_t *)name, namelen);
} }
static _Noreturn void trap1(const char *name, int64_t namelen, const char *op, static _Noreturn void trap1(const uint8_t *loc, int64_t loclen,
const char *name, int64_t namelen, const char *op,
const char *why, flan_dyn a) { const char *why, flan_dyn a) {
char sa[SAY_MAX]; char sa[SAY_MAX];
say(sa, SAY_MAX, a); say(sa, SAY_MAX, a);
fflush(stdout); fflush(stdout);
trap_where(loc, loclen);
fprintf(stderr, "dyn %s: %s, and %s — (%s %s)\n", op, tag_of(a), why, op, sa); fprintf(stderr, "dyn %s: %s, and %s — (%s %s)\n", op, tag_of(a), why, op, sa);
flan_trap((const uint8_t *)name, namelen); flan_trap((const uint8_t *)name, namelen);
} }
@ -782,11 +802,17 @@ static _Noreturn void trap1(const char *name, int64_t namelen, const char *op,
#define TYPE_TRAP "DynType", 7 #define TYPE_TRAP "DynType", 7
#define ARITH_TRAP "DynArith", 8 #define ARITH_TRAP "DynArith", 8
static _Noreturn void trap_range(const char *op, flan_dyn v, int64_t i, /* No site reaches these two yet: [at], [set-at], [push] and the allocator
* paths are not among the nine entry points this pass gave a location to. The
* parameter is here so that giving them one later is a call-site change and
* not another round of signature churn. */
static _Noreturn void trap_range(const uint8_t *loc, int64_t loclen,
const char *op, flan_dyn v, int64_t i,
int64_t len) { int64_t len) {
char sv[SAY_MAX]; char sv[SAY_MAX];
say(sv, SAY_MAX, v); say(sv, SAY_MAX, v);
fflush(stdout); fflush(stdout);
trap_where(loc, loclen);
fprintf(stderr, fprintf(stderr,
"dyn %s: index %lld is out of bounds for %s of length %lld — %s\n", "dyn %s: index %lld is out of bounds for %s of length %lld — %s\n",
op, (long long)i, tag_of(v), (long long)len, sv); op, (long long)i, tag_of(v), (long long)len, sv);
@ -1080,7 +1106,7 @@ flan_dyn flan_dyn_map_new(void) {
flan_dyn flan_dyn_map_new_class(flan_dyn k) { flan_dyn flan_dyn_map_new_class(flan_dyn k) {
flan_obj *o; flan_obj *o;
if (flan_dyn_tag(k) != FLAN_DYN_TAG_KEYWORD) if (flan_dyn_tag(k) != FLAN_DYN_TAG_KEYWORD)
trap1(TYPE_TRAP, "class instance", "a class tag is a keyword", k); trap1(NULL, 0, TYPE_TRAP, "class instance", "a class tag is a keyword", k);
o = gc_alloc(OBJ_MAP, 0); o = gc_alloc(OBJ_MAP, 0);
o->len = 0; o->len = 0;
o->u.v.items = NULL; o->u.v.items = NULL;
@ -1177,7 +1203,7 @@ static inline int is_vec(flan_dyn v) {
int64_t flan_dyn_need_i64(flan_dyn v) { int64_t flan_dyn_need_i64(flan_dyn v) {
if (flan_dyn_tag(v) != FLAN_DYN_TAG_INT) if (flan_dyn_tag(v) != FLAN_DYN_TAG_INT)
trap1(TYPE_TRAP, "i64", "an int was wanted", v); trap1(NULL, 0, TYPE_TRAP, "i64", "an int was wanted", v);
return dyn_int_value(v); return dyn_int_value(v);
} }
@ -1204,13 +1230,13 @@ int64_t flan_dyn_need_i64(flan_dyn v) {
* not tell (g 1) from (g (len xs)). */ * not tell (g 1) from (g (len xs)). */
double flan_dyn_need_f64(flan_dyn v) { double flan_dyn_need_f64(flan_dyn v) {
if (flan_dyn_tag(v) != FLAN_DYN_TAG_FLOAT) if (flan_dyn_tag(v) != FLAN_DYN_TAG_FLOAT)
trap1(TYPE_TRAP, "f64", "a float was wanted", v); trap1(NULL, 0, TYPE_TRAP, "f64", "a float was wanted", v);
return dyn_num_value(v); return dyn_num_value(v);
} }
uint8_t flan_dyn_need_bool(flan_dyn v) { uint8_t flan_dyn_need_bool(flan_dyn v) {
if (flan_dyn_tag(v) != FLAN_DYN_TAG_BOOL) if (flan_dyn_tag(v) != FLAN_DYN_TAG_BOOL)
trap1(TYPE_TRAP, "bool", "a bool was wanted", v); trap1(NULL, 0, TYPE_TRAP, "bool", "a bool was wanted", v);
return (uint8_t)(dyn_payload(v) ? 1 : 0); return (uint8_t)(dyn_payload(v) ? 1 : 0);
} }
@ -1304,7 +1330,7 @@ int32_t flan_dyn_cast_kind(flan_dyn v, const uint8_t *loc, int64_t loc_len,
: sizeof name - 1; : sizeof name - 1;
memcpy(name, target, n); memcpy(name, target, n);
name[n] = '\0'; name[n] = '\0';
trap1(TYPE_TRAP, name, "a number was wanted", v); trap1(NULL, 0, TYPE_TRAP, name, "a number was wanted", v);
} }
int32_t is_float = tag == FLAN_DYN_TAG_FLOAT ? 1 : 0; int32_t is_float = tag == FLAN_DYN_TAG_FLOAT ? 1 : 0;
if (is_float != (want_float ? 1 : 0) && site_first_time(loc, loc_len)) { if (is_float != (want_float ? 1 : 0) && site_first_time(loc, loc_len)) {
@ -1331,7 +1357,7 @@ int32_t flan_dyn_is_nil(flan_dyn v) {
* operation that refused. */ * operation that refused. */
flan_dyn flan_dyn_need_not_nil(flan_dyn v) { flan_dyn flan_dyn_need_not_nil(flan_dyn v) {
if (flan_dyn_tag(v) == FLAN_DYN_TAG_NIL) if (flan_dyn_tag(v) == FLAN_DYN_TAG_NIL)
trap1(TYPE_TRAP, "some", trap1(NULL, 0, TYPE_TRAP, "some",
"Some cannot hold nil -- nil and None would become the same case " "Some cannot hold nil -- nil and None would become the same case "
"of an (Option dyn)", v); "of an (Option dyn)", v);
return v; return v;
@ -1367,15 +1393,17 @@ uint8_t flan_dyn_truthy(flan_dyn v) {
* its own sentence for the reason flan_rt.c's gives it one: somebody meeting * its own sentence for the reason flan_rt.c's gives it one: somebody meeting
* it has probably never had to think about it. */ * it has probably never had to think about it. */
static void want_nums(const char *op, const char *why, flan_dyn a, flan_dyn b) { static void want_nums(const uint8_t *loc, int64_t loclen, const char *op,
if (!is_num(a) || !is_num(b)) trap2(TYPE_TRAP, op, why, a, b); const char *why, flan_dyn a, flan_dyn b) {
if (!is_num(a) || !is_num(b)) trap2(loc, loclen, TYPE_TRAP, op, why, a, b);
} }
#define ARITH_NUM "it takes two numbers" #define ARITH_NUM "it takes two numbers"
static flan_dyn arith(const char *op, flan_dyn a, flan_dyn b) { static flan_dyn arith(const uint8_t *loc, int64_t loclen, const char *op,
flan_dyn a, flan_dyn b) {
int64_t x, y; int64_t x, y;
want_nums(op, ARITH_NUM, a, b); want_nums(loc, loclen, op, ARITH_NUM, a, b);
if (flan_dyn_tag(a) == FLAN_DYN_TAG_INT && if (flan_dyn_tag(a) == FLAN_DYN_TAG_INT &&
flan_dyn_tag(b) == FLAN_DYN_TAG_INT) { flan_dyn_tag(b) == FLAN_DYN_TAG_INT) {
x = dyn_int_value(a); x = dyn_int_value(a);
@ -1385,14 +1413,14 @@ static flan_dyn arith(const char *op, flan_dyn a, flan_dyn b) {
case '-': return flan_dyn_from_i64((int64_t)((uint64_t)x - (uint64_t)y)); case '-': return flan_dyn_from_i64((int64_t)((uint64_t)x - (uint64_t)y));
case '*': return flan_dyn_from_i64((int64_t)((uint64_t)x * (uint64_t)y)); case '*': return flan_dyn_from_i64((int64_t)((uint64_t)x * (uint64_t)y));
case '/': case '/':
if (y == 0) trap2(ARITH_TRAP, op, "it does not divide by zero", a, b); if (y == 0) trap2(loc, loclen, ARITH_TRAP, op, "it does not divide by zero", a, b);
if (x == INT64_MIN && y == -1) if (x == INT64_MIN && y == -1)
trap2(ARITH_TRAP, op, trap2(loc, loclen, ARITH_TRAP, op,
"the quotient is one past the largest i64, which is true of " "the quotient is one past the largest i64, which is true of "
"this pair of operands and no other", a, b); "this pair of operands and no other", a, b);
return flan_dyn_from_i64(x / y); return flan_dyn_from_i64(x / y);
default: default:
if (y == 0) trap2(ARITH_TRAP, op, "it does not divide by zero", a, b); if (y == 0) trap2(loc, loclen, ARITH_TRAP, op, "it does not divide by zero", a, b);
if (x == INT64_MIN && y == -1) return flan_dyn_from_i64(0); if (x == INT64_MIN && y == -1) return flan_dyn_from_i64(0);
return flan_dyn_from_i64(x % y); return flan_dyn_from_i64(x % y);
} }
@ -1425,11 +1453,26 @@ static flan_dyn arith(const char *op, flan_dyn a, flan_dyn b) {
} }
} }
flan_dyn flan_dyn_add(flan_dyn a, flan_dyn b) { return arith("+", a, b); } flan_dyn flan_dyn_add(flan_dyn a, flan_dyn b, const uint8_t *loc,
flan_dyn flan_dyn_sub(flan_dyn a, flan_dyn b) { return arith("-", a, b); } int64_t loclen) {
flan_dyn flan_dyn_mul(flan_dyn a, flan_dyn b) { return arith("*", a, b); } return arith(loc, loclen, "+", a, b);
flan_dyn flan_dyn_div(flan_dyn a, flan_dyn b) { return arith("/", a, b); } }
flan_dyn flan_dyn_rem(flan_dyn a, flan_dyn b) { return arith("%", a, b); } flan_dyn flan_dyn_sub(flan_dyn a, flan_dyn b, const uint8_t *loc,
int64_t loclen) {
return arith(loc, loclen, "-", a, b);
}
flan_dyn flan_dyn_mul(flan_dyn a, flan_dyn b, const uint8_t *loc,
int64_t loclen) {
return arith(loc, loclen, "*", a, b);
}
flan_dyn flan_dyn_div(flan_dyn a, flan_dyn b, const uint8_t *loc,
int64_t loclen) {
return arith(loc, loclen, "/", a, b);
}
flan_dyn flan_dyn_rem(flan_dyn a, flan_dyn b, const uint8_t *loc,
int64_t loclen) {
return arith(loc, loclen, "%", a, b);
}
/* ── Ordering ────────────────────────────────────────────────────────── /* ── Ordering ──────────────────────────────────────────────────────────
* *
@ -1444,7 +1487,8 @@ flan_dyn flan_dyn_rem(flan_dyn a, flan_dyn b) { return arith("%", a, b); }
* numbering, and a program that sorted a mixed vec would get a stable answer * numbering, and a program that sorted a mixed vec would get a stable answer
* that means nothing. */ * that means nothing. */
static int order(const char *op, flan_dyn a, flan_dyn b) { static int order(const uint8_t *loc, int64_t loclen, const char *op,
flan_dyn a, flan_dyn b) {
if (is_num(a) && is_num(b)) { if (is_num(a) && is_num(b)) {
if (flan_dyn_tag(a) == FLAN_DYN_TAG_INT && if (flan_dyn_tag(a) == FLAN_DYN_TAG_INT &&
flan_dyn_tag(b) == FLAN_DYN_TAG_INT) { flan_dyn_tag(b) == FLAN_DYN_TAG_INT) {
@ -1468,22 +1512,26 @@ static int order(const char *op, flan_dyn a, flan_dyn b) {
if (c != 0) return c < 0 ? -1 : 1; if (c != 0) return c < 0 ? -1 : 1;
return x->len < y->len ? -1 : (x->len > y->len ? 1 : 0); return x->len < y->len ? -1 : (x->len > y->len ? 1 : 0);
} }
trap2(TYPE_TRAP, op, trap2(loc, loclen, TYPE_TRAP, op,
"it compares two numbers or two texts, and these are neither", a, b); "it compares two numbers or two texts, and these are neither", a, b);
} }
flan_dyn flan_dyn_lt(flan_dyn a, flan_dyn b) { flan_dyn flan_dyn_lt(flan_dyn a, flan_dyn b, const uint8_t *loc,
return flan_dyn_from_bool(order("<", a, b) == -1); int64_t loclen) {
return flan_dyn_from_bool(order(loc, loclen, "<", a, b) == -1);
} }
flan_dyn flan_dyn_le(flan_dyn a, flan_dyn b) { flan_dyn flan_dyn_le(flan_dyn a, flan_dyn b, const uint8_t *loc,
int c = order("<=", a, b); int64_t loclen) {
int c = order(loc, loclen, "<=", a, b);
return flan_dyn_from_bool(c == -1 || c == 0); return flan_dyn_from_bool(c == -1 || c == 0);
} }
flan_dyn flan_dyn_gt(flan_dyn a, flan_dyn b) { flan_dyn flan_dyn_gt(flan_dyn a, flan_dyn b, const uint8_t *loc,
return flan_dyn_from_bool(order(">", a, b) == 1); int64_t loclen) {
return flan_dyn_from_bool(order(loc, loclen, ">", a, b) == 1);
} }
flan_dyn flan_dyn_ge(flan_dyn a, flan_dyn b) { flan_dyn flan_dyn_ge(flan_dyn a, flan_dyn b, const uint8_t *loc,
int c = order(">=", a, b); int64_t loclen) {
int c = order(loc, loclen, ">=", a, b);
return flan_dyn_from_bool(c == 1 || c == 0); return flan_dyn_from_bool(c == 1 || c == 0);
} }
@ -1699,7 +1747,7 @@ static void view_unbox(const char *op, flan_dyn v, int32_t elem, flan_dyn x,
case FLAN_VIEW_I64: { case FLAN_VIEW_I64: {
int64_t n; int64_t n;
if (flan_dyn_tag(x) != FLAN_DYN_TAG_INT) if (flan_dyn_tag(x) != FLAN_DYN_TAG_INT)
trap2(TYPE_TRAP, op, "this view's elements are int", v, x); trap2(NULL, 0, TYPE_TRAP, op, "this view's elements are int", v, x);
n = dyn_int_value(x); n = dyn_int_value(x);
memcpy(p, &n, 8); memcpy(p, &n, 8);
return; return;
@ -1707,7 +1755,7 @@ static void view_unbox(const char *op, flan_dyn v, int32_t elem, flan_dyn x,
case FLAN_VIEW_F64: { case FLAN_VIEW_F64: {
double d; double d;
if (flan_dyn_tag(x) != FLAN_DYN_TAG_FLOAT) if (flan_dyn_tag(x) != FLAN_DYN_TAG_FLOAT)
trap2(TYPE_TRAP, op, "this view's elements are float", v, x); trap2(NULL, 0, TYPE_TRAP, op, "this view's elements are float", v, x);
d = dyn_num_value(x); d = dyn_num_value(x);
memcpy(p, &d, 8); memcpy(p, &d, 8);
return; return;
@ -1715,7 +1763,7 @@ static void view_unbox(const char *op, flan_dyn v, int32_t elem, flan_dyn x,
default: { default: {
uint8_t b; uint8_t b;
if (flan_dyn_tag(x) != FLAN_DYN_TAG_BOOL) if (flan_dyn_tag(x) != FLAN_DYN_TAG_BOOL)
trap2(TYPE_TRAP, op, "this view's elements are bool", v, x); trap2(NULL, 0, TYPE_TRAP, op, "this view's elements are bool", v, x);
b = dyn_payload(x) ? 1 : 0; b = dyn_payload(x) ? 1 : 0;
*p = b; *p = b;
return; return;
@ -1748,7 +1796,7 @@ flan_dyn flan_dyn_len(flan_dyn v) {
if (o->kind == OBJ_VIEW) return flan_dyn_from_i64(view_len("len", o)); if (o->kind == OBJ_VIEW) return flan_dyn_from_i64(view_len("len", o));
return flan_dyn_from_i64(o->len); return flan_dyn_from_i64(o->len);
} }
trap1(TYPE_TRAP, "len", "only a text, a vec or a map has one", v); trap1(NULL, 0, TYPE_TRAP, "len", "only a text, a vec or a map has one", v);
} }
/* The index has to be an int, and that is a separate sentence from the /* The index has to be an int, and that is a separate sentence from the
@ -1756,7 +1804,7 @@ flan_dyn flan_dyn_len(flan_dyn v) {
* and telling somebody "these are the wrong types" names neither. */ * and telling somebody "these are the wrong types" names neither. */
static int64_t need_index(const char *op, flan_dyn v, flan_dyn i) { static int64_t need_index(const char *op, flan_dyn v, flan_dyn i) {
if (flan_dyn_tag(i) != FLAN_DYN_TAG_INT) if (flan_dyn_tag(i) != FLAN_DYN_TAG_INT)
trap2(TYPE_TRAP, op, "an index must be an int", v, i); trap2(NULL, 0, TYPE_TRAP, op, "an index must be an int", v, i);
return dyn_int_value(i); return dyn_int_value(i);
} }
@ -1767,16 +1815,16 @@ flan_dyn flan_dyn_at(flan_dyn v, flan_dyn i) {
int64_t k; int64_t k;
flan_obj *o; flan_obj *o;
if (!is_text(v) && !is_vec(v)) if (!is_text(v) && !is_vec(v))
trap2(TYPE_TRAP, "at", "only a text or a vec is indexed", v, i); trap2(NULL, 0, TYPE_TRAP, "at", "only a text or a vec is indexed", v, i);
k = need_index("at", v, i); k = need_index("at", v, i);
o = dyn_obj(v); o = dyn_obj(v);
if (o->kind == OBJ_VIEW) { if (o->kind == OBJ_VIEW) {
int64_t len = view_len("at", o); int64_t len = view_len("at", o);
if (k < 0 || k >= len) trap_range("at", v, k, len); if (k < 0 || k >= len) trap_range(NULL, 0, "at", v, k, len);
return view_box(o->u.view.elem, return view_box(o->u.view.elem,
(const uint8_t *)view_base(o) + k * view_elem_size(o->u.view.elem)); (const uint8_t *)view_base(o) + k * view_elem_size(o->u.view.elem));
} }
if (k < 0 || k >= o->len) trap_range("at", v, k, o->len); if (k < 0 || k >= o->len) trap_range(NULL, 0, "at", v, k, o->len);
if (o->kind == OBJ_TEXT) return flan_dyn_from_i64(obj_text_bytes(o)[k]); if (o->kind == OBJ_TEXT) return flan_dyn_from_i64(obj_text_bytes(o)[k]);
return o->u.v.items[k]; return o->u.v.items[k];
} }
@ -1785,20 +1833,20 @@ void flan_dyn_set_at(flan_dyn v, flan_dyn i, flan_dyn x) {
int64_t k; int64_t k;
flan_obj *o; flan_obj *o;
if (is_text(v)) if (is_text(v))
trap2(TYPE_TRAP, "set-at", "a text is immutable — build another one", v, i); trap2(NULL, 0, TYPE_TRAP, "set-at", "a text is immutable — build another one", v, i);
if (!is_vec(v)) if (!is_vec(v))
trap2(TYPE_TRAP, "set-at", "only a vec is assigned into", v, i); trap2(NULL, 0, TYPE_TRAP, "set-at", "only a vec is assigned into", v, i);
k = need_index("set-at", v, i); k = need_index("set-at", v, i);
o = dyn_obj(v); o = dyn_obj(v);
if (o->kind == OBJ_VIEW) { if (o->kind == OBJ_VIEW) {
int64_t len = view_len("set-at", o); int64_t len = view_len("set-at", o);
uint8_t *p; uint8_t *p;
if (k < 0 || k >= len) trap_range("set-at", v, k, len); if (k < 0 || k >= len) trap_range(NULL, 0, "set-at", v, k, len);
p = (uint8_t *)view_base(o) + k * view_elem_size(o->u.view.elem); p = (uint8_t *)view_base(o) + k * view_elem_size(o->u.view.elem);
view_unbox("set-at", v, o->u.view.elem, x, p); view_unbox("set-at", v, o->u.view.elem, x, p);
return; return;
} }
if (k < 0 || k >= o->len) trap_range("set-at", v, k, o->len); if (k < 0 || k >= o->len) trap_range(NULL, 0, "set-at", v, k, o->len);
o->u.v.items[k] = x; o->u.v.items[k] = x;
} }
@ -1807,7 +1855,7 @@ void flan_dyn_push(flan_dyn v, flan_dyn x) {
if (!is_vec(v)) { if (!is_vec(v)) {
/* The value is in the sentence rather than the vec, because the vec is the /* The value is in the sentence rather than the vec, because the vec is the
* thing that is wrong and the value is what says which push it was. */ * thing that is wrong and the value is what says which push it was. */
trap2(TYPE_TRAP, "push", "only a vec is pushed to", v, x); trap2(NULL, 0, TYPE_TRAP, "push", "only a vec is pushed to", v, x);
} }
o = dyn_obj(v); o = dyn_obj(v);
if (o->kind == OBJ_VIEW) { if (o->kind == OBJ_VIEW) {
@ -1815,7 +1863,7 @@ void flan_dyn_push(flan_dyn v, flan_dyn x) {
static const uint8_t push_loc[] = "(dyn push)"; static const uint8_t push_loc[] = "(dyn push)";
int64_t size; int64_t size;
if (!o->u.view.is_vec) if (!o->u.view.is_vec)
trap2(TYPE_TRAP, "push", trap2(NULL, 0, TYPE_TRAP, "push",
"this view is a slice or an array and cannot grow", v, x); "this view is a slice or an array and cannot grow", v, x);
size = view_elem_size(o->u.view.elem); size = view_elem_size(o->u.view.elem);
view_unbox("push", v, o->u.view.elem, x, buf); view_unbox("push", v, o->u.view.elem, x, buf);
@ -1869,7 +1917,7 @@ static int64_t map_find(flan_obj *o, flan_dyn k) {
} }
static flan_obj *want_map(const char *op, flan_dyn m, flan_dyn k) { static flan_obj *want_map(const char *op, flan_dyn m, flan_dyn k) {
if (!is_map(m)) trap2(TYPE_TRAP, op, "only a map answers it", m, k); if (!is_map(m)) trap2(NULL, 0, TYPE_TRAP, op, "only a map answers it", m, k);
return dyn_obj(m); return dyn_obj(m);
} }

View File

@ -108,20 +108,28 @@ flan_dyn flan_dyn_kw(const uint8_t *p, int64_t n);
* sentence naming the operation, the tags it was given and the values, and * sentence naming the operation, the tags it was given and the values, and
* then takes flan_rt.c's [flan_trap] which parks the program for inspection * then takes flan_rt.c's [flan_trap] which parks the program for inspection
* in a dev session and ends it in a standalone build. The three that cannot * in a dev session and ends it in a standalone build. The three that cannot
* trap say so on their own line. */ * trap say so on their own line.
*
* The nine below take the site as well: [loc]/[loclen] are the bytes of a
* "file:line:col" string the emitter already has, and the trap prints them as
* a GNU prefix so the failure is somewhere rather than nowhere. It is the same
* pair flan_rt.c's bounds and arithmetic traps take, and the same pair
* [flan_dyn_cast_kind] takes below. A caller with no site the C tests, and
* anything outside a compiled Flan program passes (NULL, 0) and gets the
* sentence with no prefix. */
flan_dyn flan_dyn_add(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_add(flan_dyn a, flan_dyn b, const uint8_t *loc, int64_t loclen);
flan_dyn flan_dyn_sub(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_sub(flan_dyn a, flan_dyn b, const uint8_t *loc, int64_t loclen);
flan_dyn flan_dyn_mul(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_mul(flan_dyn a, flan_dyn b, const uint8_t *loc, int64_t loclen);
flan_dyn flan_dyn_div(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_div(flan_dyn a, flan_dyn b, const uint8_t *loc, int64_t loclen);
flan_dyn flan_dyn_rem(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_rem(flan_dyn a, flan_dyn b, const uint8_t *loc, int64_t loclen);
/* Answer a bool dyn. Numbers compare as numbers and text compares bytewise; /* Answer a bool dyn. Numbers compare as numbers and text compares bytewise;
* a mixture of the two, or anything else, traps. */ * a mixture of the two, or anything else, traps. */
flan_dyn flan_dyn_lt(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_lt(flan_dyn a, flan_dyn b, const uint8_t *loc, int64_t loclen);
flan_dyn flan_dyn_le(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_le(flan_dyn a, flan_dyn b, const uint8_t *loc, int64_t loclen);
flan_dyn flan_dyn_gt(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_gt(flan_dyn a, flan_dyn b, const uint8_t *loc, int64_t loclen);
flan_dyn flan_dyn_ge(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_ge(flan_dyn a, flan_dyn b, const uint8_t *loc, int64_t loclen);
/* Structural, and the one operation in this file that never traps: two values /* Structural, and the one operation in this file that never traps: two values
* of unrelated tags are not an error, they are unequal. */ * of unrelated tags are not an error, they are unequal. */

View File

@ -148,11 +148,31 @@ static flan_dyn arith(flan_dyn a, flan_dyn b, char op) {
} }
} }
flan_dyn flan_dyn_add(flan_dyn a, flan_dyn b) { return arith(a, b, '+'); } flan_dyn flan_dyn_add(flan_dyn a, flan_dyn b, const uint8_t *loc,
flan_dyn flan_dyn_sub(flan_dyn a, flan_dyn b) { return arith(a, b, '-'); } int64_t loclen) {
flan_dyn flan_dyn_mul(flan_dyn a, flan_dyn b) { return arith(a, b, '*'); } (void)loc; (void)loclen;
flan_dyn flan_dyn_div(flan_dyn a, flan_dyn b) { return arith(a, b, '/'); } return arith(a, b, '+');
flan_dyn flan_dyn_rem(flan_dyn a, flan_dyn b) { return arith(a, b, '%'); } }
flan_dyn flan_dyn_sub(flan_dyn a, flan_dyn b, const uint8_t *loc,
int64_t loclen) {
(void)loc; (void)loclen;
return arith(a, b, '-');
}
flan_dyn flan_dyn_mul(flan_dyn a, flan_dyn b, const uint8_t *loc,
int64_t loclen) {
(void)loc; (void)loclen;
return arith(a, b, '*');
}
flan_dyn flan_dyn_div(flan_dyn a, flan_dyn b, const uint8_t *loc,
int64_t loclen) {
(void)loc; (void)loclen;
return arith(a, b, '/');
}
flan_dyn flan_dyn_rem(flan_dyn a, flan_dyn b, const uint8_t *loc,
int64_t loclen) {
(void)loc; (void)loclen;
return arith(a, b, '%');
}
/* ── Ordering and equality ─────────────────────────────────────────── */ /* ── Ordering and equality ─────────────────────────────────────────── */
@ -173,10 +193,26 @@ static int cmp(flan_dyn a, flan_dyn b) {
} }
} }
flan_dyn flan_dyn_lt(flan_dyn a, flan_dyn b) { return flan_dyn_from_bool(cmp(a, b) < 0); } flan_dyn flan_dyn_lt(flan_dyn a, flan_dyn b, const uint8_t *loc,
flan_dyn flan_dyn_le(flan_dyn a, flan_dyn b) { return flan_dyn_from_bool(cmp(a, b) <= 0); } int64_t loclen) {
flan_dyn flan_dyn_gt(flan_dyn a, flan_dyn b) { return flan_dyn_from_bool(cmp(a, b) > 0); } (void)loc; (void)loclen;
flan_dyn flan_dyn_ge(flan_dyn a, flan_dyn b) { return flan_dyn_from_bool(cmp(a, b) >= 0); } return flan_dyn_from_bool(cmp(a, b) < 0);
}
flan_dyn flan_dyn_le(flan_dyn a, flan_dyn b, const uint8_t *loc,
int64_t loclen) {
(void)loc; (void)loclen;
return flan_dyn_from_bool(cmp(a, b) <= 0);
}
flan_dyn flan_dyn_gt(flan_dyn a, flan_dyn b, const uint8_t *loc,
int64_t loclen) {
(void)loc; (void)loclen;
return flan_dyn_from_bool(cmp(a, b) > 0);
}
flan_dyn flan_dyn_ge(flan_dyn a, flan_dyn b, const uint8_t *loc,
int64_t loclen) {
(void)loc; (void)loclen;
return flan_dyn_from_bool(cmp(a, b) >= 0);
}
/* Structural, and never traps — the header's one exception. */ /* Structural, and never traps — the header's one exception. */
static int eq(cell *x, cell *y) { static int eq(cell *x, cell *y) {

View File

@ -40,6 +40,20 @@
* rather than a hand-copied list. */ * rather than a hand-copied list. */
#include "flan_dyn.h" #include "flan_dyn.h"
/* The nine trapping operators took a site — (loc, len) — when flan_dyn.c's
* traps learned to print a file and a line. This file calls the runtime
* directly and has no source position to offer, so it passes (NULL, 0), which
* prints the sentence exactly as it printed before. */
#define FDYN_add(a, b) flan_dyn_add((a), (b), NULL, 0)
#define FDYN_sub(a, b) flan_dyn_sub((a), (b), NULL, 0)
#define FDYN_mul(a, b) flan_dyn_mul((a), (b), NULL, 0)
#define FDYN_div(a, b) flan_dyn_div((a), (b), NULL, 0)
#define FDYN_rem(a, b) flan_dyn_rem((a), (b), NULL, 0)
#define FDYN_lt(a, b) flan_dyn_lt((a), (b), NULL, 0)
#define FDYN_le(a, b) flan_dyn_le((a), (b), NULL, 0)
#define FDYN_gt(a, b) flan_dyn_gt((a), (b), NULL, 0)
#define FDYN_ge(a, b) flan_dyn_ge((a), (b), NULL, 0)
void flan_rt_init(int32_t argc, char **argv); void flan_rt_init(int32_t argc, char **argv);
void flan_vec_free(void *v, int64_t size, int64_t align, const uint8_t *loc, void flan_vec_free(void *v, int64_t size, int64_t align, const uint8_t *loc,
int64_t loclen); int64_t loclen);
@ -161,41 +175,41 @@ static void ops(void) {
"a boxed int is still an int"); "a boxed int is still an int");
/* Arithmetic. Two ints answer an int; a float anywhere answers a float. */ /* Arithmetic. Two ints answer an int; a float anywhere answers a float. */
check(num(flan_dyn_add(flan_dyn_from_i64(2), flan_dyn_from_i64(3))) == 5, "+"); check(num(FDYN_add(flan_dyn_from_i64(2), flan_dyn_from_i64(3))) == 5, "+");
check(num(flan_dyn_sub(flan_dyn_from_i64(2), flan_dyn_from_i64(3))) == -1, "-"); check(num(FDYN_sub(flan_dyn_from_i64(2), flan_dyn_from_i64(3))) == -1, "-");
check(num(flan_dyn_mul(flan_dyn_from_i64(2), flan_dyn_from_i64(3))) == 6, "*"); check(num(FDYN_mul(flan_dyn_from_i64(2), flan_dyn_from_i64(3))) == 6, "*");
check(num(flan_dyn_div(flan_dyn_from_i64(7), flan_dyn_from_i64(2))) == 3, "/"); check(num(FDYN_div(flan_dyn_from_i64(7), flan_dyn_from_i64(2))) == 3, "/");
check(num(flan_dyn_rem(flan_dyn_from_i64(7), flan_dyn_from_i64(2))) == 1, "%"); check(num(FDYN_rem(flan_dyn_from_i64(7), flan_dyn_from_i64(2))) == 1, "%");
check(num(flan_dyn_rem(flan_dyn_from_i64(-7), flan_dyn_from_i64(2))) == -1, check(num(FDYN_rem(flan_dyn_from_i64(-7), flan_dyn_from_i64(2))) == -1,
"% keeps the sign of the dividend"); "% keeps the sign of the dividend");
check(flan_dyn_need_f64( check(flan_dyn_need_f64(
flan_dyn_add(flan_dyn_from_i64(1), flan_dyn_from_f64(0.5))) == 1.5, FDYN_add(flan_dyn_from_i64(1), flan_dyn_from_f64(0.5))) == 1.5,
"int and float promote"); "int and float promote");
check(flan_dyn_need_f64( check(flan_dyn_need_f64(
flan_dyn_div(flan_dyn_from_f64(1.0), flan_dyn_from_f64(4.0))) == 0.25, FDYN_div(flan_dyn_from_f64(1.0), flan_dyn_from_f64(4.0))) == 0.25,
"float /"); "float /");
check(flan_dyn_need_f64( check(flan_dyn_need_f64(
flan_dyn_rem(flan_dyn_from_f64(7.5), flan_dyn_from_f64(2.0))) == 1.5, FDYN_rem(flan_dyn_from_f64(7.5), flan_dyn_from_f64(2.0))) == 1.5,
"float %"); "float %");
/* The boxed end of the range arithmetically, not only as a round trip. */ /* The boxed end of the range arithmetically, not only as a round trip. */
check(num(flan_dyn_add(flan_dyn_from_i64(140737488355327LL), check(num(FDYN_add(flan_dyn_from_i64(140737488355327LL),
flan_dyn_from_i64(1))) == 140737488355328LL, flan_dyn_from_i64(1))) == 140737488355328LL,
"+ crosses into the box"); "+ crosses into the box");
/* Ordering. Numbers against numbers across the two tags, text bytewise, and /* Ordering. Numbers against numbers across the two tags, text bytewise, and
a NaN that is none of less, equal or greater. */ a NaN that is none of less, equal or greater. */
check(truth(flan_dyn_lt(flan_dyn_from_i64(1), flan_dyn_from_i64(2))), "<"); check(truth(FDYN_lt(flan_dyn_from_i64(1), flan_dyn_from_i64(2))), "<");
check(!truth(flan_dyn_lt(flan_dyn_from_i64(2), flan_dyn_from_i64(2))), "< eq"); check(!truth(FDYN_lt(flan_dyn_from_i64(2), flan_dyn_from_i64(2))), "< eq");
check(truth(flan_dyn_le(flan_dyn_from_i64(2), flan_dyn_from_i64(2))), "<="); check(truth(FDYN_le(flan_dyn_from_i64(2), flan_dyn_from_i64(2))), "<=");
check(truth(flan_dyn_gt(flan_dyn_from_f64(2.5), flan_dyn_from_i64(2))), ">"); check(truth(FDYN_gt(flan_dyn_from_f64(2.5), flan_dyn_from_i64(2))), ">");
check(truth(flan_dyn_ge(flan_dyn_from_i64(2), flan_dyn_from_f64(2.0))), ">="); check(truth(FDYN_ge(flan_dyn_from_i64(2), flan_dyn_from_f64(2.0))), ">=");
check(truth(flan_dyn_lt(text("abc"), text("abd"))), "< text"); check(truth(FDYN_lt(text("abc"), text("abd"))), "< text");
check(truth(flan_dyn_lt(text("ab"), text("abc"))), "< text prefix"); check(truth(FDYN_lt(text("ab"), text("abc"))), "< text prefix");
check(!truth(flan_dyn_lt(text("abc"), text("abc"))), "< text equal"); check(!truth(FDYN_lt(text("abc"), text("abc"))), "< text equal");
{ {
flan_dyn n = flan_dyn_from_f64(0.0 / 0.0), one = flan_dyn_from_i64(1); flan_dyn n = flan_dyn_from_f64(0.0 / 0.0), one = flan_dyn_from_i64(1);
check(!truth(flan_dyn_lt(n, one)) && !truth(flan_dyn_gt(n, one)) check(!truth(FDYN_lt(n, one)) && !truth(FDYN_gt(n, one))
&& !truth(flan_dyn_le(n, one)) && !truth(flan_dyn_ge(n, one)), && !truth(FDYN_le(n, one)) && !truth(FDYN_ge(n, one)),
"nan is unordered in all four directions"); "nan is unordered in all four directions");
} }
@ -952,27 +966,27 @@ static void desc(void) {
static void refuse(const char *what) { static void refuse(const char *what) {
flan_dyn v = flan_dyn_vec_new(); flan_dyn v = flan_dyn_vec_new();
flan_dyn t = text("hi"); flan_dyn t = text("hi");
if (strcmp(what, "add") == 0) (void)flan_dyn_add(flan_dyn_from_i64(3), t); if (strcmp(what, "add") == 0) (void)FDYN_add(flan_dyn_from_i64(3), t);
else if (strcmp(what, "sub") == 0) else if (strcmp(what, "sub") == 0)
(void)flan_dyn_sub(flan_dyn_nil(), flan_dyn_from_i64(1)); (void)FDYN_sub(flan_dyn_nil(), flan_dyn_from_i64(1));
else if (strcmp(what, "mul") == 0) else if (strcmp(what, "mul") == 0)
(void)flan_dyn_mul(flan_dyn_from_bool(1), flan_dyn_from_i64(2)); (void)FDYN_mul(flan_dyn_from_bool(1), flan_dyn_from_i64(2));
else if (strcmp(what, "div") == 0) else if (strcmp(what, "div") == 0)
(void)flan_dyn_div(v, flan_dyn_from_i64(2)); (void)FDYN_div(v, flan_dyn_from_i64(2));
else if (strcmp(what, "rem") == 0) else if (strcmp(what, "rem") == 0)
(void)flan_dyn_rem(flan_dyn_from_i64(2), flan_dyn_nil()); (void)FDYN_rem(flan_dyn_from_i64(2), flan_dyn_nil());
else if (strcmp(what, "divzero") == 0) else if (strcmp(what, "divzero") == 0)
(void)flan_dyn_div(flan_dyn_from_i64(1), flan_dyn_from_i64(0)); (void)FDYN_div(flan_dyn_from_i64(1), flan_dyn_from_i64(0));
else if (strcmp(what, "remzero") == 0) else if (strcmp(what, "remzero") == 0)
(void)flan_dyn_rem(flan_dyn_from_i64(1), flan_dyn_from_i64(0)); (void)FDYN_rem(flan_dyn_from_i64(1), flan_dyn_from_i64(0));
else if (strcmp(what, "divover") == 0) else if (strcmp(what, "divover") == 0)
(void)flan_dyn_div(flan_dyn_from_i64(INT64_MIN), flan_dyn_from_i64(-1)); (void)FDYN_div(flan_dyn_from_i64(INT64_MIN), flan_dyn_from_i64(-1));
else if (strcmp(what, "lt") == 0) else if (strcmp(what, "lt") == 0)
(void)flan_dyn_lt(flan_dyn_from_i64(1), t); (void)FDYN_lt(flan_dyn_from_i64(1), t);
else if (strcmp(what, "le") == 0) (void)flan_dyn_le(t, flan_dyn_nil()); else if (strcmp(what, "le") == 0) (void)FDYN_le(t, flan_dyn_nil());
else if (strcmp(what, "gt") == 0) (void)flan_dyn_gt(v, v); else if (strcmp(what, "gt") == 0) (void)FDYN_gt(v, v);
else if (strcmp(what, "ge") == 0) else if (strcmp(what, "ge") == 0)
(void)flan_dyn_ge(flan_dyn_from_bool(0), flan_dyn_from_bool(1)); (void)FDYN_ge(flan_dyn_from_bool(0), flan_dyn_from_bool(1));
else if (strcmp(what, "len") == 0) (void)flan_dyn_len(flan_dyn_from_i64(1)); else if (strcmp(what, "len") == 0) (void)flan_dyn_len(flan_dyn_from_i64(1));
else if (strcmp(what, "at") == 0) else if (strcmp(what, "at") == 0)
(void)flan_dyn_at(flan_dyn_from_i64(3), flan_dyn_from_i64(0)); (void)flan_dyn_at(flan_dyn_from_i64(3), flan_dyn_from_i64(0));

View File

@ -1555,6 +1555,12 @@ let () =
check "the x86 backend roots its dyn values" check "the x86 backend roots its dyn values"
(contains dyn_asm "flan_dyn_root_push" (contains dyn_asm "flan_dyn_root_push"
&& contains dyn_asm "flan_dyn_root_pop"); && contains dyn_asm "flan_dyn_root_pop");
(* The site travels with the operands, on this backend as on the other. A
dyn arithmetic trap is the type error of a dynamic program and it used to
print with no file and no line; the string literal below is what the
runtime prints as a GNU prefix in front of the sentence. *)
check "the x86 backend hands the dyn operators their site"
(contains dyn_asm "<test>:1:21");
(* And a program with no dyn in it emits not one byte of any of it, which is (* And a program with no dyn in it emits not one byte of any of it, which is
what lets the sweep's other MATCHes stand as a regression check on this what lets the sweep's other MATCHes stand as a regression check on this
lane rather than being re-measured by it. *) lane rather than being re-measured by it. *)