diff --git a/fec/src/ir.c b/fec/src/ir.c index 6b5f117..5ee0072 100644 --- a/fec/src/ir.c +++ b/fec/src/ir.c @@ -5,7 +5,7 @@ void fe_ir_module_init(FeIrModule *m) { fe_arena_init(&m->arena, 16384); - m->unit_file = ""; + m->file_count = 0; m->entry_main = 0; m->funcs = 0; m->last_func = 0; @@ -13,6 +13,20 @@ void fe_ir_module_init(FeIrModule *m) m->last_global = 0; } +/* The index of this path in the module's file table, adding it if it is new. + Traps carry the index rather than the string so the generator emits each + name once. */ +unsigned fe_ir_file(FeIrModule *m, const char *path) +{ + unsigned i; + if (!path) path = ""; + for (i = 0; i < m->file_count; ++i) + if (!strcmp(m->files[i], path)) return i; + if (m->file_count >= FE_IR_FILE_MAX) return 0; + m->files[m->file_count] = path; + return m->file_count++; +} + void fe_ir_module_destroy(FeIrModule *m) { fe_arena_destroy(&m->arena); @@ -301,13 +315,15 @@ void fe_ir_ret(FeIrBlock *b, unsigned value, int has_value) b->has_ret_value = has_value; } -void fe_ir_trap(FeIrBlock *b, FeIrTrap reason, unsigned long line) +void fe_ir_trap(FeIrBlock *b, FeIrTrap reason, unsigned long line, + unsigned file) { if (b->terminated) return; b->terminated = 1; b->term = FE_IR_TRAP; b->trap = reason; b->trap_line = line; + b->trap_file = file; } const char *fe_ir_type_name(FeIrType t) @@ -432,8 +448,8 @@ void fe_ir_dump(const FeIrModule *m, FILE *out) const FeIrValue *v; const FeIrGlobal *g; unsigned i; - if (m->unit_file && m->unit_file[0]) - fprintf(out, "; unit file %s\n", m->unit_file); + for (i = 0; i < m->file_count; ++i) + fprintf(out, "; file %u %s\n", i, m->files[i]); for (g = m->globals; g; g = g->next) fprintf(out, "global @%s : %s %lu\n", g->name, fe_ir_type_name(g->type), g->size); diff --git a/fec/src/ir.h b/fec/src/ir.h index beff64f..8801844 100644 --- a/fec/src/ir.h +++ b/fec/src/ir.h @@ -4,6 +4,9 @@ #include "arena.h" #include +/* How many source files one build can trap from. */ +#define FE_IR_FILE_MAX 64 + /* The intermediate representation. `IR.md` is the description; this is the shape it takes in memory. @@ -88,6 +91,7 @@ typedef struct FeIrBlock { int has_ret_value; FeIrTrap trap; unsigned long trap_line; + unsigned trap_file; /* index into the module's file table */ /* Set once a terminator is chosen. Lowering asks before appending a jump, so a `return` inside a branch is not overwritten by the jump to the join block. */ @@ -142,7 +146,10 @@ typedef struct FeIrGlobal { typedef struct FeIrModule { FeArena arena; - const char *unit_file; /* the one file-name string a unit's traps share */ + /* Every unit in the build lands in one module, so a trap has to say which + file it came from rather than share one name with the whole program. */ + const char *files[FE_IR_FILE_MAX]; + unsigned file_count; /* The entry unit's `main`, if it has one. The runtime's start stub calls a fixed name, so the generator emits a jump to this one. */ const char *entry_main; @@ -152,6 +159,7 @@ typedef struct FeIrModule { FeIrGlobal *last_global; } FeIrModule; +unsigned fe_ir_file(FeIrModule *m, const char *path); void fe_ir_module_init(FeIrModule *m); void fe_ir_module_destroy(FeIrModule *m); @@ -198,7 +206,8 @@ void fe_ir_copy(FeIrModule *m, FeIrBlock *b, FeIrPlace dst, FeIrPlace src, void fe_ir_jmp(FeIrBlock *b, unsigned target); void fe_ir_br(FeIrBlock *b, unsigned cond, unsigned t, unsigned f); void fe_ir_ret(FeIrBlock *b, unsigned value, int has_value); -void fe_ir_trap(FeIrBlock *b, FeIrTrap reason, unsigned long line); +void fe_ir_trap(FeIrBlock *b, FeIrTrap reason, unsigned long line, + unsigned file); void fe_ir_dump(const FeIrModule *m, FILE *out); const char *fe_ir_type_name(FeIrType t); diff --git a/fec/src/lower.c b/fec/src/lower.c index 41ff46e..dbfc439 100644 --- a/fec/src/lower.c +++ b/fec/src/lower.c @@ -199,6 +199,13 @@ FeIrBlock *new_block(Lower *L) return fe_ir_block(L->m, L->fn); } +/* Which file a trap raised right now came from. The whole build lowers into + one module, so the unit being lowered is the only thing that knows. */ +unsigned trap_file(Lower *L) +{ + return fe_ir_file(L->m, L->c->unit ? L->c->unit->path : ""); +} + /* A check that must hold. `ok` is a condition; when it is false the program stops where it is. `--no-checks` removes the comparison and the branch, not just the message, which is the whole point of the flag. */ @@ -208,7 +215,7 @@ void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line) FeIrBlock *cont = new_block(L); fe_ir_br(L->b, ok, cont->id, bad->id); L->b = bad; - fe_ir_trap(L->b, reason, line); + fe_ir_trap(L->b, reason, line, trap_file(L)); L->b = cont; } @@ -403,13 +410,13 @@ int lower_builtin(Lower *L, FeNode *n, Slot *out) const char *name = n->text; if (!name || name[0] != '@') return 0; if (!strcmp(name, "@trap")) { - fe_ir_trap(L->b, FE_TRAP_EXPLICIT, n->loc.line); + fe_ir_trap(L->b, FE_TRAP_EXPLICIT, n->loc.line, trap_file(L)); L->b = new_block(L); *out = slot_void(); return 1; } if (!strcmp(name, "@unreachable")) { - fe_ir_trap(L->b, FE_TRAP_UNREACHABLE, n->loc.line); + fe_ir_trap(L->b, FE_TRAP_UNREACHABLE, n->loc.line, trap_file(L)); L->b = new_block(L); *out = slot_void(); return 1; diff --git a/fec/src/lowerpri.h b/fec/src/lowerpri.h index 9ebd779..6b87c6b 100644 --- a/fec/src/lowerpri.h +++ b/fec/src/lowerpri.h @@ -121,6 +121,7 @@ unsigned declare_var(Lower *L, const char *cname, const FeType *t, int release_flag(Lower *L, unsigned local, unsigned *flag); LowerVar *find_var(Lower *L, const char *cname); FeIrBlock *new_block(Lower *L); +unsigned trap_file(Lower *L); void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line); FeIrType tag_type(const FeType *t); int uses_niche(const FeType *t); diff --git a/fec/src/lowerstm.c b/fec/src/lowerstm.c index df09f18..cfc5f5d 100644 --- a/fec/src/lowerstm.c +++ b/fec/src/lowerstm.c @@ -631,7 +631,6 @@ int fe_lower_program(FeCheck *c, FeIrModule *out) c->ast = &unit->ast; c->unit = unit; c->types.unit_name = unit->name[0] ? unit->name : "unit"; - if (!out->unit_file || !out->unit_file[0]) out->unit_file = unit->path; for (n = unit->ast.root ? unit->ast.root->children : 0; n; n = n->next) if (n->kind == FE_N_GLOBAL || n->kind == FE_N_CONST) lower_global(&L, n); diff --git a/fec/src/x86.c b/fec/src/x86.c index 98e5824..69315b7 100644 --- a/fec/src/x86.c +++ b/fec/src/x86.c @@ -548,7 +548,8 @@ static void emit_func(const FeIrModule *m, const FeIrFunc *f, FILE *out) break; case FE_IR_TRAP: fprintf(out, " push %lu\n", b->trap_line); - fprintf(out, " push offset FE_UNIT_FILE\n"); + fprintf(out, " push offset FE_FILE_%u\n", + b->trap_file); fprintf(out, " push %u\n", (unsigned)b->trap); fprintf(out, " call fe_trap\n"); fprintf(out, " add esp, 12\n"); @@ -587,6 +588,7 @@ void fe_x86_emit(const FeIrModule *m, FILE *out) const FeIrGlobal *g; int any_trap = 0; const FeIrBlock *b; + unsigned i; for (f = m->funcs; f && !any_trap; f = f->next) for (b = f->first; b; b = b->next) @@ -623,9 +625,12 @@ void fe_x86_emit(const FeIrModule *m, FILE *out) if (any_trap) fputs("extern fe_trap : near\n", out); fputs("\n_DATA segment dword public 'DATA'\n", out); - if (any_trap) { - fputs("public FE_UNIT_FILE\nFE_UNIT_FILE label byte\n", out); - emit_string(m->unit_file, out); + /* One name per file a trap can come from. A build is many units in + one module, and a trap that names the wrong file is worse than + one that names none. */ + for (i = 0; i < m->file_count; ++i) { + fprintf(out, "public FE_FILE_%u\nFE_FILE_%u label byte\n", i, i); + emit_string(m->files[i], out); } for (g = m->globals; g; g = g->next) { unsigned long i; diff --git a/fec/tests/exec/lexer/parse.fe b/fec/tests/exec/lexer/parse.fe index ffd5d2b..50f759d 100644 --- a/fec/tests/exec/lexer/parse.fe +++ b/fec/tests/exec/lexer/parse.fe @@ -23,10 +23,8 @@ pub struct Parser { pub errors: usize, pub fn on(src: []u8) -> !Self { - let nodes: list.List(ast.Node) = - try list.List(ast.Node).with_capacity(16); var p: Self = Self{ - nodes: nodes, + nodes: try list.List(ast.Node).with_capacity(16), at: 0, line: 1, cur: tok.Token{ kind: tok.Kind.End, from: 0, len: 0, line: 1 }, diff --git a/fec/tests/exec/trapsrc/main.fe b/fec/tests/exec/trapsrc/main.fe new file mode 100644 index 0000000..12f89d7 --- /dev/null +++ b/fec/tests/exec/trapsrc/main.fe @@ -0,0 +1,20 @@ +// EXIT:3 +// OUTPUT:index out of bounds +// OUTPUT:pick.fe:6 +// NOCHECKS:0 +unit main; + +import pick; + +// A build is many units in one module. A trap has to name the file the check +// was written in, not the file the program was started from. + +const S: str = "abc"; + +fn main() -> i32 { + let c: u8 = pick.at(S, 9); + // Without the checks the read runs off the end and `c` is whatever + // was there, so nothing is decided by its value. + if c == 0 { return 0; } + return 0; +} diff --git a/fec/tests/exec/trapsrc/pick.fe b/fec/tests/exec/trapsrc/pick.fe new file mode 100644 index 0000000..628d989 --- /dev/null +++ b/fec/tests/exec/trapsrc/pick.fe @@ -0,0 +1,7 @@ +unit pick; + +// The failing index is here, two units away from the program that runs it. + +pub fn at(s: []u8, i: usize) -> u8 { + return s[i]; +}