ww: implement package initialization
This commit is contained in:
744
cmd/wcc/check.c
744
cmd/wcc/check.c
@@ -7,6 +7,8 @@
|
||||
* diagnostics from one run. Nodes get their resolved Type attached.
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static void cstmt(Checker*, Node*);
|
||||
@@ -1448,6 +1450,7 @@ cexpr(Checker *c, Node *n)
|
||||
* here; the DOT case below resolves the qualified symbol. */
|
||||
if (s->kind == SK_USE)
|
||||
return n->type = ty_err;
|
||||
n->refdecl = s->decl;
|
||||
n->type = s->type;
|
||||
return s->type;
|
||||
}
|
||||
@@ -1499,8 +1502,10 @@ cexpr(Checker *c, Node *n)
|
||||
return n->type = err(c, n->pos,
|
||||
"package '%s' has no exported declaration '%s'",
|
||||
n->lhs->str, n->str);
|
||||
if (fs)
|
||||
if (fs) {
|
||||
n->refdecl = fs->decl;
|
||||
return n->type = fs->type;
|
||||
}
|
||||
/* A bare `w6c -T` intentionally leaves the
|
||||
* compiler-generated support.run hook external; the
|
||||
* ordinary driver supplies lib/test. This is the only
|
||||
@@ -3006,7 +3011,8 @@ src_imports(Node *file, const char *modtag, int source, const char *name)
|
||||
{
|
||||
if (file == NULL || name == NULL || name[0] == '\0') return 0;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->sourceid != source) continue;
|
||||
if (u->kind != N_USE || u->useblank
|
||||
|| u->sourceid != source) continue;
|
||||
/* Skip self-imports: lib/fmt/fmt_test.ww carries `use fmt;`
|
||||
* even though its module tag is also "fmt"; that directive
|
||||
* doesn't introduce a foreign module bareword and lib/fmt's
|
||||
@@ -3132,6 +3138,680 @@ top_decl_kind(Node *d)
|
||||
|| d->kind == N_FNDECL || d->kind == N_LET);
|
||||
}
|
||||
|
||||
static int
|
||||
init_private_symbol(Node *d)
|
||||
{
|
||||
for (Node *a = d ? d->attr : NULL; a; a = a->next) {
|
||||
if (a->kind != N_ATTR || a->str == NULL
|
||||
|| strcmp(a->str, "symbol") != 0 || a->list == NULL
|
||||
|| a->list->kind != N_STRLIT || a->list->str == NULL)
|
||||
continue;
|
||||
if (strncmp(a->list->str, "__ww..", 6) == 0)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Mutable module lets are WW's Go-variable analogue. Literal data stays in
|
||||
* the archive's static image; every surviving value computation is moved to
|
||||
* the package task. `def` and `const` deliberately remain outside this path. */
|
||||
static Node *
|
||||
init_strip_cast(Node *n)
|
||||
{
|
||||
while (n != NULL && n->kind == N_CAST) n = n->lhs;
|
||||
return n;
|
||||
}
|
||||
|
||||
static int init_expr_static(Type *, Node *);
|
||||
static int init_array_static(Type *, Node *, unsigned);
|
||||
|
||||
static int
|
||||
init_fnptr_static(Node *n)
|
||||
{
|
||||
Node *r = init_strip_cast(n);
|
||||
if (r == NULL || r->kind != N_UN || r->op != TK_AMP) return 0;
|
||||
Node *v = init_strip_cast(r->lhs);
|
||||
Type *vt = type_chase_named(v ? v->type : NULL);
|
||||
if (v == NULL || vt == NULL || vt->kind != TY_FN) return 0;
|
||||
if (v->kind == N_IDENT) return 1;
|
||||
return v->kind == N_DOT && v->lhs != NULL
|
||||
&& v->lhs->kind == N_IDENT
|
||||
&& (v->lhs->type == NULL || v->lhs->type == ty_err);
|
||||
}
|
||||
|
||||
static int
|
||||
init_float_static(Node *n)
|
||||
{
|
||||
Node *r = init_strip_cast(n);
|
||||
if (r != NULL && r->kind == N_UN
|
||||
&& (r->op == TK_PLUS || r->op == TK_MINUS))
|
||||
r = init_strip_cast(r->lhs);
|
||||
return r != NULL && r->kind == N_FLOATLIT;
|
||||
}
|
||||
|
||||
static int
|
||||
init_tagged_raw_static(Type *u, Node *n)
|
||||
{
|
||||
Node *r = init_strip_cast(n);
|
||||
Type *ru = type_chase_named(r ? r->type : NULL);
|
||||
u64 ignored;
|
||||
return u != NULL && u->kind == TY_TAGGED && !u->nullable
|
||||
&& r != NULL && variant_present(u->params, r->type)
|
||||
&& (ru == NULL || (ru->kind != TY_STR && ru->kind != TY_SLICE))
|
||||
&& fold_int_literal(r, &ignored);
|
||||
}
|
||||
|
||||
static int
|
||||
init_tuple_static(Type *u, Node *n)
|
||||
{
|
||||
Node *r = init_strip_cast(n);
|
||||
if (u == NULL || u->kind != TY_TUPLE
|
||||
|| r == NULL || r->kind != N_TUPLE)
|
||||
return 0;
|
||||
Tparam *tp = u->params;
|
||||
for (Node *e = r->list; e; e = e->next) {
|
||||
if (tp == NULL) return 0;
|
||||
Node *v = init_strip_cast(e);
|
||||
Type *et = type_chase_named(tp->type);
|
||||
if (v == NULL || (et != NULL && et->kind == TY_TAGGED)) return 0;
|
||||
if (et != NULL && (et->kind == TY_STR || et->kind == TY_SLICE)) {
|
||||
if (v->kind != N_STRLIT) return 0;
|
||||
} else if (!init_fnptr_static(v)) {
|
||||
u64 ignored;
|
||||
if (!fold_int_literal(v, &ignored)) return 0;
|
||||
}
|
||||
tp = tp->next;
|
||||
}
|
||||
return tp == NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
init_struct_static(Type *u, Node *n)
|
||||
{
|
||||
Node *r = init_strip_cast(n);
|
||||
if (u == NULL || u->kind != TY_STRUCT
|
||||
|| r == NULL || r->kind != N_STRUCTLIT)
|
||||
return 0;
|
||||
for (Tfield *f = u->fields; f; f = f->next) {
|
||||
Node *value = NULL;
|
||||
for (Node *e = r->list; e; e = e->next)
|
||||
if (e->str != NULL && f->name != NULL
|
||||
&& strcmp(e->str, f->name) == 0) {
|
||||
value = e->lhs;
|
||||
break;
|
||||
}
|
||||
if (value != NULL) {
|
||||
Type *fu = type_chase_named(f->type);
|
||||
int ok = 0;
|
||||
if (fu != NULL && fu->kind == TY_TAGGED && !fu->nullable)
|
||||
ok = init_tagged_raw_static(fu, value);
|
||||
else if (fu != NULL && fu->kind == TY_STRUCT)
|
||||
ok = init_struct_static(fu, value);
|
||||
else if (fu != NULL && fu->kind == TY_ARRAY)
|
||||
ok = init_array_static(fu, value, 1);
|
||||
else if (type_isfloat(f->type))
|
||||
ok = init_float_static(value);
|
||||
else {
|
||||
u64 ignored;
|
||||
ok = fold_int_literal(init_strip_cast(value), &ignored);
|
||||
}
|
||||
if (!ok) return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define INIT_ARR_REPEAT 1u
|
||||
#define INIT_ARR_STR_RELOC 2u
|
||||
#define INIT_ARR_TUPLE_ROWS 4u
|
||||
|
||||
static int
|
||||
init_array_static(Type *u, Node *n, unsigned flags)
|
||||
{
|
||||
Node *r = init_strip_cast(n);
|
||||
if (u == NULL || (u->kind != TY_ARRAY && u->kind != TY_SLICE)
|
||||
|| r == NULL || r->kind != N_ARRLIT)
|
||||
return 0;
|
||||
Type *et = u->sub;
|
||||
Type *eu = type_chase_named(et);
|
||||
int seen = 0;
|
||||
for (Node *e = r->list; e; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str != NULL
|
||||
&& strcmp(e->str, "...") == 0)
|
||||
return (flags & INIT_ARR_REPEAT) != 0 && seen > 0
|
||||
&& e->next == NULL
|
||||
&& (eu == NULL || eu->kind != TY_ARRAY);
|
||||
Node *v = init_strip_cast(e);
|
||||
if (v == NULL) return 0;
|
||||
if (eu != NULL && eu->kind == TY_STR) {
|
||||
if ((flags & INIT_ARR_STR_RELOC) == 0
|
||||
|| v->kind != N_STRLIT) return 0;
|
||||
} else if (eu != NULL && eu->kind == TY_STRUCT) {
|
||||
if (!init_struct_static(eu, v)) return 0;
|
||||
} else if (eu != NULL && eu->kind == TY_ARRAY) {
|
||||
if (!init_array_static(eu, v, INIT_ARR_REPEAT)) return 0;
|
||||
} else if (eu != NULL && eu->kind == TY_TUPLE) {
|
||||
if ((flags & INIT_ARR_TUPLE_ROWS) == 0
|
||||
|| !init_tuple_static(eu, v)) return 0;
|
||||
} else if (eu != NULL && eu->kind == TY_TAGGED) {
|
||||
if (!init_tagged_raw_static(eu, v)) return 0;
|
||||
} else if (eu != NULL && (eu->kind == TY_SLICE
|
||||
|| eu->kind == TY_PTR
|
||||
|| eu->kind == TY_FN)) {
|
||||
return 0;
|
||||
} else if (type_isfloat(et)) {
|
||||
if (!init_float_static(v)) return 0;
|
||||
} else {
|
||||
u64 ignored;
|
||||
if (!fold_int_literal(v, &ignored)) return 0;
|
||||
}
|
||||
seen++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* This predicate is the checker's validate-only twin of cgen's static-data
|
||||
* arms. A true result must be safe to emit; every other valid mutable value
|
||||
* is zero-backed and evaluated exactly once by the package task. */
|
||||
static int
|
||||
init_expr_static(Type *t, Node *n)
|
||||
{
|
||||
Node *r = init_strip_cast(n);
|
||||
if (r == NULL) return 1;
|
||||
Type *u = type_chase_named(t);
|
||||
if (type_isfloat(t)) return init_float_static(r);
|
||||
if (u != NULL && (u->kind == TY_STR || u->kind == TY_UNTYPED_STR))
|
||||
return r->kind == N_STRLIT || r->kind == N_NIL;
|
||||
if (u != NULL && u->kind == TY_ARRAY)
|
||||
return init_array_static(u, r,
|
||||
INIT_ARR_REPEAT | INIT_ARR_STR_RELOC);
|
||||
if (u != NULL && u->kind == TY_STRUCT)
|
||||
return init_struct_static(u, r);
|
||||
if (u != NULL && u->kind == TY_TUPLE)
|
||||
return init_tuple_static(u, r);
|
||||
if (u != NULL && u->kind == TY_SLICE) {
|
||||
if (r->kind == N_NIL) return 1;
|
||||
return init_array_static(u, r, INIT_ARR_TUPLE_ROWS);
|
||||
}
|
||||
if (u != NULL && u->kind == TY_TAGGED && !u->nullable) {
|
||||
Type *ru = type_chase_named(r->type);
|
||||
if (!variant_present(u->params, r->type)) return 0;
|
||||
if (ru != NULL && (ru->kind == TY_STR || ru->kind == TY_SLICE))
|
||||
return r->kind == N_STRLIT;
|
||||
return init_tagged_raw_static(u, r);
|
||||
}
|
||||
if (init_fnptr_static(r)) return 1;
|
||||
u64 ignored;
|
||||
return fold_int_literal(r, &ignored);
|
||||
}
|
||||
|
||||
struct initwalkitem {
|
||||
Node *node;
|
||||
struct initwalkitem *next;
|
||||
};
|
||||
|
||||
struct initwalk {
|
||||
struct initwalkitem *stack;
|
||||
struct initwalkitem *seenfn;
|
||||
};
|
||||
|
||||
static int
|
||||
initwalk_push(struct initwalkitem **head, Node *x)
|
||||
{
|
||||
if (x == NULL) return 0;
|
||||
struct initwalkitem *p = malloc(sizeof *p);
|
||||
if (p == NULL) return -1;
|
||||
p->node = x;
|
||||
p->next = *head;
|
||||
*head = p;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
initwalk_seen_fn(struct initwalk *w, Node *fn)
|
||||
{
|
||||
for (struct initwalkitem *p = w->seenfn; p; p = p->next)
|
||||
if (p->node == fn) return 1;
|
||||
if (initwalk_push(&w->seenfn, fn) < 0) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
initwalk_free(struct initwalkitem *p)
|
||||
{
|
||||
while (p != NULL) {
|
||||
struct initwalkitem *next = p->next;
|
||||
free(p);
|
||||
p = next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Does variable `from` depend on `target`? Function nodes are transparent,
|
||||
* as in go/types initOrder: references in their bodies become variable edges. */
|
||||
static int
|
||||
init_refers(Node *from, Node *target)
|
||||
{
|
||||
struct initwalk w = {0};
|
||||
int result = 0;
|
||||
if (initwalk_push(&w.stack, from->rhs) < 0)
|
||||
result = -1;
|
||||
while (result == 0 && w.stack != NULL) {
|
||||
struct initwalkitem *top = w.stack;
|
||||
Node *n = top->node;
|
||||
w.stack = top->next;
|
||||
free(top);
|
||||
if (n->refdecl == target) {
|
||||
result = 1;
|
||||
break;
|
||||
}
|
||||
Node *r = n->refdecl;
|
||||
if (r != NULL && r->kind == N_FNDECL && !r->imported
|
||||
&& !r->initfn && r->body != NULL) {
|
||||
int seen = initwalk_seen_fn(&w, r);
|
||||
if (seen < 0) { result = -1; break; }
|
||||
if (!seen && initwalk_push(&w.stack, r->body) < 0) {
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Node *child[] = { n->attr, n->lhs, n->rhs, n->cond,
|
||||
n->body, n->els, n->list, n->next };
|
||||
for (size_t i = 0; result == 0 && i < nelem(child); i++)
|
||||
if (initwalk_push(&w.stack, child[i]) < 0)
|
||||
result = -1;
|
||||
}
|
||||
initwalk_free(w.stack);
|
||||
initwalk_free(w.seenfn);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
init_cycle_note(Node *from, Node *to)
|
||||
{
|
||||
FILE *f = errout ? errout : stderr;
|
||||
fprintf(f, "\t%s:%d:%d: %s refers to %s\n",
|
||||
from->pos.file ? from->pos.file : "?", from->pos.line,
|
||||
from->pos.col, from->str, to->str);
|
||||
}
|
||||
|
||||
/* Go's findPath, iteratively: dependencies are visited in source order and a
|
||||
* global seen set prevents a side cycle from consuming the native stack. */
|
||||
static int
|
||||
init_find_cycle(Checker *c, Node **vars, size_t nvar, size_t start)
|
||||
{
|
||||
unsigned char *seen = calloc(nvar, 1);
|
||||
size_t *path = malloc(nvar * sizeof *path);
|
||||
size_t *next = calloc(nvar, sizeof *next);
|
||||
if (seen == NULL || path == NULL || next == NULL) {
|
||||
free(next); free(path); free(seen);
|
||||
err(c, vars[start]->pos,
|
||||
"out of memory while ordering package initialization");
|
||||
return -1;
|
||||
}
|
||||
size_t depth = 1;
|
||||
path[0] = start;
|
||||
seen[start] = 1;
|
||||
while (depth > 0) {
|
||||
size_t from = path[depth - 1];
|
||||
int descended = 0;
|
||||
while (next[depth - 1] < nvar) {
|
||||
size_t to = next[depth - 1]++;
|
||||
int dep = init_refers(vars[from], vars[to]);
|
||||
if (dep < 0) {
|
||||
err(c, vars[start]->pos,
|
||||
"out of memory while ordering package initialization");
|
||||
free(next); free(path); free(seen);
|
||||
return -1;
|
||||
}
|
||||
if (!dep) continue;
|
||||
if (to == start) {
|
||||
if (depth == 1) {
|
||||
err(c, vars[start]->pos,
|
||||
"initialization cycle: %s refers to itself",
|
||||
vars[start]->str);
|
||||
} else {
|
||||
err(c, vars[start]->pos,
|
||||
"initialization cycle for %s",
|
||||
vars[start]->str);
|
||||
for (size_t i = 1; i < depth; i++)
|
||||
init_cycle_note(vars[path[i - 1]],
|
||||
vars[path[i]]);
|
||||
init_cycle_note(vars[path[depth - 1]],
|
||||
vars[start]);
|
||||
}
|
||||
free(next); free(path); free(seen);
|
||||
return 1;
|
||||
}
|
||||
if (seen[to]) continue;
|
||||
seen[to] = 1;
|
||||
path[depth] = to;
|
||||
next[depth] = 0;
|
||||
depth++;
|
||||
descended = 1;
|
||||
break;
|
||||
}
|
||||
if (!descended) depth--;
|
||||
}
|
||||
free(next); free(path); free(seen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Node *
|
||||
init_make_call(Checker *c, Node *fn, Pos pos)
|
||||
{
|
||||
Node *id = newnode(c->a, N_IDENT, pos);
|
||||
id->str = fn->str;
|
||||
id->strlen = strlen(fn->str);
|
||||
id->type = fn->type;
|
||||
id->refdecl = fn;
|
||||
Node *call = newnode(c->a, N_CALL, pos);
|
||||
call->lhs = id;
|
||||
call->type = ty_void;
|
||||
Node *stmt = newnode(c->a, N_EXPRSTMT, pos);
|
||||
stmt->lhs = call;
|
||||
return stmt;
|
||||
}
|
||||
|
||||
static u64
|
||||
init_arrlit_count(Node *lit)
|
||||
{
|
||||
u64 count = 0;
|
||||
for (Node *e = lit ? lit->list : NULL; e; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str != NULL
|
||||
&& strcmp(e->str, "...") == 0)
|
||||
continue;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Runtime slice literals cannot use the ordinary local-literal backing: that
|
||||
* storage dies when the compiler-generated variable helper returns. Attach a
|
||||
* canonical package-owned backing symbol to every slice literal contained in
|
||||
* the value being published. The cgen emits zeroed writable storage for the
|
||||
* symbol and fills it at the literal's exact evaluation point.
|
||||
*
|
||||
* The name is derived only from the action-owned package-init symbol, the
|
||||
* Go-ordered variable ordinal, and literal preorder. Source names, import
|
||||
* aliases, declared package names, and path leaves never enter the identity. */
|
||||
static void
|
||||
init_mark_slice_backings(Checker *c, Type *want, Node *expr,
|
||||
const char *base, u64 order, u64 *preorder)
|
||||
{
|
||||
Node *r = init_strip_cast(expr);
|
||||
Type *u = type_chase_named(want);
|
||||
if (r == NULL || u == NULL) return;
|
||||
if (u->kind == TY_SLICE && r->kind == N_ARRLIT) {
|
||||
u64 count = init_arrlit_count(r);
|
||||
(*preorder)++;
|
||||
r->linksym = aprintf(c->a, "%s.v.%llu.b.%llu", base,
|
||||
(unsigned long long)order,
|
||||
(unsigned long long)*preorder);
|
||||
r->type = type_array(c->a, u->sub, count);
|
||||
for (Node *e = r->list; e; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str != NULL
|
||||
&& strcmp(e->str, "...") == 0)
|
||||
continue;
|
||||
init_mark_slice_backings(c, u->sub, e, base, order,
|
||||
preorder);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (u->kind == TY_ARRAY && r->kind == N_ARRLIT) {
|
||||
for (Node *e = r->list; e; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str != NULL
|
||||
&& strcmp(e->str, "...") == 0)
|
||||
continue;
|
||||
init_mark_slice_backings(c, u->sub, e, base, order,
|
||||
preorder);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (u->kind == TY_STRUCT && r->kind == N_STRUCTLIT) {
|
||||
for (Node *e = r->list; e; e = e->next) {
|
||||
Tfield *field = NULL;
|
||||
for (Tfield *f = u->fields; f; f = f->next)
|
||||
if (e->str != NULL && f->name != NULL
|
||||
&& strcmp(e->str, f->name) == 0) {
|
||||
field = f;
|
||||
break;
|
||||
}
|
||||
if (field != NULL)
|
||||
init_mark_slice_backings(c, field->type, e->lhs,
|
||||
base, order, preorder);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (u->kind == TY_TUPLE && r->kind == N_TUPLE) {
|
||||
Tparam *p = u->params;
|
||||
for (Node *e = r->list; e && p; e = e->next, p = p->next)
|
||||
init_mark_slice_backings(c, p->type, e, base, order,
|
||||
preorder);
|
||||
}
|
||||
}
|
||||
|
||||
static Node *
|
||||
init_make_helper(Checker *c, Node *d, const char *base)
|
||||
{
|
||||
Node *fn = newnode(c->a, N_FNDECL, d->pos);
|
||||
fn->str = aprintf(c->a, "__ww_init_var_%llu",
|
||||
(unsigned long long)d->initorder);
|
||||
fn->strlen = strlen(fn->str);
|
||||
fn->module = d->module;
|
||||
fn->pkgname = d->pkgname;
|
||||
fn->sourceid = d->sourceid;
|
||||
fn->initsynthetic = 1;
|
||||
fn->linksym = aprintf(c->a, "%s.v.%llu", base,
|
||||
(unsigned long long)d->initorder);
|
||||
Type *ft = newtype(c->a, TY_FN);
|
||||
ft->size = 8; ft->align = 8; ft->ret = ty_void;
|
||||
fn->type = ft;
|
||||
|
||||
/* Evaluate into an addressable local first. The established local-let and
|
||||
* full-value assignment paths cover arrays, structs, tuples, calls, and
|
||||
* allocation without asking the static-data emitter to understand them. */
|
||||
Node *tmp = newnode(c->a, N_LET, d->pos);
|
||||
tmp->str = aprintf(c->a, "__ww_init_tmp_%llu",
|
||||
(unsigned long long)d->initorder);
|
||||
tmp->strlen = strlen(tmp->str);
|
||||
tmp->lhs = d->lhs;
|
||||
tmp->rhs = d->rhs;
|
||||
tmp->type = d->type;
|
||||
tmp->initsynthetic = 1;
|
||||
|
||||
Node *id = newnode(c->a, N_IDENT, d->pos);
|
||||
id->str = d->str;
|
||||
id->strlen = strlen(d->str);
|
||||
id->type = d->type;
|
||||
id->refdecl = d;
|
||||
Node *value = newnode(c->a, N_IDENT, d->pos);
|
||||
value->str = tmp->str;
|
||||
value->strlen = tmp->strlen;
|
||||
value->type = d->type;
|
||||
value->refdecl = tmp;
|
||||
Node *assign = newnode(c->a, N_ASSIGN, d->pos);
|
||||
assign->op = TK_ASSIGN;
|
||||
assign->lhs = id;
|
||||
assign->rhs = value;
|
||||
assign->type = d->type;
|
||||
Node *stmt = newnode(c->a, N_EXPRSTMT, d->pos);
|
||||
stmt->lhs = assign;
|
||||
Node *body = newnode(c->a, N_BLOCK, d->pos);
|
||||
body->list = tmp;
|
||||
tmp->next = stmt;
|
||||
fn->body = body;
|
||||
d->rhs = NULL;
|
||||
return fn;
|
||||
}
|
||||
|
||||
static int
|
||||
init_lower_package(Checker *c, Node *file)
|
||||
{
|
||||
int nvar = 0;
|
||||
u64 nruntime = 0;
|
||||
u64 ninit = 0;
|
||||
Node *firstinit = NULL;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->initfn && !d->imported) {
|
||||
ninit++;
|
||||
if (firstinit == NULL) firstinit = d;
|
||||
}
|
||||
if (d->kind != N_LET || d->imported || d->rhs == NULL)
|
||||
continue;
|
||||
if (d->op == TK_CONST) continue;
|
||||
if (nvar == INT_MAX) {
|
||||
err(c, d->pos,
|
||||
"out of memory while ordering package initialization");
|
||||
return -1;
|
||||
}
|
||||
nvar++;
|
||||
if (!init_expr_static(d->type, d->rhs)) {
|
||||
d->runtimeinit = 1;
|
||||
nruntime++;
|
||||
if (firstinit == NULL) firstinit = d;
|
||||
}
|
||||
}
|
||||
if (c->errs) return -1;
|
||||
/* A separate package with executable initialization must have the exact
|
||||
* action-owned symbol supplied by its driver. Legacy non-separate mode
|
||||
* retains its historical private fallback. */
|
||||
if (c->sep_mode && (c->package_init_symbol == NULL
|
||||
|| c->package_init_symbol[0] == '\0')
|
||||
&& (nruntime != 0 || ninit != 0)) {
|
||||
err(c, firstinit ? firstinit->pos : file->pos,
|
||||
"package initialization requires --package-init-symbol under -c");
|
||||
return -1;
|
||||
}
|
||||
if ((c->package_init_symbol == NULL
|
||||
|| c->package_init_symbol[0] == '\0') && nruntime == 0 && ninit == 0)
|
||||
return 0;
|
||||
|
||||
Node **vars = NULL;
|
||||
if (nvar != 0) {
|
||||
if ((size_t)nvar > (size_t)-1 / sizeof *vars
|
||||
|| (vars = malloc((size_t)nvar * sizeof *vars)) == NULL) {
|
||||
err(c, file->pos,
|
||||
"out of memory while ordering package initialization");
|
||||
return -1;
|
||||
}
|
||||
int vi = 0;
|
||||
for (Node *d = file->list; d; d = d->next)
|
||||
if (d->kind == N_LET && !d->imported && d->rhs != NULL
|
||||
&& d->op != TK_CONST)
|
||||
vars[vi++] = d;
|
||||
}
|
||||
int done = 0;
|
||||
while (done < nvar) {
|
||||
size_t best = (size_t)-1;
|
||||
size_t bestdeps = (size_t)-1;
|
||||
for (int i = 0; i < nvar; i++) {
|
||||
if (vars[i]->initorder != 0) continue;
|
||||
size_t ndeps = 0;
|
||||
for (int j = 0; j < nvar; j++) {
|
||||
if (vars[j]->initorder != 0) continue;
|
||||
int dep = init_refers(vars[i], vars[j]);
|
||||
if (dep < 0) {
|
||||
err(c, vars[i]->pos,
|
||||
"out of memory while ordering package initialization");
|
||||
free(vars);
|
||||
return -1;
|
||||
}
|
||||
if (dep) ndeps++;
|
||||
}
|
||||
if (best == (size_t)-1 || ndeps < bestdeps) {
|
||||
best = (size_t)i;
|
||||
bestdeps = ndeps;
|
||||
}
|
||||
}
|
||||
if (best == (size_t)-1) break;
|
||||
if (bestdeps != 0) {
|
||||
int cycle = init_find_cycle(c, vars, (size_t)nvar, best);
|
||||
if (cycle < 0) { free(vars); return -1; }
|
||||
/* A reported cycle is broken by removing this node, exactly
|
||||
* like go/types' priority-queue walk. Continue so disjoint or
|
||||
* overlapping later cycles retain their deterministic errors. */
|
||||
}
|
||||
vars[best]->initorder = (u64)++done;
|
||||
}
|
||||
free(vars);
|
||||
if (c->errs) return -1;
|
||||
|
||||
const char *base = c->package_init_symbol;
|
||||
if (base == NULL || base[0] == '\0')
|
||||
base = "__ww..pkg.v0.r0.e.init";
|
||||
Node *tail = file->list;
|
||||
while (tail && tail->next) tail = tail->next;
|
||||
Node *helpers = NULL, *helpertail = NULL;
|
||||
for (u64 order = 1; order <= (u64)nvar; order++)
|
||||
for (Node *d = file->list; d; d = d->next)
|
||||
if (d->runtimeinit && d->initorder == order) {
|
||||
u64 preorder = 0;
|
||||
init_mark_slice_backings(c, d->type, d->rhs,
|
||||
base, order, &preorder);
|
||||
Node *fn = init_make_helper(c, d, base);
|
||||
if (helpers == NULL) helpers = fn;
|
||||
else helpertail->next = fn;
|
||||
helpertail = fn;
|
||||
break;
|
||||
}
|
||||
if (tail) tail->next = helpers;
|
||||
else file->list = helpers;
|
||||
if (helpertail) tail = helpertail;
|
||||
|
||||
Node *task = newnode(c->a, N_FNDECL, file->pos);
|
||||
task->str = "__ww_init_task";
|
||||
task->strlen = strlen(task->str);
|
||||
task->initsynthetic = 1;
|
||||
task->linksym = base;
|
||||
Type *tt = newtype(c->a, TY_FN);
|
||||
tt->size = 8; tt->align = 8; tt->ret = ty_void;
|
||||
task->type = tt;
|
||||
Node *body = newnode(c->a, N_BLOCK, file->pos);
|
||||
Node *stail = NULL;
|
||||
for (Node *fn = helpers; fn; fn = fn->next) {
|
||||
Node *s = init_make_call(c, fn, fn->pos);
|
||||
if (body->list == NULL) body->list = s;
|
||||
else stail->next = s;
|
||||
stail = s;
|
||||
}
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (!d->initfn || d->imported) continue;
|
||||
Node *s = init_make_call(c, d, d->pos);
|
||||
if (body->list == NULL) body->list = s;
|
||||
else stail->next = s;
|
||||
stail = s;
|
||||
}
|
||||
task->body = body;
|
||||
if (tail) tail->next = task;
|
||||
else file->list = task;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
classify_init_decls(Checker *c, Node *file)
|
||||
{
|
||||
u64 ordinal = 0;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (init_private_symbol(d))
|
||||
err(c, d->pos, "@symbol name uses reserved prefix __ww..");
|
||||
if (d->str == NULL || strcmp(d->str, "init") != 0
|
||||
|| !top_decl_kind(d))
|
||||
continue;
|
||||
if (d->kind != N_FNDECL) {
|
||||
err(c, d->pos, "cannot declare init - must be func");
|
||||
continue;
|
||||
}
|
||||
d->initfn = 1;
|
||||
d->initorder = ++ordinal;
|
||||
if (d->export)
|
||||
err(c, d->pos, "func init cannot be exported");
|
||||
if (d->body == NULL)
|
||||
err(c, d->pos, "func init must have a body");
|
||||
if (d->attr != NULL)
|
||||
err(c, d->pos, "func init cannot have attributes");
|
||||
}
|
||||
}
|
||||
|
||||
/* Import usage is a property of the file-local qualifier occurrence. Record
|
||||
* qualified syntax before resolving declaration bodies so import diagnostics
|
||||
* retain production Go's source order without making a failed bare lookup a
|
||||
@@ -3186,10 +3866,12 @@ check_import_redeclarations(Checker *c, Node *file)
|
||||
{
|
||||
if (!c->sep_mode) return;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->imported || u->str == NULL)
|
||||
if (u->kind != N_USE || u->useblank
|
||||
|| u->imported || u->str == NULL)
|
||||
continue;
|
||||
for (Node *v = file->list; v != u; v = v->next) {
|
||||
if (v->kind != N_USE || v->imported || v->str == NULL
|
||||
if (v->kind != N_USE || v->useblank
|
||||
|| v->imported || v->str == NULL
|
||||
|| v->sourceid != u->sourceid)
|
||||
continue;
|
||||
if (strcmp(v->str, u->str) == 0) {
|
||||
@@ -3210,7 +3892,8 @@ check_import_usage_and_collisions(Checker *c, Node *file)
|
||||
{
|
||||
if (!c->sep_mode) return;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->imported || u->used || u->str == NULL)
|
||||
if (u->kind != N_USE || u->useblank
|
||||
|| u->imported || u->used || u->str == NULL)
|
||||
continue;
|
||||
const char *path = u->usesource ? u->usesource
|
||||
: u->usepath ? u->usepath : u->str;
|
||||
@@ -3226,7 +3909,8 @@ check_import_usage_and_collisions(Checker *c, Node *file)
|
||||
if (d->imported || !top_decl_kind(d) || d->str == NULL)
|
||||
continue;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->imported || u->str == NULL
|
||||
if (u->kind != N_USE || u->useblank
|
||||
|| u->imported || u->str == NULL
|
||||
|| strcmp(d->str, u->str) != 0)
|
||||
continue;
|
||||
const char *path = u->usesource ? u->usesource
|
||||
@@ -3287,6 +3971,7 @@ check_file(Checker *c, Node *file)
|
||||
mark_import_uses(c, file);
|
||||
check_import_redeclarations(c, file);
|
||||
check_import_usage_and_collisions(c, file);
|
||||
classify_init_decls(c, file);
|
||||
|
||||
/* pass 1: install names (types first, then defs/fns).
|
||||
* For self-referential types we install the named-type placeholder
|
||||
@@ -3308,6 +3993,12 @@ check_file(Checker *c, Node *file)
|
||||
&& strcmp(d->usepath, owner) == 0)
|
||||
err(c, d->pos, "self-import: package "
|
||||
"'%s' cannot import itself", owner);
|
||||
if (d->useblank)
|
||||
continue;
|
||||
if (d->str != NULL && strcmp(d->str, "init") == 0) {
|
||||
err(c, d->pos, "cannot import package as init - init must be a func");
|
||||
continue;
|
||||
}
|
||||
Sym *prev = scope_lookup_local(c->cur, d->str);
|
||||
if (prev != NULL) {
|
||||
/* Self-import: the driver concatenates the
|
||||
@@ -3323,6 +4014,7 @@ check_file(Checker *c, Node *file)
|
||||
continue;
|
||||
}
|
||||
if (d->kind != N_TYPEDECL) continue;
|
||||
if (d->str != NULL && strcmp(d->str, "init") == 0) continue;
|
||||
const char *mod = decl_mod(file, d);
|
||||
Sym *fact = same_import_fact(c, d, mod, SK_TYPE);
|
||||
if (fact != NULL) {
|
||||
@@ -3357,6 +4049,7 @@ check_file(Checker *c, Node *file)
|
||||
c->cur_source = 0;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind != N_DEF) continue;
|
||||
if (d->str != NULL && strcmp(d->str, "init") == 0) continue;
|
||||
c->cur_mod = decl_mod(file, d);
|
||||
c->cur_source = d->sourceid;
|
||||
const char *mod = decl_mod(file, d);
|
||||
@@ -3390,6 +4083,10 @@ check_file(Checker *c, Node *file)
|
||||
break;
|
||||
case N_DEF: {
|
||||
Type *t = resolve_type(c, d->lhs);
|
||||
if (d->str != NULL && strcmp(d->str, "init") == 0) {
|
||||
d->type = t;
|
||||
break;
|
||||
}
|
||||
const char *mod = decl_mod(file, d);
|
||||
Sym *fact = same_import_fact(c, d, mod, SK_DEF);
|
||||
if (fact != NULL) {
|
||||
@@ -3419,6 +4116,18 @@ check_file(Checker *c, Node *file)
|
||||
case N_FNDECL: {
|
||||
Type *t = build_fn_type(c, d);
|
||||
d->type = t;
|
||||
if (d->initfn) {
|
||||
Type *ret = type_chase_named(t->ret);
|
||||
if (d->list != NULL || ret != ty_void)
|
||||
err(c, d->pos, "func init must have no arguments and no return values");
|
||||
const char *base = c->package_init_symbol;
|
||||
if ((base == NULL || base[0] == '\0') && !c->sep_mode)
|
||||
base = "__ww..pkg.v0.r0.e.init";
|
||||
if (base != NULL && base[0] != '\0')
|
||||
d->linksym = aprintf(c->a, "%s.f.%llu", base,
|
||||
(unsigned long long)d->initorder);
|
||||
break;
|
||||
}
|
||||
Sym *prev = scope_lookup_local(c->cur, d->str);
|
||||
const char *mod = decl_mod(file, d);
|
||||
if (prev && prev->kind == SK_USE) {
|
||||
@@ -3444,26 +4153,8 @@ check_file(Checker *c, Node *file)
|
||||
if (t)
|
||||
require_sized(c, t, d->pos, "a variable");
|
||||
d->type = t;
|
||||
/* A module-scope initializer must be link-time data:
|
||||
* an alloc/call rhs runs code, emit_lets' fold-fail
|
||||
* skipped the DATAW slot silently, and every
|
||||
* reference died at LINK time ("undefined reference")
|
||||
* — reject at the declaration instead (rule 7).
|
||||
* Hare rejects at check time too (ref/harec/src/
|
||||
* check.c:4360 "Unable to evaluate initializer at
|
||||
* compile time"); ww has no @init path. */
|
||||
{
|
||||
Node *r = d->rhs;
|
||||
while (r != NULL && (r->kind == N_CAST
|
||||
|| r->kind == N_TRYPROP
|
||||
|| r->kind == N_TRYUNW))
|
||||
r = r->lhs;
|
||||
if (r != NULL && (r->kind == N_ALLOC
|
||||
|| r->kind == N_CALL))
|
||||
err(c, d->pos, "module-scope let %s: "
|
||||
"runtime initializer unsupported "
|
||||
"(alloc/call; rule 7)", d->str);
|
||||
}
|
||||
if (d->str != NULL && strcmp(d->str, "init") == 0)
|
||||
break;
|
||||
if (d->str && d->str[0]) {
|
||||
Sym *prev = scope_lookup_local(c->cur, d->str);
|
||||
const char *mod = decl_mod(file, d);
|
||||
@@ -3929,6 +4620,7 @@ check_file(Checker *c, Node *file)
|
||||
}
|
||||
c->cur_mod = NULL;
|
||||
c->cur_source = 0;
|
||||
(void)init_lower_package(c, file);
|
||||
/*
|
||||
* #6 harec-fidelity (ref/harec/src/check.c:3941): a @test fn is
|
||||
* fully checked above — pass 2 walked its body like every fn — but
|
||||
|
||||
@@ -1326,9 +1326,7 @@ parseuse(Parser *p)
|
||||
const char *alias = NULL;
|
||||
const char *first;
|
||||
if (p->cur.kind == TK_UNDER) {
|
||||
errorf(p->cur.pos, "blank import alias _ is not implemented");
|
||||
p->errs++;
|
||||
alias = "_";
|
||||
n->useblank = 1;
|
||||
advance(p);
|
||||
first = expectident(p);
|
||||
} else {
|
||||
@@ -1344,8 +1342,10 @@ parseuse(Parser *p)
|
||||
leaf = expectident(p);
|
||||
path = aprintf(p->a, "%s.%s", path, leaf);
|
||||
}
|
||||
n->str = alias ? alias : leaf;
|
||||
n->strlen = strlen(n->str);
|
||||
if (!n->useblank) {
|
||||
n->str = alias ? alias : leaf;
|
||||
n->strlen = strlen(n->str);
|
||||
}
|
||||
n->usesource = path;
|
||||
n->usepath = path;
|
||||
n->usealias = alias;
|
||||
|
||||
@@ -349,12 +349,20 @@ struct Node {
|
||||
const char *usealias; /* N_USE: explicit file-local alias, or NULL. */
|
||||
const char *usepkgname; /* N_USE: imported declared package name,
|
||||
* independent of the visible binding in `str`. */
|
||||
int useblank; /* N_USE: `_` spelling; no source binding. */
|
||||
const char *pkgname; /* declared package name for this source/export
|
||||
* section; independent of canonical `module`. */
|
||||
int sourceid; /* lexical source-file scope within the parsed
|
||||
* owner unit; module-reset/module boundaries
|
||||
* advance it deterministically. */
|
||||
int used; /* N_USE: checker observed this file-local binding. */
|
||||
int initfn; /* special source `fn init`, absent from scope/API. */
|
||||
int initsynthetic; /* compiler-owned variable helper/package task. */
|
||||
int runtimeinit; /* module let lowered through an init helper. */
|
||||
u64 initorder; /* 1-based variable or init-function order. */
|
||||
const char *linksym; /* raw compiler-private assembler symbol. */
|
||||
Node *refdecl; /* checker-resolved value declaration. */
|
||||
u64 initmark; /* checker-private initializer walk mark. */
|
||||
int imported; /* M1 #22: decl reached through an
|
||||
* `//ww:module <path>` import boundary
|
||||
* (vs root/primary). Gates the root-only
|
||||
@@ -616,6 +624,7 @@ struct Checker {
|
||||
* any OTHER empty alloc has no element-type hint
|
||||
* and must fail to infer (harec check.c:1801).
|
||||
* Set by clet around its cexpr, NULL elsewhere. */
|
||||
const char *package_init_symbol; /* canonical action-owned hidden task. */
|
||||
};
|
||||
|
||||
void check_init(Checker*, Arena*);
|
||||
|
||||
Reference in New Issue
Block a user