compiler: make package exports self-contained
This commit is contained in:
389
cmd/w6c/wwi.c
389
cmd/w6c/wwi.c
@@ -15,12 +15,13 @@
|
||||
* stays dead on the normal `.s` path. It rejects exactly one thing:
|
||||
* an exported signature naming a non-exported nominal type.
|
||||
*
|
||||
* A `.wwi` is ONE package's interface. The M2 gate feeds w6c a driver-
|
||||
* combined unit (imports concatenated ahead of the target), so the emit
|
||||
* filters to PRIMARY decls (imported == 0) — in the real per-package
|
||||
* compile every decl is primary, so the filter is a no-op there. Output
|
||||
* is a pure function of the exported API: package line, byte-sorted
|
||||
* imports, byte-sorted decls (no map/hash iteration order).
|
||||
* A `.wwi` is ONE package's self-contained interface. The primary section
|
||||
* is followed by compiler-owned `//ww:module <path>` sections for exported
|
||||
* foreign type/const facts recursively needed by the public surface. The
|
||||
* package driver therefore only needs to pass a package's DIRECT imports;
|
||||
* the interface itself carries the selected public type closure. Output is
|
||||
* a pure function of the exported API: byte-sorted imports, declarations,
|
||||
* fact owners and fact names (no map/hash iteration order).
|
||||
*/
|
||||
#include "gc.h"
|
||||
#include <stdio.h>
|
||||
@@ -41,24 +42,78 @@ wwi_primary(Node *n)
|
||||
* leaf. By the time the producer runs, check_file has finished and
|
||||
* c->cur == c->top.
|
||||
*/
|
||||
static int
|
||||
wwi_mod_eq(const char *a, const char *b)
|
||||
{
|
||||
if (a == NULL || b == NULL) return a == b;
|
||||
return strcmp(a, b) == 0;
|
||||
}
|
||||
|
||||
/* The import alias map is source-owner-local. Imported `.wwi` sections are
|
||||
* flat in the parser, so consulting every N_USE without this owner filter
|
||||
* would let one dependency accidentally resolve another dependency's alias. */
|
||||
static const char *
|
||||
wwi_use_path(Checker *c, const char *owner, const char *alias)
|
||||
{
|
||||
if (owner && alias) {
|
||||
const char *dot = strrchr(owner, '.');
|
||||
const char *leaf = dot ? dot + 1 : owner;
|
||||
if (strcmp(alias, leaf) == 0)
|
||||
return owner;
|
||||
}
|
||||
for (Node *u = c->file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->str == NULL
|
||||
|| strcmp(u->str, alias) != 0)
|
||||
continue;
|
||||
int same = owner == NULL ? u->imported == 0
|
||||
: u->imported != 0 && wwi_mod_eq(u->module, owner);
|
||||
if (same)
|
||||
return u->usepath ? u->usepath : u->str;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
wwi_direct_mod_visible(Checker *c, const char *owner, const char *mod)
|
||||
{
|
||||
if (mod == NULL || mod[0] == '\0') return 0;
|
||||
const char *dot = strrchr(mod, '.');
|
||||
const char *alias = dot ? dot + 1 : mod;
|
||||
const char *path = wwi_use_path(c, owner, alias);
|
||||
return path != NULL && strcmp(path, mod) == 0;
|
||||
}
|
||||
|
||||
static Sym *
|
||||
wwi_typesym(Checker *c, const char *nm)
|
||||
wwi_typesym(Checker *c, const char *owner, const char *nm)
|
||||
{
|
||||
if (nm == NULL) return NULL;
|
||||
const char *dot = strrchr(nm, '.');
|
||||
Sym *s = NULL;
|
||||
if (dot) {
|
||||
size_t n = (size_t)(dot - nm);
|
||||
for (Node *u = c->file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || !wwi_primary(u) || u->str == NULL
|
||||
|| strlen(u->str) != n || strncmp(u->str, nm, n) != 0)
|
||||
continue;
|
||||
const char *mod = u->usepath ? u->usepath : u->str;
|
||||
char *alias = malloc(n + 1);
|
||||
if (alias == NULL) fatal("wwi: out of memory");
|
||||
memcpy(alias, nm, n);
|
||||
alias[n] = '\0';
|
||||
const char *mod = wwi_use_path(c, owner, alias);
|
||||
if (mod)
|
||||
s = scope_lookup_in_module(c->cur, mod, dot + 1);
|
||||
break;
|
||||
}
|
||||
free(alias);
|
||||
} else {
|
||||
s = scope_lookup_type(c->cur, NULL, nm);
|
||||
s = scope_lookup_type(c->cur, owner, nm);
|
||||
if (s == NULL) {
|
||||
for (Scope *p = c->cur; p; p = p->parent) {
|
||||
for (Sym *b = p->first; b; b = b->next) {
|
||||
if (b->kind == SK_TYPE
|
||||
&& strcmp(b->name, nm) == 0
|
||||
&& wwi_direct_mod_visible(c, owner, b->mod)) {
|
||||
s = b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s != NULL) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s && s->kind == SK_TYPE)
|
||||
return s;
|
||||
@@ -66,14 +121,14 @@ wwi_typesym(Checker *c, const char *nm)
|
||||
}
|
||||
|
||||
static int
|
||||
wwi_check_type(Checker *c, Pos loc, Node *t)
|
||||
wwi_check_type(Checker *c, const char *owner, Pos loc, Node *t)
|
||||
{
|
||||
int bad = 0;
|
||||
if (t == NULL)
|
||||
return 0;
|
||||
switch (t->kind) {
|
||||
case N_TNAME: {
|
||||
Sym *s = wwi_typesym(c, t->str);
|
||||
Sym *s = wwi_typesym(c, owner, t->str);
|
||||
/* Sym.exported is vestigial (the checker never sets it); the
|
||||
* nominal's export status lives on its decl node, parser-set.
|
||||
* No file-guard twin to wwstage's: cstage resolves `nomem` and
|
||||
@@ -92,30 +147,30 @@ wwi_check_type(Checker *c, Pos loc, Node *t)
|
||||
case N_TSLICE:
|
||||
case N_TBANG:
|
||||
case N_TCHAN:
|
||||
bad |= wwi_check_type(c, loc, t->lhs);
|
||||
bad |= wwi_check_type(c, owner, loc, t->lhs);
|
||||
break;
|
||||
case N_TARRAY:
|
||||
/* element only; the length is a const-expr, not a type. */
|
||||
bad |= wwi_check_type(c, loc, t->lhs);
|
||||
bad |= wwi_check_type(c, owner, loc, t->lhs);
|
||||
break;
|
||||
case N_TFN:
|
||||
for (Node *p = t->list; p; p = p->next)
|
||||
bad |= wwi_check_type(c, loc, p->lhs);
|
||||
bad |= wwi_check_type(c, loc, t->lhs);
|
||||
bad |= wwi_check_type(c, owner, loc, p->lhs);
|
||||
bad |= wwi_check_type(c, owner, loc, t->lhs);
|
||||
break;
|
||||
case N_TSTRUCT:
|
||||
for (Node *f = t->list; f; f = f->next)
|
||||
bad |= wwi_check_type(c, loc, f->lhs);
|
||||
bad |= wwi_check_type(c, owner, loc, f->lhs);
|
||||
break;
|
||||
case N_TTAGGED:
|
||||
case N_TTUPLE:
|
||||
for (Node *e = t->list; e; e = e->next)
|
||||
bad |= wwi_check_type(c, loc, e);
|
||||
bad |= wwi_check_type(c, owner, loc, e);
|
||||
break;
|
||||
case N_TENUM:
|
||||
/* the inline enum body is the definition, not a reference;
|
||||
* recurse only its storage type (members are values). */
|
||||
bad |= wwi_check_type(c, loc, t->lhs);
|
||||
bad |= wwi_check_type(c, owner, loc, t->lhs);
|
||||
break;
|
||||
default:
|
||||
/* a non-type node in type position should not occur — leaf. */
|
||||
@@ -131,18 +186,18 @@ wwi_check_decl(Checker *c, Node *d)
|
||||
switch (d->kind) {
|
||||
case N_FNDECL:
|
||||
for (Node *p = d->list; p; p = p->next)
|
||||
bad |= wwi_check_type(c, d->pos, p->lhs);
|
||||
bad |= wwi_check_type(c, d->pos, d->lhs); /* ret */
|
||||
bad |= wwi_check_type(c, NULL, d->pos, p->lhs);
|
||||
bad |= wwi_check_type(c, NULL, d->pos, d->lhs); /* ret */
|
||||
break;
|
||||
case N_TYPEDECL:
|
||||
bad |= wwi_check_type(c, d->pos, d->lhs);
|
||||
bad |= wwi_check_type(c, NULL, d->pos, d->lhs);
|
||||
break;
|
||||
case N_DEF:
|
||||
/* the declared type; the rhs const value is not a type. */
|
||||
bad |= wwi_check_type(c, d->pos, d->lhs);
|
||||
bad |= wwi_check_type(c, NULL, d->pos, d->lhs);
|
||||
break;
|
||||
case N_LET:
|
||||
bad |= wwi_check_type(c, d->pos, d->lhs);
|
||||
bad |= wwi_check_type(c, NULL, d->pos, d->lhs);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -357,6 +412,13 @@ wwi_expr(FILE *of, Node *e)
|
||||
fputs(tokname(e->op), of);
|
||||
wwi_expr(of, e->lhs);
|
||||
break;
|
||||
case N_CAST:
|
||||
fputc('(', of);
|
||||
wwi_expr(of, e->lhs);
|
||||
fputs(": ", of);
|
||||
wwi_type(of, e->rhs);
|
||||
fputc(')', of);
|
||||
break;
|
||||
default:
|
||||
fatal("wwi: unhandled const-expr node kind %d", e->kind);
|
||||
}
|
||||
@@ -434,7 +496,7 @@ wwi_decl(FILE *of, Node *d)
|
||||
* fold of an aggregate def's FIELDS — a no-op, ww never folds
|
||||
* struct-literal field access, and an aggregate-def field
|
||||
* demanded in a const-fold context stays a LOUD error (task #71). */
|
||||
if (d->rhs && (d->rhs->kind == N_STRUCTLIT
|
||||
if (d->rhs == NULL || (d->rhs->kind == N_STRUCTLIT
|
||||
|| d->rhs->kind == N_ARRLIT)) {
|
||||
fputs(";\n", of);
|
||||
} else {
|
||||
@@ -462,6 +524,16 @@ wwi_decl(FILE *of, Node *d)
|
||||
|
||||
struct declent { Node *d; int idx; };
|
||||
struct useent { const char *path; int idx; };
|
||||
struct factent { Node *d; const char *mod; int idx; };
|
||||
|
||||
struct factset {
|
||||
Checker *c;
|
||||
struct factent *facts;
|
||||
int nfacts, capfacts;
|
||||
struct factent *seen;
|
||||
int nseen, capseen;
|
||||
int bad;
|
||||
};
|
||||
|
||||
static int
|
||||
declcmp(const void *a, const void *b)
|
||||
@@ -481,6 +553,20 @@ usecmp(const void *a, const void *b)
|
||||
return x->idx - y->idx;
|
||||
}
|
||||
|
||||
static int
|
||||
factcmp(const void *a, const void *b)
|
||||
{
|
||||
const struct factent *x = a, *y = b;
|
||||
int r = strcmp(x->mod, y->mod);
|
||||
if (r != 0) return r;
|
||||
int xr = x->d->kind == N_TYPEDECL ? 0 : 1;
|
||||
int yr = y->d->kind == N_TYPEDECL ? 0 : 1;
|
||||
if (xr != yr) return xr - yr;
|
||||
r = strcmp(x->d->str, y->d->str);
|
||||
if (r != 0) return r;
|
||||
return x->idx - y->idx;
|
||||
}
|
||||
|
||||
static int
|
||||
wwi_is_decl(Node *d)
|
||||
{
|
||||
@@ -488,6 +574,214 @@ wwi_is_decl(Node *d)
|
||||
|| d->kind == N_DEF || d->kind == N_LET;
|
||||
}
|
||||
|
||||
static int
|
||||
wwi_fact_same(struct factent *f, const char *mod, Node *d)
|
||||
{
|
||||
return wwi_mod_eq(f->mod, mod) && f->d->kind == d->kind
|
||||
&& strcmp(f->d->str, d->str) == 0;
|
||||
}
|
||||
|
||||
static void
|
||||
wwi_fact_grow(struct factent **v, int *cap, int need)
|
||||
{
|
||||
if (*cap >= need) return;
|
||||
int ncap = *cap ? *cap * 2 : 16;
|
||||
while (ncap < need) ncap *= 2;
|
||||
struct factent *nv = realloc(*v, (size_t)ncap * sizeof *nv);
|
||||
if (nv == NULL) fatal("wwi: out of memory");
|
||||
*v = nv;
|
||||
*cap = ncap;
|
||||
}
|
||||
|
||||
static Sym *
|
||||
wwi_valuesym(Checker *c, const char *owner, const char *name)
|
||||
{
|
||||
for (Scope *p = c->top; p; p = p->parent)
|
||||
for (Sym *s = p->first; s; s = s->next)
|
||||
if (s->kind == SK_DEF && strcmp(s->name, name) == 0
|
||||
&& wwi_mod_eq(s->mod, owner))
|
||||
return s;
|
||||
for (Scope *p = c->top; p; p = p->parent)
|
||||
for (Sym *s = p->first; s; s = s->next)
|
||||
if (s->kind == SK_DEF && strcmp(s->name, name) == 0
|
||||
&& wwi_direct_mod_visible(c, owner, s->mod))
|
||||
return s;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void wwi_collect_decl(struct factset*, const char*, Node*);
|
||||
static void wwi_collect_type(struct factset*, const char*, Node*);
|
||||
|
||||
static void
|
||||
wwi_collect_expr(struct factset *fs, const char *owner, Node *e)
|
||||
{
|
||||
if (e == NULL) return;
|
||||
Sym *s = NULL;
|
||||
if (e->kind == N_IDENT) {
|
||||
s = wwi_valuesym(fs->c, owner, e->str);
|
||||
} else if (e->kind == N_DOT && e->lhs
|
||||
&& e->lhs->kind == N_IDENT) {
|
||||
const char *mod = wwi_use_path(fs->c, owner, e->lhs->str);
|
||||
if (mod) s = wwi_valuesym(fs->c, mod, e->str);
|
||||
}
|
||||
if (s && s->decl && s->decl->kind == N_DEF) {
|
||||
if (!s->decl->export) {
|
||||
errorf(e->pos, "exported declaration references "
|
||||
"unexported def '%s'", e->str);
|
||||
fs->bad = 1;
|
||||
return;
|
||||
}
|
||||
wwi_collect_decl(fs, s->mod, s->decl);
|
||||
return;
|
||||
}
|
||||
if (e->kind == N_BIN) {
|
||||
wwi_collect_expr(fs, owner, e->lhs);
|
||||
wwi_collect_expr(fs, owner, e->rhs);
|
||||
} else if (e->kind == N_UN) {
|
||||
wwi_collect_expr(fs, owner, e->lhs);
|
||||
} else if (e->kind == N_CAST) {
|
||||
wwi_collect_expr(fs, owner, e->lhs);
|
||||
wwi_collect_type(fs, owner, e->rhs);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wwi_collect_type(struct factset *fs, const char *owner, Node *t)
|
||||
{
|
||||
if (t == NULL) return;
|
||||
switch (t->kind) {
|
||||
case N_TNAME: {
|
||||
Sym *s = wwi_typesym(fs->c, owner, t->str);
|
||||
if (s && s->decl && s->decl->kind == N_TYPEDECL)
|
||||
wwi_collect_decl(fs, s->mod, s->decl);
|
||||
break;
|
||||
}
|
||||
case N_TPTR:
|
||||
case N_TSLICE:
|
||||
case N_TBANG:
|
||||
case N_TCHAN:
|
||||
wwi_collect_type(fs, owner, t->lhs);
|
||||
break;
|
||||
case N_TARRAY:
|
||||
/* Array length is part of the resolved type identity, not a source
|
||||
* name dependency. Canonicalize it so private constants stay private
|
||||
* and a consumer never needs an implementation def to size the type. */
|
||||
if (t->rhs != NULL && t->rhs->kind != N_INTLIT) {
|
||||
u64 len;
|
||||
if (!check_eval_const(fs->c, t->rhs, owner, &len)) {
|
||||
errorf(t->pos, "cannot encode array dimension");
|
||||
fs->bad = 1;
|
||||
} else {
|
||||
Node *e = t->rhs;
|
||||
e->kind = N_INTLIT;
|
||||
e->uval = len;
|
||||
e->tsuffix = NULL;
|
||||
e->lhs = e->rhs = e->list = NULL;
|
||||
}
|
||||
}
|
||||
wwi_collect_type(fs, owner, t->lhs);
|
||||
break;
|
||||
case N_TFN:
|
||||
for (Node *p = t->list; p; p = p->next)
|
||||
wwi_collect_type(fs, owner, p->lhs);
|
||||
wwi_collect_type(fs, owner, t->lhs);
|
||||
break;
|
||||
case N_TSTRUCT:
|
||||
for (Node *f = t->list; f; f = f->next)
|
||||
wwi_collect_type(fs, owner, f->lhs);
|
||||
break;
|
||||
case N_TTAGGED:
|
||||
case N_TTUPLE:
|
||||
for (Node *e = t->list; e; e = e->next)
|
||||
wwi_collect_type(fs, owner, e);
|
||||
break;
|
||||
case N_TENUM:
|
||||
wwi_collect_type(fs, owner, t->lhs);
|
||||
/* Member identifiers are enum-local prior-sibling references, not
|
||||
* package defs; the complete member list already carries their facts. */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wwi_collect_decl(struct factset *fs, const char *owner, Node *d)
|
||||
{
|
||||
for (int i = 0; i < fs->nseen; i++)
|
||||
if (wwi_fact_same(&fs->seen[i], owner, d))
|
||||
return;
|
||||
wwi_fact_grow(&fs->seen, &fs->capseen, fs->nseen + 1);
|
||||
fs->seen[fs->nseen] = (struct factent){d, owner, fs->nseen};
|
||||
fs->nseen++;
|
||||
|
||||
if (owner != NULL) {
|
||||
if (!d->export) {
|
||||
errorf(d->pos, "exported declaration references unexported "
|
||||
"%s '%s'", d->kind == N_TYPEDECL ? "type" : "def",
|
||||
d->str);
|
||||
fs->bad = 1;
|
||||
return;
|
||||
}
|
||||
wwi_fact_grow(&fs->facts, &fs->capfacts, fs->nfacts + 1);
|
||||
fs->facts[fs->nfacts] = (struct factent){d, owner, fs->nfacts};
|
||||
fs->nfacts++;
|
||||
}
|
||||
|
||||
switch (d->kind) {
|
||||
case N_FNDECL:
|
||||
for (Node *p = d->list; p; p = p->next)
|
||||
wwi_collect_type(fs, owner, p->lhs);
|
||||
wwi_collect_type(fs, owner, d->lhs);
|
||||
break;
|
||||
case N_TYPEDECL:
|
||||
wwi_collect_type(fs, owner, d->lhs);
|
||||
break;
|
||||
case N_DEF:
|
||||
wwi_collect_type(fs, owner, d->lhs);
|
||||
wwi_collect_expr(fs, owner, d->rhs);
|
||||
break;
|
||||
case N_LET:
|
||||
wwi_collect_type(fs, owner, d->lhs);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
wwi_use_owned(Node *u, const char *owner)
|
||||
{
|
||||
return u->kind == N_USE && u->imported != 0
|
||||
&& wwi_mod_eq(u->module, owner);
|
||||
}
|
||||
|
||||
static void
|
||||
wwi_emit_fact_imports(FILE *of, Node *file, const char *owner)
|
||||
{
|
||||
int nuse = 0;
|
||||
for (Node *u = file->list; u; u = u->next)
|
||||
if (wwi_use_owned(u, owner)) nuse++;
|
||||
if (nuse == 0) return;
|
||||
struct useent *us = malloc((size_t)nuse * sizeof *us);
|
||||
if (us == NULL) fatal("wwi: out of memory");
|
||||
int k = 0;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (!wwi_use_owned(u, owner)) continue;
|
||||
us[k].path = u->usepath ? u->usepath : u->str;
|
||||
us[k].idx = k;
|
||||
k++;
|
||||
}
|
||||
qsort(us, (size_t)nuse, sizeof *us, usecmp);
|
||||
const char *previous = NULL;
|
||||
for (int i = 0; i < nuse; i++) {
|
||||
if (previous && strcmp(previous, us[i].path) == 0) continue;
|
||||
fprintf(of, "import %s;\n", us[i].path);
|
||||
previous = us[i].path;
|
||||
}
|
||||
free(us);
|
||||
}
|
||||
|
||||
int
|
||||
wwi_emit(Checker *c, FILE *of, Node *file)
|
||||
{
|
||||
@@ -502,6 +796,22 @@ wwi_emit(Checker *c, FILE *of, Node *file)
|
||||
if (bad)
|
||||
return 1;
|
||||
|
||||
/* Relocate the exported API's reachable foreign type facts into this
|
||||
* package's own interface. The driver remains deliberately ignorant of
|
||||
* the representation and will pass only this artifact for a direct dep. */
|
||||
struct factset fs = {.c = c};
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (!wwi_primary(d) || !d->export || !wwi_is_decl(d)) continue;
|
||||
wwi_collect_decl(&fs, NULL, d);
|
||||
}
|
||||
if (fs.bad) {
|
||||
free(fs.facts);
|
||||
free(fs.seen);
|
||||
return 1;
|
||||
}
|
||||
if (fs.nfacts > 1)
|
||||
qsort(fs.facts, (size_t)fs.nfacts, sizeof *fs.facts, factcmp);
|
||||
|
||||
/* package line: leaf of the first primary decl's module tag. */
|
||||
const char *pkg = "main";
|
||||
int found = 0;
|
||||
@@ -573,5 +883,24 @@ wwi_emit(Checker *c, FILE *of, Node *file)
|
||||
wwi_decl(of, ds[i].d);
|
||||
free(ds);
|
||||
}
|
||||
|
||||
/* Compiler-owned public fact closure. A module marker changes semantic
|
||||
* ownership without making the namespace a source import of the eventual
|
||||
* consumer; direct visibility continues to come solely from its own N_USE. */
|
||||
const char *lastmod = NULL;
|
||||
for (int i = 0; i < fs.nfacts; i++) {
|
||||
struct factent *f = &fs.facts[i];
|
||||
if (lastmod == NULL || strcmp(lastmod, f->mod) != 0) {
|
||||
const char *dot = strrchr(f->mod, '.');
|
||||
const char *leaf = dot ? dot + 1 : f->mod;
|
||||
fprintf(of, "//ww:module %s\n", f->mod);
|
||||
fprintf(of, "package %s;\n", leaf);
|
||||
wwi_emit_fact_imports(of, file, f->mod);
|
||||
lastmod = f->mod;
|
||||
}
|
||||
wwi_decl(of, f->d);
|
||||
}
|
||||
free(fs.facts);
|
||||
free(fs.seen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user