ww: implement package initialization
This commit is contained in:
283
cmd/w6c/cgen.c
283
cmd/w6c/cgen.c
@@ -1189,6 +1189,7 @@ ffi_collect(Cg *c, Node *file)
|
||||
if (file == NULL) return;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind != N_FNDECL) continue;
|
||||
if (d->initfn || d->initsynthetic) continue;
|
||||
for (Node *a = d->attr; a; a = a->next) {
|
||||
if (a->kind != N_ATTR) continue;
|
||||
if (strcmp(a->str, "symbol") != 0) continue;
|
||||
@@ -1487,6 +1488,7 @@ mod_collect(Cg *c, Node *file)
|
||||
use_map = u;
|
||||
continue;
|
||||
}
|
||||
if (d->initfn || d->initsynthetic) continue;
|
||||
int isfn = (d->kind == N_FNDECL);
|
||||
int track = isfn || (d->kind == N_TYPEDECL)
|
||||
|| (d->kind == N_DEF) || (d->kind == N_LET);
|
||||
@@ -4196,6 +4198,238 @@ cg_arrlit_fill_bp(Cg *c, Local **locals, Type *lu, Node *arrlit, int off)
|
||||
}
|
||||
}
|
||||
|
||||
/* Package-variable helpers publish their local only after the complete value
|
||||
* has been evaluated. Composite literals need a memory-directed path here:
|
||||
* the ordinary tuple cursor has finite register capacity, nested aggregates
|
||||
* are not scalar expressions, and an ordinary slice literal points at stack
|
||||
* storage that dies when the helper returns. This path is gated by the
|
||||
* checker-owned N_LET.initsynthetic bit and therefore cannot perturb normal
|
||||
* user locals or any statically emitted initializer. */
|
||||
static void cg_init_value_bp(Cg*, Local**, Type*, Node*, int);
|
||||
|
||||
static void
|
||||
cg_init_zero_bp(Cg *c, int off, int sz)
|
||||
{
|
||||
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||
int k = 0;
|
||||
for (; k + 8 <= sz; k += 8)
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + k));
|
||||
if (k + 4 <= sz) {
|
||||
ins2(c, A_MOVL, areg(D_AX), amem(D_BP, off + k));
|
||||
k += 4;
|
||||
}
|
||||
if (k + 2 <= sz) {
|
||||
ins2(c, A_MOVW, areg(D_AX), amem(D_BP, off + k));
|
||||
k += 2;
|
||||
}
|
||||
if (k + 1 <= sz)
|
||||
ins2(c, A_MOVB, areg(D_AX), amem(D_BP, off + k));
|
||||
}
|
||||
|
||||
static void
|
||||
cg_init_copy_bp(Cg *c, int src, int dst, int sz)
|
||||
{
|
||||
ins2(c, A_LEAQ, amem(D_BP, src), areg(D_SI));
|
||||
ins2(c, A_LEAQ, amem(D_BP, dst), areg(D_BX));
|
||||
cg_aggcopy(c, sz);
|
||||
}
|
||||
|
||||
static Node *
|
||||
cg_init_strip_cast(Node *n)
|
||||
{
|
||||
while (n != NULL && n->kind == N_CAST) n = n->lhs;
|
||||
return n;
|
||||
}
|
||||
|
||||
static Tfield *
|
||||
cg_init_field(Type *u, const char *name)
|
||||
{
|
||||
for (Tfield *f = u ? u->fields : NULL; f; f = f->next)
|
||||
if (name != NULL && f->name != NULL
|
||||
&& strcmp(name, f->name) == 0)
|
||||
return f;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
cg_init_array_bp(Cg *c, Local **locals, Type *u, Node *lit, int off)
|
||||
{
|
||||
Type *et = u->sub;
|
||||
int esz = et ? (int)type_chase_named(et)->size : 1;
|
||||
int total = (int)u->alen;
|
||||
cg_init_zero_bp(c, off, (int)u->size);
|
||||
int idx = 0, lastoff = 0;
|
||||
for (Node *e = lit->list; e; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str != NULL
|
||||
&& strcmp(e->str, "...") == 0) {
|
||||
if (idx == 0)
|
||||
fatal("package initializer array repeat has no value");
|
||||
while (idx < total) {
|
||||
cg_init_copy_bp(c, lastoff, off + idx * esz, esz);
|
||||
idx++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (idx >= total)
|
||||
fatal("package initializer array literal exceeds destination");
|
||||
lastoff = off + idx * esz;
|
||||
cg_init_value_bp(c, locals, et, e, lastoff);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cg_init_struct_bp(Cg *c, Local **locals, Type *u, Node *lit, int off)
|
||||
{
|
||||
cg_init_zero_bp(c, off, (int)u->size);
|
||||
for (Node *e = lit->list; e; e = e->next) {
|
||||
Tfield *f = cg_init_field(u, e->str);
|
||||
if (f == NULL) continue; /* checker owns unknown/ellipsis errors */
|
||||
cg_init_value_bp(c, locals, f->type, e->lhs,
|
||||
off + (int)f->offset);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cg_init_tuple_bp(Cg *c, Local **locals, Type *u, Node *lit, int off)
|
||||
{
|
||||
cg_init_zero_bp(c, off, (int)u->size);
|
||||
Node *e = lit->list;
|
||||
int eoff = 0;
|
||||
for (Tparam *p = u->params; p && e; p = p->next, e = e->next) {
|
||||
cg_init_value_bp(c, locals, p->type, e, off + eoff);
|
||||
eoff += tuple_eslot(p->type);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cg_init_slice_bp(Cg *c, Local **locals, Type *u, Node *lit, int off)
|
||||
{
|
||||
if (lit->linksym == NULL || lit->linksym[0] == '\0')
|
||||
fatal("runtime package slice literal has no canonical backing");
|
||||
Type *at = type_chase_named(lit->type);
|
||||
if (at == NULL || at->kind != TY_ARRAY || at->sub == NULL)
|
||||
fatal("runtime package slice literal has no backing type");
|
||||
int count = (int)at->alen;
|
||||
int bsz = (int)at->size;
|
||||
if (bsz != 0) {
|
||||
int scr = local_alloc(c, locals, "@initbacking", bsz, cg_frame);
|
||||
cg_init_array_bp(c, locals, at, lit, scr);
|
||||
ins2(c, A_LEAQ, amem(D_BP, scr), areg(D_SI));
|
||||
ins2(c, A_LEAQ, asym(lit->linksym), areg(D_BX));
|
||||
cg_aggcopy(c, bsz);
|
||||
}
|
||||
ins2(c, A_LEAQ, asym(lit->linksym), areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off));
|
||||
ins2(c, A_MOVQ, aimm(count), amem(D_BP, off + 8));
|
||||
ins2(c, A_MOVQ, aimm(count), amem(D_BP, off + 16));
|
||||
}
|
||||
|
||||
static void
|
||||
cg_init_aggregate_expr_bp(Cg *c, Local **locals, Type *t, Type *u,
|
||||
Node *expr, int off)
|
||||
{
|
||||
int sz = (int)u->size;
|
||||
if (expr->kind == N_CALL && cg_sret_retsize(t) > 0) {
|
||||
cg_sret_dest_off = off;
|
||||
cgexpr(c, expr, *locals);
|
||||
cg_sret_dest_off = 0;
|
||||
return;
|
||||
}
|
||||
if (u->kind == TY_TUPLE && cg_sret_retsize(t) == 0) {
|
||||
cgexpr(c, expr, *locals);
|
||||
int gpcur = 0, ssecur = 0, eoff = 0, f32;
|
||||
for (Tparam *p = u->params; p; p = p->next) {
|
||||
int isfloat = fld_isfloat(p->type, &f32);
|
||||
tuple_store(c, p->type, gpcur, ssecur, off + eoff);
|
||||
if (isfloat) ssecur++;
|
||||
else gpcur += tuple_eslot(p->type) / 8;
|
||||
eoff += tuple_eslot(p->type);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (expr->kind == N_CALL && u->kind == TY_STRUCT) {
|
||||
int cls[2], nb = struct_float_class(u, cls);
|
||||
if (nb > 0) {
|
||||
cgexpr(c, expr, *locals);
|
||||
int gp = 0, sse = 0;
|
||||
for (int i = 0; i < nb; i++) {
|
||||
if (cls[i]) {
|
||||
ins2(c, A_MOVSD, areg(tuple_sse_seq[sse++]),
|
||||
amem(D_BP, off + i * 8));
|
||||
} else {
|
||||
ins2(c, A_MOVQ, areg(tuple_rseq[gp++]),
|
||||
amem(D_BP, off + i * 8));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (expr->kind == N_CALL && sz <= 24) {
|
||||
cgexpr(c, expr, *locals);
|
||||
cg_agg_reg_store(c, locals, D_BP, off, sz, 1);
|
||||
return;
|
||||
}
|
||||
if (aggarg_srcaddr(c, expr, D_SI, *locals)) {
|
||||
ins2(c, A_LEAQ, amem(D_BP, off), areg(D_BX));
|
||||
cg_aggcopy(c, sz);
|
||||
return;
|
||||
}
|
||||
fatal("package initializer aggregate expression shape unsupported");
|
||||
}
|
||||
|
||||
static void
|
||||
cg_init_value_bp(Cg *c, Local **locals, Type *t, Node *expr, int off)
|
||||
{
|
||||
Type *u = type_chase_named(t);
|
||||
Node *r = cg_init_strip_cast(expr);
|
||||
if (u == NULL || r == NULL)
|
||||
fatal("package initializer value has no type or expression");
|
||||
if (u->kind == TY_ARRAY && r->kind == N_ARRLIT) {
|
||||
cg_init_array_bp(c, locals, u, r, off);
|
||||
return;
|
||||
}
|
||||
if (u->kind == TY_STRUCT && r->kind == N_STRUCTLIT) {
|
||||
cg_init_struct_bp(c, locals, u, r, off);
|
||||
return;
|
||||
}
|
||||
if (u->kind == TY_TUPLE && r->kind == N_TUPLE) {
|
||||
cg_init_tuple_bp(c, locals, u, r, off);
|
||||
return;
|
||||
}
|
||||
if (u->kind == TY_SLICE && r->kind == N_ARRLIT) {
|
||||
cg_init_slice_bp(c, locals, u, r, off);
|
||||
return;
|
||||
}
|
||||
if (u->kind == TY_TAGGED) {
|
||||
cg_widen_tagged_store(c, locals, u, expr, D_BP, off,
|
||||
(int)u->size);
|
||||
return;
|
||||
}
|
||||
if (type_isstr(t) || u->kind == TY_SLICE) {
|
||||
cgexpr(c, expr, *locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 16));
|
||||
return;
|
||||
}
|
||||
if (u->kind == TY_ARRAY || u->kind == TY_STRUCT
|
||||
|| u->kind == TY_TUPLE) {
|
||||
cg_init_aggregate_expr_bp(c, locals, t, u, r, off);
|
||||
return;
|
||||
}
|
||||
cgexpr(c, expr, *locals);
|
||||
int f32 = 0;
|
||||
if (fld_isfloat(t, &f32)) {
|
||||
ins2(c, f32 ? A_MOVSS : A_MOVSD, areg(D_X0),
|
||||
amem(D_BP, off));
|
||||
return;
|
||||
}
|
||||
int sz = (int)u->size;
|
||||
if (sz != 1 && sz != 2 && sz != 4) sz = 8;
|
||||
ins2(c, fldstoreop(t, sz), areg(D_AX), amem(D_BP, off));
|
||||
}
|
||||
|
||||
/* cg_dotfield_combine — single-dot field compound combine. The old
|
||||
* field value is in BX, the rhs in AX; the result is left in AX.
|
||||
* PLUSEQ/MINUSEQ preserve the pre-#34 emission (byte-id); the other 8
|
||||
@@ -10941,7 +11175,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
else
|
||||
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||
}
|
||||
if (n->lhs->kind == N_IDENT) {
|
||||
if (n->lhs->kind == N_IDENT && n->lhs->refdecl != NULL
|
||||
&& n->lhs->refdecl->linksym != NULL) {
|
||||
ins1(c, A_CALL, asym(n->lhs->refdecl->linksym));
|
||||
} else if (n->lhs->kind == N_IDENT) {
|
||||
/* If the callee names a local variable holding a
|
||||
* function pointer, load it and call indirect. Without
|
||||
* this check `CALL fp(SB)` is emitted as if `fp` were
|
||||
@@ -13506,6 +13743,14 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
int off = letloc->off;
|
||||
int isf = cg_isfloat(lt);
|
||||
int isf32 = type_isf32(lt);
|
||||
Node *initlit = cg_init_strip_cast(n->rhs);
|
||||
if (n->initsynthetic && initlit != NULL
|
||||
&& (initlit->kind == N_ARRLIT
|
||||
|| initlit->kind == N_STRUCTLIT
|
||||
|| initlit->kind == N_TUPLE)) {
|
||||
cg_init_value_bp(c, locals, lt, n->rhs, off);
|
||||
goto letlink;
|
||||
}
|
||||
/* alloc([], n) initialiser for a slice local: allocate
|
||||
* n*esize bytes, build the {ptr, 0, n} header in the slot.
|
||||
* Element size comes from the declared slice type.
|
||||
@@ -15843,7 +16088,9 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
* !sep_isdep — under sep a dep unit's main is imported==0 (path-
|
||||
* carrying `//ww:module-reset`, #57); only the root/link-entry unit
|
||||
* (wwiout==NULL, #69) keeps the bare label. */
|
||||
if (fn->str && strcmp(fn->str, "main") == 0 && !fn->imported
|
||||
if (fn->linksym != NULL)
|
||||
text->to = asym(fn->linksym);
|
||||
else if (fn->str && strcmp(fn->str, "main") == 0 && !fn->imported
|
||||
&& !c->sep_isdep)
|
||||
text->to = asym("main");
|
||||
else
|
||||
@@ -16098,6 +16345,15 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
fatal("#38b: >48B tagged param mixed with stack-spilled "
|
||||
"params unwired");
|
||||
|
||||
/* The package coordinator owns one complete dependency-first schedule.
|
||||
* Only the selected source/generated main invokes it; package tasks never
|
||||
* recurse through dependencies, so independent-package tie order cannot
|
||||
* accidentally become linker or DFS order. */
|
||||
if (c->init_dispatch_symbol != NULL && fn->str != NULL
|
||||
&& strcmp(fn->str, "main") == 0 && !fn->imported
|
||||
&& !c->sep_isdep)
|
||||
ins1(c, A_CALL, asym(c->init_dispatch_symbol));
|
||||
|
||||
/* Iterate the fn body's statements directly rather than dispatching
|
||||
* the outermost N_BLOCK through cgstmt — N_BLOCK now save/restores
|
||||
* the locals head to scope inner shadows, but the function body is
|
||||
@@ -17674,6 +17930,28 @@ let_pre_intern(Cg *c, Node *file)
|
||||
c->cur_source = save_source;
|
||||
}
|
||||
|
||||
static void
|
||||
emit_init_backings(FILE *out, Node *n)
|
||||
{
|
||||
for (; n; n = n->next) {
|
||||
if (n->kind == N_ARRLIT && n->linksym != NULL
|
||||
&& n->linksym[0] != '\0') {
|
||||
Type *u = type_chase_named(n->type);
|
||||
if (u == NULL || u->kind != TY_ARRAY)
|
||||
fatal("runtime package slice backing has no array type");
|
||||
emit_data_row_zero(out, "DATAW", n->linksym,
|
||||
u->size == 0 ? 1 : (int)u->size);
|
||||
}
|
||||
emit_init_backings(out, n->attr);
|
||||
emit_init_backings(out, n->lhs);
|
||||
emit_init_backings(out, n->rhs);
|
||||
emit_init_backings(out, n->cond);
|
||||
emit_init_backings(out, n->body);
|
||||
emit_init_backings(out, n->els);
|
||||
emit_init_backings(out, n->list);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cg_file(Cg *c, FILE *out, Node *file)
|
||||
{
|
||||
@@ -17697,5 +17975,6 @@ cg_file(Cg *c, FILE *out, Node *file)
|
||||
let_pre_intern(c, file);
|
||||
emit_data(c, out);
|
||||
emit_defs(c, out, file);
|
||||
emit_init_backings(out, file->list);
|
||||
emit_lets(c, out, file);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,9 @@ struct Cg {
|
||||
* the bare-`main` carve-out: a dep's `fn main` must
|
||||
* mangle on its path like any decl; only the root
|
||||
* entry stays bare. */
|
||||
const char *init_dispatch_symbol; /* package-mode entry hook; the
|
||||
* root-owned dispatcher runs the complete reachable
|
||||
* package-init schedule before source main/test main. */
|
||||
};
|
||||
|
||||
/* cgen.c */
|
||||
|
||||
262
cmd/w6c/main.c
262
cmd/w6c/main.c
@@ -1,7 +1,167 @@
|
||||
#include "gc.h"
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
struct publication {
|
||||
const char *dst;
|
||||
char *stage;
|
||||
char *backup;
|
||||
int had_old;
|
||||
int installed;
|
||||
};
|
||||
|
||||
static char *
|
||||
publish_path(const char *dst, const char *kind)
|
||||
{
|
||||
int n = snprintf(NULL, 0, "%s.w6c.%ld.%s", dst, (long)getpid(), kind);
|
||||
if (n < 0 || (size_t)n == (size_t)-1) return NULL;
|
||||
char *p = malloc((size_t)n + 1);
|
||||
if (p == NULL) return NULL;
|
||||
if (snprintf(p, (size_t)n + 1, "%s.w6c.%ld.%s", dst,
|
||||
(long)getpid(), kind) != n) {
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static int
|
||||
materialize_stream(FILE *src, struct publication *p)
|
||||
{
|
||||
p->stage = publish_path(p->dst, "new");
|
||||
p->backup = publish_path(p->dst, "old");
|
||||
if (p->stage == NULL || p->backup == NULL) {
|
||||
fputs("w6c: out of memory\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
int fd = open(p->stage, O_WRONLY | O_CREAT | O_EXCL, 0644);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "w6c: cannot stage %s\n", p->dst);
|
||||
return -1;
|
||||
}
|
||||
FILE *out = fdopen(fd, "wb");
|
||||
if (out == NULL) {
|
||||
close(fd);
|
||||
unlink(p->stage);
|
||||
return -1;
|
||||
}
|
||||
int bad = fflush(src) != 0 || fseek(src, 0, SEEK_SET) != 0;
|
||||
unsigned char buf[65536];
|
||||
while (!bad) {
|
||||
size_t n = fread(buf, 1, sizeof buf, src);
|
||||
if (n != 0 && fwrite(buf, 1, n, out) != n) bad = 1;
|
||||
if (n < sizeof buf) {
|
||||
if (ferror(src)) bad = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fclose(out) != 0) bad = 1;
|
||||
if (bad) {
|
||||
unlink(p->stage);
|
||||
fprintf(stderr, "w6c: cannot stage %s\n", p->dst);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
publication_free(struct publication *p)
|
||||
{
|
||||
free(p->backup);
|
||||
free(p->stage);
|
||||
}
|
||||
|
||||
/* A publication rollback name is occupied by any terminal directory entry,
|
||||
* including a dangling symlink. Never follow it while deciding whether the
|
||||
* compiler may park an existing destination there. */
|
||||
static int
|
||||
path_exists_nofollow(const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
if (lstat(path, &st) == 0) return 1;
|
||||
return errno == ENOENT ? 0 : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
path_is_regular_nofollow(const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
return lstat(path, &st) == 0 && S_ISREG(st.st_mode);
|
||||
}
|
||||
|
||||
/* Publish assembly and interface as one rollback group. Named output bytes
|
||||
* never change before checking and lowering have both succeeded. */
|
||||
static int
|
||||
publish_all(struct publication *p, int n)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (!path_is_regular_nofollow(p[i].stage)) {
|
||||
fprintf(stderr, "w6c: publication stage is not a regular file for %s\n",
|
||||
p[i].dst);
|
||||
goto rollback;
|
||||
}
|
||||
struct stat st;
|
||||
if (lstat(p[i].dst, &st) == 0) {
|
||||
if (!S_ISREG(st.st_mode)) {
|
||||
fprintf(stderr,
|
||||
"w6c: publication destination is not a regular file: %s\n",
|
||||
p[i].dst);
|
||||
goto rollback;
|
||||
}
|
||||
} else if (errno != ENOENT) {
|
||||
fprintf(stderr, "w6c: cannot inspect publication destination %s\n",
|
||||
p[i].dst);
|
||||
goto rollback;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int exists = path_exists_nofollow(p[i].backup);
|
||||
if (exists != 0) {
|
||||
if (exists < 0)
|
||||
fprintf(stderr, "w6c: cannot inspect publication backup for %s\n",
|
||||
p[i].dst);
|
||||
else
|
||||
fprintf(stderr, "w6c: publication backup exists for %s\n",
|
||||
p[i].dst);
|
||||
goto rollback;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (rename(p[i].dst, p[i].backup) == 0)
|
||||
p[i].had_old = 1;
|
||||
else if (errno != ENOENT) {
|
||||
fprintf(stderr, "w6c: cannot preserve %s\n", p[i].dst);
|
||||
goto rollback;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (rename(p[i].stage, p[i].dst) != 0) {
|
||||
fprintf(stderr, "w6c: cannot publish %s\n", p[i].dst);
|
||||
goto rollback;
|
||||
}
|
||||
p[i].installed = 1;
|
||||
}
|
||||
/* Installation is the commit point. Backup cleanup cannot truthfully
|
||||
* turn a completely installed pair into a rejected compilation. */
|
||||
for (int i = 0; i < n; i++)
|
||||
if (p[i].had_old && unlink(p[i].backup) != 0) {
|
||||
fprintf(stderr, "w6c: cannot remove backup for %s\n", p[i].dst);
|
||||
}
|
||||
return 0;
|
||||
|
||||
rollback:
|
||||
for (int i = n - 1; i >= 0; i--) {
|
||||
if (p[i].installed) (void)unlink(p[i].dst);
|
||||
if (p[i].had_old) (void)rename(p[i].backup, p[i].dst);
|
||||
if (p[i].stage != NULL) (void)unlink(p[i].stage);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
slurp(const char *path, char **outbuf, u64 *outlen)
|
||||
@@ -148,8 +308,10 @@ bind_import_names(Node *list, struct importin *imports, int nimports,
|
||||
}
|
||||
if (name != NULL) {
|
||||
u->usepkgname = name;
|
||||
u->str = u->usealias ? u->usealias : name;
|
||||
u->strlen = strlen(u->str);
|
||||
if (!u->useblank) {
|
||||
u->str = u->usealias ? u->usealias : name;
|
||||
u->strlen = strlen(u->str);
|
||||
}
|
||||
} else if (!u->imported) {
|
||||
fprintf(stderr,
|
||||
"w6c: import %s has no declared package name in direct export data\n",
|
||||
@@ -159,8 +321,10 @@ bind_import_names(Node *list, struct importin *imports, int nimports,
|
||||
/* A closure-only import need not contribute declarations to this
|
||||
* interface. Keep it canonical-path keyed without reinstalling
|
||||
* the historical path-leaf qualifier. */
|
||||
u->str = u->usealias ? u->usealias : u->usepath;
|
||||
u->strlen = strlen(u->str);
|
||||
if (!u->useblank) {
|
||||
u->str = u->usealias ? u->usealias : u->usepath;
|
||||
u->strlen = strlen(u->str);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -184,6 +348,8 @@ main(int argc, char **argv)
|
||||
const char *wwiout = NULL; /* -I <out.wwi>: M2 export-data producer */
|
||||
const char *testsupport = NULL;
|
||||
const char *testtarget = NULL;
|
||||
const char *packageinit = NULL;
|
||||
const char *initdispatch = NULL;
|
||||
int testmode = 0;
|
||||
int testpackage = 0;
|
||||
int commandpackage = 0;
|
||||
@@ -215,6 +381,18 @@ main(int argc, char **argv)
|
||||
commandpackage = 1;
|
||||
} else if (strcmp(a, "--entry") == 0) {
|
||||
entrymode = 1;
|
||||
} else if (strcmp(a, "--package-init-symbol") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
fputs("w6c: --package-init-symbol requires arg\n", stderr);
|
||||
return 2;
|
||||
}
|
||||
packageinit = argv[++i];
|
||||
} else if (strcmp(a, "--init-dispatch-symbol") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
fputs("w6c: --init-dispatch-symbol requires arg\n", stderr);
|
||||
return 2;
|
||||
}
|
||||
initdispatch = argv[++i];
|
||||
} else if (strcmp(a, "--test-support-module") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
fputs("w6c: --test-support-module requires arg\n", stderr);
|
||||
@@ -256,10 +434,14 @@ main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
if (src == NULL) {
|
||||
fputs("usage: w6c [-T|--test-package] [--command-package] [--entry] [--test-target-package path] [-c] [-I out.wwi] "
|
||||
fputs("usage: w6c [-T|--test-package] [--command-package] [--entry] [--package-init-symbol symbol] [--init-dispatch-symbol symbol] [--test-target-package path] [-c] [-I out.wwi] "
|
||||
"[--import path dep.wwi]... [--import-map source path]... [-o out.s] file.ww\n", stderr);
|
||||
return 2;
|
||||
}
|
||||
if (out != NULL && wwiout != NULL && strcmp(out, wwiout) == 0) {
|
||||
fputs("w6c: assembly and interface outputs must be distinct\n", stderr);
|
||||
return 2;
|
||||
}
|
||||
if (nimports > 0 && !sepmode) {
|
||||
fputs("w6c: --import requires -c\n", stderr);
|
||||
return 2;
|
||||
@@ -272,6 +454,19 @@ main(int argc, char **argv)
|
||||
fputs("w6c: --entry, --test-package, and --command-package require -c\n", stderr);
|
||||
return 2;
|
||||
}
|
||||
if ((packageinit != NULL || initdispatch != NULL) && !sepmode) {
|
||||
fputs("w6c: package initialization symbols require -c\n", stderr);
|
||||
return 2;
|
||||
}
|
||||
if (packageinit != NULL && packageinit[0] == '\0') {
|
||||
fputs("w6c: --package-init-symbol is empty\n", stderr);
|
||||
return 2;
|
||||
}
|
||||
if (initdispatch != NULL && (initdispatch[0] == '\0' || !entrymode)) {
|
||||
fputs("w6c: --init-dispatch-symbol requires --entry and a non-empty symbol\n",
|
||||
stderr);
|
||||
return 2;
|
||||
}
|
||||
if (testmode && testpackage) {
|
||||
fputs("w6c: -T and --test-package are mutually exclusive\n", stderr);
|
||||
return 2;
|
||||
@@ -427,27 +622,33 @@ main(int argc, char **argv)
|
||||
if (testsupport != NULL) c.test_module = testsupport;
|
||||
c.test_target = testtarget;
|
||||
c.sep_mode = sepmode;
|
||||
c.package_init_symbol = packageinit;
|
||||
check_file(&c, file);
|
||||
if (c.errs) return 1;
|
||||
|
||||
/* M2 export-data: write the `.wwi` after a clean check, before cgen.
|
||||
* Dead on the live path (no existing invocation passes -I); the
|
||||
* producer's check_exported_type may reject a dangling export. */
|
||||
/* Anonymous streams keep every named destination untouched until checking,
|
||||
* export writing, and lowering have all succeeded. A cgen fatal exits with
|
||||
* only kernel-owned anonymous files open. */
|
||||
FILE *wf = NULL;
|
||||
if (wwiout) {
|
||||
FILE *wf = fopen(wwiout, "wb");
|
||||
wf = tmpfile();
|
||||
if (wf == NULL) {
|
||||
fprintf(stderr, "w6c: cannot open %s\n", wwiout);
|
||||
fprintf(stderr, "w6c: cannot stage %s\n", wwiout);
|
||||
return 1;
|
||||
}
|
||||
if (wwi_emit(&c, wf, file) != 0 || fflush(wf) != 0
|
||||
|| ferror(wf)) {
|
||||
fclose(wf);
|
||||
return 1;
|
||||
}
|
||||
if (wwi_emit(&c, wf, file) != 0) { fclose(wf); return 1; }
|
||||
fclose(wf);
|
||||
}
|
||||
|
||||
FILE *of = stdout;
|
||||
if (out) {
|
||||
of = fopen(out, "wb");
|
||||
of = tmpfile();
|
||||
if (of == NULL) {
|
||||
fprintf(stderr, "w6c: cannot open %s\n", out);
|
||||
fprintf(stderr, "w6c: cannot stage %s\n", out);
|
||||
if (wf != NULL) fclose(wf);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -458,13 +659,46 @@ main(int argc, char **argv)
|
||||
* properties. Legacy raw invocations without -I remain entry-like; every
|
||||
* driver package now supplies -I, and only link roots add --entry. */
|
||||
cg.sep_isdep = (wwiout != NULL && !entrymode);
|
||||
cg.init_dispatch_symbol = initdispatch;
|
||||
cg_file(&cg, of, file);
|
||||
|
||||
if (of != stdout && (fflush(of) != 0 || ferror(of))) {
|
||||
fclose(of);
|
||||
if (wf != NULL) fclose(wf);
|
||||
return 1;
|
||||
}
|
||||
struct publication pub[2] = {0};
|
||||
int npub = 0;
|
||||
if (wwiout != NULL) {
|
||||
pub[npub].dst = wwiout;
|
||||
if (materialize_stream(wf, &pub[npub]) < 0) goto publish_fail;
|
||||
npub++;
|
||||
}
|
||||
if (out != NULL) {
|
||||
pub[npub].dst = out;
|
||||
if (materialize_stream(of, &pub[npub]) < 0) goto publish_fail;
|
||||
npub++;
|
||||
}
|
||||
if (wf != NULL) fclose(wf);
|
||||
if (of != stdout) fclose(of);
|
||||
if (publish_all(pub, npub) < 0) goto publish_free_fail;
|
||||
for (int i = 0; i < npub; i++) publication_free(&pub[i]);
|
||||
freearena(a);
|
||||
for (int i = 0; i < nimports; i++) free(imports[i].buf);
|
||||
free(imports);
|
||||
free(maps);
|
||||
free(buf);
|
||||
return 0;
|
||||
|
||||
publish_fail:
|
||||
if (wf != NULL) fclose(wf);
|
||||
if (of != stdout) fclose(of);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (pub[i].stage != NULL) (void)unlink(pub[i].stage);
|
||||
publication_free(&pub[i]);
|
||||
}
|
||||
return 1;
|
||||
publish_free_fail:
|
||||
for (int i = 0; i < npub; i++) publication_free(&pub[i]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ static const char *
|
||||
wwi_use_path(Checker *c, const char *owner, int source, const char *alias)
|
||||
{
|
||||
for (Node *u = c->file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->str == NULL
|
||||
if (u->kind != N_USE || u->useblank || u->str == NULL
|
||||
|| u->sourceid != source || strcmp(u->str, alias) != 0)
|
||||
continue;
|
||||
int same = owner == NULL ? u->imported == 0
|
||||
@@ -592,6 +592,7 @@ factcmp(const void *a, const void *b)
|
||||
static int
|
||||
wwi_is_decl(Node *d)
|
||||
{
|
||||
if (d->initfn || d->initsynthetic) return 0;
|
||||
return d->kind == N_FNDECL || d->kind == N_TYPEDECL
|
||||
|| d->kind == N_DEF || d->kind == N_LET;
|
||||
}
|
||||
@@ -791,7 +792,7 @@ wwi_collect_decl(struct factset *fs, const char *owner, Node *d)
|
||||
static int
|
||||
wwi_use_owned(Node *u, const char *owner, int source)
|
||||
{
|
||||
return u->kind == N_USE && u->imported != 0
|
||||
return u->kind == N_USE && !u->useblank && u->imported != 0
|
||||
&& u->sourceid == source && wwi_mod_eq(u->module, owner);
|
||||
}
|
||||
|
||||
@@ -803,7 +804,7 @@ wwi_emit_imports(FILE *of, Node *file, const char *owner, int source,
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
int owned = imported ? wwi_use_owned(u, owner, source)
|
||||
: u->kind == N_USE && u->imported == 0
|
||||
&& u->sourceid == source;
|
||||
&& !u->useblank && u->sourceid == source;
|
||||
if (owned) nuse++;
|
||||
}
|
||||
if (nuse == 0) return;
|
||||
@@ -813,7 +814,7 @@ wwi_emit_imports(FILE *of, Node *file, const char *owner, int source,
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
int owned = imported ? wwi_use_owned(u, owner, source)
|
||||
: u->kind == N_USE && u->imported == 0
|
||||
&& u->sourceid == source;
|
||||
&& !u->useblank && u->sourceid == source;
|
||||
if (!owned) continue;
|
||||
us[k].path = u->usepath ? u->usepath : u->str;
|
||||
us[k].idx = k;
|
||||
@@ -836,7 +837,7 @@ wwi_primary_section_has(Checker *c, Node *file, struct factset *fs,
|
||||
struct declent *exports, int nexports, int source)
|
||||
{
|
||||
for (Node *u = file->list; u; u = u->next)
|
||||
if (u->kind == N_USE && u->imported == 0
|
||||
if (u->kind == N_USE && !u->useblank && u->imported == 0
|
||||
&& u->sourceid == source)
|
||||
return 1;
|
||||
for (int i = 0; i < fs->nprivate; i++)
|
||||
|
||||
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*);
|
||||
|
||||
1273
cmd/ww/main.c
1273
cmd/ww/main.c
File diff suppressed because it is too large
Load Diff
@@ -75,7 +75,7 @@ export type stream = struct {
|
||||
//
|
||||
// Returns the stream BY VALUE; the caller passes `&b.vt` to the io
|
||||
// dispatchers. Mirrors ref/hare/bufio/stream.ha:69.
|
||||
export fn init(src: io.stream, rbuf: []u8, wbuf: []u8) stream = {
|
||||
export fn newstream(src: io.stream, rbuf: []u8, wbuf: []u8) stream = {
|
||||
let r: stream;
|
||||
r.vt.reader = (&bread): *io.reader;
|
||||
r.vt.writer = (&bwrite): *io.writer;
|
||||
|
||||
@@ -10,7 +10,7 @@ package random;
|
||||
export type random = u64;
|
||||
|
||||
// Mirrors Hare's random::init.
|
||||
export fn init(seed: u64) random = { return seed: random; };
|
||||
export fn fromseed(seed: u64) random = { return seed: random; };
|
||||
|
||||
// SplitMix64, per Hare's random::next.
|
||||
export fn next(r: *random) u64 = {
|
||||
|
||||
@@ -131,9 +131,17 @@ export type node = struct {
|
||||
usepath: str, // N_USE: canonical vendor-expanded identity
|
||||
usealias: str, // N_USE: explicit file-local alias, or empty
|
||||
usepkgname: str,// N_USE: imported declared package name
|
||||
useblank: i32, // N_USE: `_` spelling; no source binding
|
||||
pkgname: str, // declared package name; independent of canonical nmod
|
||||
sourceid: i32, // lexical source-file scope in an owner/export unit
|
||||
used: i32, // N_USE: checker observed this file-local binding
|
||||
initfn: i32, // special source `fn init`, absent from scope/API
|
||||
initsynthetic: i32,// compiler-owned variable helper/package task
|
||||
runtimeinit: i32,// module let lowered through an init helper
|
||||
initorder: u64,// 1-based variable or init-function order
|
||||
linksym: str, // raw compiler-private assembler symbol
|
||||
refdecl: *node,// checker-resolved value declaration
|
||||
initmark: u64,// checker-private initializer dependency walk mark
|
||||
imported: i32, // M1 #22: decl reached via `//ww:module <path>` import
|
||||
// boundary (vs root/primary); gates root-only bare main
|
||||
};
|
||||
@@ -142,7 +150,7 @@ export fn newnode(k: nkind, file: str, line: i32, col: i32) *node = {
|
||||
// fval cast-init: 990's wwdump TK_FLOAT diff requires this file
|
||||
// to tokenise identically through C and ww (lex.ww:382 has the
|
||||
// same workaround for the cstage %g-formats vs ww-skips divergence).
|
||||
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, packed=0, type_=nil, tsuffix="", nmod="", usesource="", usepath="", usealias="", usepkgname="", pkgname="", sourceid=0, used=0, imported=0})!;
|
||||
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, packed=0, type_=nil, tsuffix="", nmod="", usesource="", usepath="", usealias="", usepkgname="", useblank=0, pkgname="", sourceid=0, used=0, initfn=0, initsynthetic=0, runtimeinit=0, initorder=0u64, linksym="", refdecl=nil, initmark=0u64, imported=0})!;
|
||||
return n;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@ fn parseuse(p: *parser) *node = {
|
||||
let alias: str;
|
||||
let first: str;
|
||||
if (p.curkind == tkind.TK_UNDER) {
|
||||
errmsg(p, "blank import alias _ is not implemented");
|
||||
alias = "_";
|
||||
n.useblank = 1;
|
||||
advance(p);
|
||||
expectident(p, &first);
|
||||
} else {
|
||||
@@ -34,7 +33,10 @@ fn parseuse(p: *parser) *node = {
|
||||
expectident(p, &leaf);
|
||||
path = strings.concat(path, ".", leaf);
|
||||
};
|
||||
if (alias.len > 0) { n.str = alias; } else { n.str = leaf; };
|
||||
if (n.useblank != 0) {
|
||||
let empty: str;
|
||||
n.str = empty;
|
||||
} else { if (alias.len > 0) { n.str = alias; } else { n.str = leaf; }; };
|
||||
n.usesource = path;
|
||||
n.usepath = path;
|
||||
n.usealias = alias;
|
||||
@@ -68,14 +70,16 @@ fn parsedef(p: *parser, exported: i32) *node = {
|
||||
};
|
||||
|
||||
fn parselet(p: *parser, exported: i32) *node = {
|
||||
let pf: str = p.curfile;
|
||||
let pl: i32 = p.curline;
|
||||
let pc: i32 = p.curcol;
|
||||
// Accept `let` or `const`. Const-bound bindings are marked via
|
||||
// n.op = tkind.TK_CONST so the checker can reject reassignment.
|
||||
let is_const: i32 = 0;
|
||||
if (p.curkind == tkind.TK_CONST) { is_const = 1; };
|
||||
advance(p);
|
||||
// The declaration position is the binding identifier, matching the C
|
||||
// parser and the source anchor used by package-initialization diagnostics.
|
||||
let pf: str = p.curfile;
|
||||
let pl: i32 = p.curline;
|
||||
let pc: i32 = p.curcol;
|
||||
let n: *node = newnode(nkind.N_LET, pf, pl, pc);
|
||||
n.nmod = p.curmod;
|
||||
let id: str;
|
||||
|
||||
@@ -113,7 +113,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
view.ptr = src;
|
||||
view.len = nlen: i32;
|
||||
let fname: str = strings.dup(view);
|
||||
init(&s, fname, buf, blen);
|
||||
parserinit(&s, fname, buf, blen);
|
||||
|
||||
if (parse(&s) != 0) { return 1; };
|
||||
if (encode(&s) != 0) { return 1; };
|
||||
|
||||
@@ -136,7 +136,7 @@ fn reglookup(p: *u8, n: u64) i32 = {
|
||||
return D_NONE;
|
||||
};
|
||||
|
||||
export fn init(a: *asm_, file: str, src: *u8, len: u64) void = {
|
||||
export fn parserinit(a: *asm_, file: str, src: *u8, len: u64) void = {
|
||||
a.file = file;
|
||||
a.src = src;
|
||||
a.srclen = len;
|
||||
|
||||
@@ -3,10 +3,204 @@
|
||||
package main;
|
||||
|
||||
import os;
|
||||
import strconv;
|
||||
import strings;
|
||||
import syntax;
|
||||
import wcc;
|
||||
|
||||
type publication = struct {
|
||||
dst: *u8,
|
||||
stage: str,
|
||||
backup: str,
|
||||
hadold: bool,
|
||||
installed: bool,
|
||||
};
|
||||
|
||||
fn publicationpath(dst: *u8, kind: str) str = {
|
||||
return strings.concat(pathstr(dst), ".w6c.",
|
||||
strconv.i32tos(os.getpid(), strconv.base.DEC), ".", kind);
|
||||
};
|
||||
|
||||
// Open a destination-adjacent file and immediately unlink its name. Cgen's
|
||||
// fatal exit then leaves only a kernel-owned fd, never a partial destination
|
||||
// or named staging artifact.
|
||||
fn anonymousfd(dst: *u8, kind: str) i32 = {
|
||||
let path: str = publicationpath(dst, kind);
|
||||
let fd: i32 = os.open(path,
|
||||
os.flag.RDWR | os.flag.CREATE | os.flag.EXCL, 384i32);
|
||||
if (fd < 0) {
|
||||
os.write(2, "w6c: cannot create anonymous output\n".ptr,
|
||||
"w6c: cannot create anonymous output\n".len: u64);
|
||||
return -1;
|
||||
};
|
||||
if (os.remove(path) != 0) {
|
||||
os.close(fd);
|
||||
os.write(2, "w6c: cannot unlink anonymous output\n".ptr,
|
||||
"w6c: cannot unlink anonymous output\n".len: u64);
|
||||
return -1;
|
||||
};
|
||||
return fd;
|
||||
};
|
||||
|
||||
fn copystream(src: i32, dst: i32) bool = {
|
||||
if (os.lseek(src, 0i64, os.whence.SET) < 0) { return false; };
|
||||
let buf: [65536]u8;
|
||||
for (true) {
|
||||
let n: i64 = os.read(src, buf.ptr, 65536u64);
|
||||
if (n < 0) { return false; };
|
||||
if (n == 0) { return true; };
|
||||
let wr: (i64 | os.oserror) = os.writeall(dst, buf.ptr, n: u64);
|
||||
match (wr) {
|
||||
case let wrote: i64 => { if (wrote != n) { return false; }; };
|
||||
case let e: os.oserror => return false;
|
||||
};
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn materializestream(src: i32, dst: *u8, p: *publication) bool = {
|
||||
p.dst = dst;
|
||||
p.stage = publicationpath(dst, "new");
|
||||
p.backup = publicationpath(dst, "old");
|
||||
p.hadold = false;
|
||||
p.installed = false;
|
||||
let fd: i32 = os.open(p.stage,
|
||||
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 420i32);
|
||||
if (fd < 0) { return false; };
|
||||
let ok: bool = copystream(src, fd);
|
||||
if (os.close(fd) != 0) { ok = false; };
|
||||
if (!ok) { os.remove(p.stage); };
|
||||
return ok;
|
||||
};
|
||||
|
||||
// A publication rollback name is occupied by any terminal directory entry,
|
||||
// including a dangling symlink. Never follow it while deciding whether the
|
||||
// compiler may park an existing destination there.
|
||||
fn pathexistsnofollow(path: str) i32 = {
|
||||
let fi: os.filestat;
|
||||
match (os.lstat(&fi, path)) {
|
||||
case void => return 1;
|
||||
case let e: os.oserror => {
|
||||
if ((e: i64) == -2i64) { return 0; };
|
||||
return -1;
|
||||
};
|
||||
};
|
||||
return -1;
|
||||
};
|
||||
|
||||
fn pathisregularnofollow(path: str) bool = {
|
||||
let fi: os.filestat;
|
||||
match (os.lstat(&fi, path)) {
|
||||
case void => {
|
||||
return (((fi.mode: u32) & 61440u32) == (os.mode.REG: u32));
|
||||
};
|
||||
case let e: os.oserror => return false;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn publishdiag(prefix: str, dst: *u8) void = {
|
||||
os.write(2, prefix.ptr, prefix.len: u64);
|
||||
let path: str = pathstr(dst);
|
||||
os.write(2, path.ptr, path.len: u64);
|
||||
os.write(2, "\n".ptr, 1u64);
|
||||
};
|
||||
|
||||
fn publishall(p: *publication, n: i32) bool = {
|
||||
let i: i32 = 0;
|
||||
let ok: bool = true;
|
||||
for (i < n) {
|
||||
if (!pathisregularnofollow(p[i].stage)) {
|
||||
publishdiag("w6c: publication stage is not a regular file for ",
|
||||
p[i].dst);
|
||||
ok = false;
|
||||
break;
|
||||
};
|
||||
let fi: os.filestat;
|
||||
match (os.lstat(&fi, pathstr(p[i].dst))) {
|
||||
case void => {
|
||||
if (((fi.mode: u32) & 61440u32) != (os.mode.REG: u32)) {
|
||||
publishdiag(
|
||||
"w6c: publication destination is not a regular file: ",
|
||||
p[i].dst);
|
||||
ok = false;
|
||||
};
|
||||
};
|
||||
case let e: os.oserror => {
|
||||
if ((e: i64) != -2i64) {
|
||||
publishdiag("w6c: cannot inspect publication destination ",
|
||||
p[i].dst);
|
||||
ok = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (!ok) { break; };
|
||||
i += 1;
|
||||
};
|
||||
if (ok) {
|
||||
i = 0;
|
||||
for (i < n) {
|
||||
let exists: i32 = pathexistsnofollow(p[i].backup);
|
||||
if (exists != 0) {
|
||||
if (exists < 0) {
|
||||
publishdiag("w6c: cannot inspect publication backup for ",
|
||||
p[i].dst);
|
||||
} else {
|
||||
publishdiag("w6c: publication backup exists for ", p[i].dst);
|
||||
};
|
||||
ok = false;
|
||||
break;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
if (ok) {
|
||||
i = 0;
|
||||
for (i < n) {
|
||||
let rc: i32 = os.rename(pathstr(p[i].dst), p[i].backup);
|
||||
if (rc == 0) { p[i].hadold = true; }
|
||||
else { if (rc != -2) {
|
||||
publishdiag("w6c: cannot preserve ", p[i].dst);
|
||||
ok = false;
|
||||
break;
|
||||
}; };
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
if (ok) {
|
||||
i = 0;
|
||||
for (i < n) {
|
||||
if (os.rename(p[i].stage, pathstr(p[i].dst)) != 0) {
|
||||
publishdiag("w6c: cannot publish ", p[i].dst);
|
||||
ok = false;
|
||||
break;
|
||||
};
|
||||
p[i].installed = true;
|
||||
i += 1;
|
||||
};
|
||||
if (ok) {
|
||||
// Installation is the commit point. Backup cleanup cannot
|
||||
// truthfully turn a completely installed pair into rejection.
|
||||
i = 0;
|
||||
for (i < n) {
|
||||
if (p[i].hadold && os.remove(p[i].backup) != 0) {
|
||||
publishdiag("w6c: cannot remove backup for ", p[i].dst);
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
};
|
||||
i = n - 1;
|
||||
for (i >= 0) {
|
||||
if (p[i].installed) { os.remove(pathstr(p[i].dst)); };
|
||||
if (p[i].hadold) { os.rename(p[i].backup, pathstr(p[i].dst)); };
|
||||
if (p[i].stage.len != 0) { os.remove(p[i].stage); };
|
||||
i -= 1;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn cstreq(a: *u8, lit: str) bool = {
|
||||
let n: u64 = lit.len: u64;
|
||||
let i: u64 = 0u64;
|
||||
@@ -192,8 +386,10 @@ fn bindimportnames(list: *syntax.node, asts: []*syntax.node, paths: []*u8,
|
||||
};
|
||||
if (name.len > 0) {
|
||||
u.usepkgname = name;
|
||||
if (u.usealias.len > 0) { u.str = u.usealias; }
|
||||
else { u.str = name; };
|
||||
if (u.useblank == 0) {
|
||||
if (u.usealias.len > 0) { u.str = u.usealias; }
|
||||
else { u.str = name; };
|
||||
};
|
||||
} else { if (u.imported == 0) {
|
||||
let pre: str = "w6c: import ";
|
||||
let post: str = " has no declared package name in direct export data\n";
|
||||
@@ -205,8 +401,10 @@ fn bindimportnames(list: *syntax.node, asts: []*syntax.node, paths: []*u8,
|
||||
// A closure-only import may have no declarations in this
|
||||
// interface. Its canonical path must never become a leaf
|
||||
// qualifier by fallback.
|
||||
if (u.usealias.len > 0) { u.str = u.usealias; }
|
||||
else { u.str = u.usepath; };
|
||||
if (u.useblank == 0) {
|
||||
if (u.usealias.len > 0) { u.str = u.usealias; }
|
||||
else { u.str = u.usepath; };
|
||||
};
|
||||
}; };
|
||||
};
|
||||
};
|
||||
@@ -225,6 +423,8 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
let testpackage: i32 = 0i32;
|
||||
let commandpackage: i32 = 0i32;
|
||||
let entrymode: i32 = 0i32;
|
||||
let packageinit: *u8 = nil;
|
||||
let initdispatch: *u8 = nil;
|
||||
let sepmode: i32 = 0i32; // -c: #22 M3 separate-compile / primary-
|
||||
// only codegen (emit imported==0 decls
|
||||
// only; treat `.wwi` deps as external)
|
||||
@@ -302,6 +502,22 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
commandpackage = 1i32;
|
||||
} else { if (cstreq(a, "--entry")) {
|
||||
entrymode = 1i32;
|
||||
} else { if (cstreq(a, "--package-init-symbol")) {
|
||||
i += 1;
|
||||
if (i >= argc) {
|
||||
let m: str = "w6c: --package-init-symbol requires arg\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
return 2;
|
||||
};
|
||||
packageinit = argv[i];
|
||||
} else { if (cstreq(a, "--init-dispatch-symbol")) {
|
||||
i += 1;
|
||||
if (i >= argc) {
|
||||
let m: str = "w6c: --init-dispatch-symbol requires arg\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
return 2;
|
||||
};
|
||||
initdispatch = argv[i];
|
||||
} else { if (cstreq(a, "--test-support-module")) {
|
||||
i += 1;
|
||||
if (i >= argc) {
|
||||
@@ -352,12 +568,17 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
return 2;
|
||||
};
|
||||
src = a;
|
||||
}; }; }; }; }; }; }; }; }; }; }; };
|
||||
}; }; }; }; }; }; }; }; }; }; }; }; }; };
|
||||
i += 1;
|
||||
};
|
||||
|
||||
if (src == nil) {
|
||||
let m: str = "usage: w6c_ww [-T|--test-package] [--command-package] [--entry] [--test-target-package path] [-c] [-I out.wwi] [--import path dep.wwi]... [--import-map source path]... [-o out.s] file.ww\n";
|
||||
let m: str = "usage: w6c_ww [-T|--test-package] [--command-package] [--entry] [--package-init-symbol symbol] [--init-dispatch-symbol symbol] [--test-target-package path] [-c] [-I out.wwi] [--import path dep.wwi]... [--import-map source path]... [-o out.s] file.ww\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
return 2;
|
||||
};
|
||||
if (out != nil && wwiout != nil && cstreq(out, pathstr(wwiout))) {
|
||||
let m: str = "w6c: assembly and interface outputs must be distinct\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
return 2;
|
||||
};
|
||||
@@ -377,6 +598,22 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
return 2;
|
||||
};
|
||||
if ((packageinit != nil || initdispatch != nil) && sepmode == 0) {
|
||||
let m: str = "w6c: package initialization symbols require -c\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
return 2;
|
||||
};
|
||||
if (packageinit != nil && packageinit[0u64] == 0u8) {
|
||||
let m: str = "w6c: --package-init-symbol is empty\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
return 2;
|
||||
};
|
||||
if (initdispatch != nil
|
||||
&& (initdispatch[0u64] == 0u8 || entrymode == 0)) {
|
||||
let m: str = "w6c: --init-dispatch-symbol requires --entry and a non-empty symbol\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
return 2;
|
||||
};
|
||||
if (testmode != 0 && testpackage != 0) {
|
||||
let m: str = "w6c: -T and --test-package are mutually exclusive\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
@@ -595,30 +832,82 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
f.list = importhead;
|
||||
};
|
||||
|
||||
// cgen writes directly to fd 1; dup2 keeps that implementation private.
|
||||
let wwifd: i32 = -1;
|
||||
if (wwiout != nil) {
|
||||
wwifd = anonymousfd(wwiout, "anon.wwi");
|
||||
if (wwifd < 0) { return 1; };
|
||||
};
|
||||
// cgen writes directly to fd 1. Redirect it only to an already-unlinked
|
||||
// anonymous file, never to the named destination.
|
||||
let asmfd: i32 = -1;
|
||||
if (out != nil) {
|
||||
let ofd: i32 = os.open(pathstr(out),
|
||||
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (ofd < 0) {
|
||||
let m: str = "w6c: cannot open output\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
asmfd = anonymousfd(out, "anon.asm");
|
||||
if (asmfd < 0) {
|
||||
if (wwifd >= 0) { os.close(wwifd); };
|
||||
return 1;
|
||||
};
|
||||
if (os.dup2(ofd, 1i32) < 0) {
|
||||
if (os.dup2(asmfd, 1i32) < 0) {
|
||||
let m: str = "w6c: dup2 failed\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.close(ofd);
|
||||
os.close(asmfd);
|
||||
if (wwifd >= 0) { os.close(wwifd); };
|
||||
return 1;
|
||||
};
|
||||
os.close(ofd);
|
||||
};
|
||||
|
||||
let testmodule: str;
|
||||
if (testsupport != nil) { testmodule = pathstr(testsupport); };
|
||||
let testtargetmodule: str;
|
||||
if (testtarget != nil) { testtargetmodule = pathstr(testtarget); };
|
||||
let interfaceout: str;
|
||||
if (wwiout != nil) { interfaceout = pathstr(wwiout); };
|
||||
return wcc.compilefile(f, testmode, testpackage, testmodule,
|
||||
testtargetmodule, sepmode, interfaceout, entrymode);
|
||||
let packageinitsymbol: str;
|
||||
if (packageinit != nil) { packageinitsymbol = pathstr(packageinit); };
|
||||
let initdispatchsymbol: str;
|
||||
if (initdispatch != nil) { initdispatchsymbol = pathstr(initdispatch); };
|
||||
let haswwi: i32 = 0;
|
||||
if (wwiout != nil) { haswwi = 1; };
|
||||
let compilerc: i32 = wcc.compilefile(f, testmode, testpackage, testmodule,
|
||||
testtargetmodule, sepmode, wwifd, haswwi, entrymode,
|
||||
packageinitsymbol, initdispatchsymbol);
|
||||
if (out != nil) { os.close(1i32); };
|
||||
if (compilerc != 0) {
|
||||
if (asmfd >= 0) { os.close(asmfd); };
|
||||
if (wwifd >= 0) { os.close(wwifd); };
|
||||
return compilerc;
|
||||
};
|
||||
let pubs: [2]publication;
|
||||
let pi: i32 = 0;
|
||||
for (pi < 2) {
|
||||
pubs[pi].dst = nil;
|
||||
pubs[pi].stage = "";
|
||||
pubs[pi].backup = "";
|
||||
pubs[pi].hadold = false;
|
||||
pubs[pi].installed = false;
|
||||
pi += 1;
|
||||
};
|
||||
let npub: i32 = 0;
|
||||
let stagedok: bool = true;
|
||||
if (wwiout != nil) {
|
||||
stagedok = materializestream(wwifd, wwiout, &pubs[npub]);
|
||||
if (stagedok) { npub += 1; };
|
||||
};
|
||||
if (stagedok && out != nil) {
|
||||
stagedok = materializestream(asmfd, out, &pubs[npub]);
|
||||
if (stagedok) { npub += 1; };
|
||||
};
|
||||
if (asmfd >= 0) { os.close(asmfd); };
|
||||
if (wwifd >= 0) { os.close(wwifd); };
|
||||
if (!stagedok) {
|
||||
pi = 0;
|
||||
for (pi < 2) {
|
||||
if (pubs[pi].stage.len != 0) { os.remove(pubs[pi].stage); };
|
||||
pi += 1;
|
||||
};
|
||||
let m: str = "w6c: cannot stage compiler output\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
return 1;
|
||||
};
|
||||
if (!publishall(&pubs[0], npub)) {
|
||||
return 1;
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
|
||||
@@ -3,8 +3,9 @@ package wcc;
|
||||
import syntax;
|
||||
|
||||
export fn compilefile(file: *syntax.node, testmode: i32, testpackage: i32,
|
||||
testmodule: str, testtarget: str, sepmode: i32, wwiout: str,
|
||||
entrymode: i32) i32 = {
|
||||
testmodule: str, testtarget: str, sepmode: i32, wwifd: i32,
|
||||
haswwi: i32,
|
||||
entrymode: i32, packageinitsymbol: str, initdispatchsymbol: str) i32 = {
|
||||
let tc: syntax.tctx;
|
||||
syntax.typesinit(&tc);
|
||||
let ck: checker;
|
||||
@@ -14,18 +15,19 @@ export fn compilefile(file: *syntax.node, testmode: i32, testpackage: i32,
|
||||
if (testmodule.len > 0) { ck.testmodule = testmodule; };
|
||||
ck.testtarget = testtarget;
|
||||
ck.sepmode = sepmode;
|
||||
ck.packageinitsymbol = packageinitsymbol;
|
||||
checkfile(&ck, file);
|
||||
if (ck.errs > 0) { return 1; };
|
||||
if (wwiout.len > 0) {
|
||||
if (wwiemit(&ck, file, wwiout) != 0) { return 1; };
|
||||
if (haswwi != 0) {
|
||||
if (wwiemitfd(&ck, file, wwifd) != 0) { return 1; };
|
||||
};
|
||||
|
||||
let cg: cgen;
|
||||
cgeninit(&cg);
|
||||
cg.sepmode = sepmode;
|
||||
if (wwiout.len > 0 && entrymode == 0) { cg.sepisdep = 1i32; };
|
||||
cgfile(&cg, file);
|
||||
return 0;
|
||||
if (haswwi != 0 && entrymode == 0) { cg.sepisdep = 1i32; };
|
||||
cg.initdispatchsymbol = initdispatchsymbol;
|
||||
return cgfile(&cg, file);
|
||||
};
|
||||
|
||||
export fn resolvefile(file: *syntax.node, verbose: i32,
|
||||
|
||||
@@ -509,6 +509,7 @@ type cgen = struct {
|
||||
// root entry stays bare. Like sepmode, NOT reset by cgeninit (per-fn).
|
||||
// Symmetric with cstage Cg.sep_isdep.
|
||||
sepisdep: i32,
|
||||
initdispatchsymbol: str, // root-owned complete package-init schedule
|
||||
};
|
||||
|
||||
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
||||
@@ -741,6 +742,17 @@ fn localfind(c: *cgen, name: str) i32 = {
|
||||
let cgoutstream: memio.stream;
|
||||
let cgoutmode: i32 = 0;
|
||||
let cgoutinit: i32 = 0;
|
||||
let cgwritefailed: i32 = 0;
|
||||
|
||||
fn cgwrite(p: *u8, n: u64) void = {
|
||||
if (cgwritefailed != 0) { return; };
|
||||
match (os.writeall(1i32, p, n)) {
|
||||
case let wrote: i64 => {
|
||||
if (wrote < 0 || (wrote: u64) != n) { cgwritefailed = 1; };
|
||||
};
|
||||
case let e: os.oserror => cgwritefailed = 1;
|
||||
};
|
||||
};
|
||||
|
||||
fn cgout_enable() void = {
|
||||
if (cgoutinit == 0) {
|
||||
@@ -754,7 +766,7 @@ fn cgout_disable() void = { cgoutmode = 0; };
|
||||
|
||||
fn cgout_flush() void = {
|
||||
if (cgoutstream.pos > 0) {
|
||||
os.write(1, cgoutstream.ptr, cgoutstream.pos: u64);
|
||||
cgwrite(cgoutstream.ptr, cgoutstream.pos: u64);
|
||||
memio.reset(&cgoutstream);
|
||||
};
|
||||
};
|
||||
@@ -769,7 +781,7 @@ fn emitbytes(p: *u8, n: u64) void = {
|
||||
// lib/log/log.ww stdprintln. #94 fold-eFinal.
|
||||
io.write(&cgoutstream.vt, buf);
|
||||
} else {
|
||||
os.write(1, p, n);
|
||||
cgwrite(p, n);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2894,6 +2906,35 @@ fn emittupledata(c: *cgen, name: str, module: str, tt: *syntax.node, rhs: *synta
|
||||
return true;
|
||||
};
|
||||
|
||||
fn emitinitbackings(n: *syntax.node) void = {
|
||||
for (n != nil) {
|
||||
if (n.kind == syntax.nkind.N_ARRLIT && n.linksym.len > 0) {
|
||||
let u: *syntax.tinfo = tichase(n.type_: *syntax.tinfo);
|
||||
if (u == nil || u.kind != syntax.tykind.TY_ARRAY) {
|
||||
let msg: str = "runtime package slice backing has no array type\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
emitline("DATAW ");
|
||||
emitbytes(n.linksym.ptr, n.linksym.len: u64);
|
||||
emitline("(SB),\"");
|
||||
let count: u64 = u.size;
|
||||
if (count == 0u64) { count = 1u64; };
|
||||
let i: u64 = 0u64;
|
||||
for (i < count) { emitdatawbyte(0u8); i += 1u64; };
|
||||
emitline("\"\n");
|
||||
};
|
||||
emitinitbackings(n.attr);
|
||||
emitinitbackings(n.lhs);
|
||||
emitinitbackings(n.rhs);
|
||||
emitinitbackings(n.cond);
|
||||
emitinitbackings(n.body);
|
||||
emitinitbackings(n.els);
|
||||
emitinitbackings(n.list);
|
||||
n = n.next;
|
||||
};
|
||||
};
|
||||
|
||||
fn emitletdataw(c: *cgen, file: *syntax.node) void = {
|
||||
let savedmod: str = c.curmod;
|
||||
let savedsource: i32 = c.cursource;
|
||||
@@ -3458,6 +3499,10 @@ fn collectfnrets(c: *cgen, file: *syntax.node) void = {
|
||||
let d: *syntax.node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == syntax.nkind.N_FNDECL) {
|
||||
if (d.initfn != 0 || d.initsynthetic != 0) {
|
||||
d = d.next;
|
||||
continue;
|
||||
};
|
||||
let f: *fnret = alloc(fnret{fname=d.str, fmod=d.nmod, rtype=d.lhs, params=d.list, frnext=c.fnrets})!;
|
||||
c.fnrets = f;
|
||||
};
|
||||
@@ -3788,12 +3833,16 @@ fn collectmods(c: *cgen, file: *syntax.node) void = {
|
||||
let d: *syntax.node = file.list;
|
||||
for (d != nil) {
|
||||
// M1 #22: record alias→path for the qualified-ref hint.
|
||||
if (d.kind == syntax.nkind.N_USE) {
|
||||
if (d.kind == syntax.nkind.N_USE && d.useblank == 0) {
|
||||
if (d.usepath.len > 0) {
|
||||
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, omod=d.nmod, sourceid=d.sourceid, mnext=c.uses})!;
|
||||
c.uses = um;
|
||||
};
|
||||
};
|
||||
if (d.initfn != 0 || d.initsynthetic != 0) {
|
||||
d = d.next;
|
||||
continue;
|
||||
};
|
||||
// Mirror collectfnrets' shape exactly (plain prepend in one
|
||||
// branch). Earlier nested-if/early-return variants tickled a
|
||||
// wwstage cgen bug that dropped most prepends.
|
||||
@@ -3988,6 +4037,10 @@ fn fficollect(c: *cgen, file: *syntax.node) void = {
|
||||
let d: *syntax.node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == syntax.nkind.N_FNDECL) {
|
||||
if (d.initfn != 0 || d.initsynthetic != 0) {
|
||||
d = d.next;
|
||||
continue;
|
||||
};
|
||||
let a: *syntax.node = d.attr;
|
||||
for (a != nil) {
|
||||
if (a.kind == syntax.nkind.N_ATTR) {
|
||||
|
||||
@@ -544,6 +544,12 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = {
|
||||
};
|
||||
|
||||
cgfnparams(c, fn_.list);
|
||||
if (c.initdispatchsymbol.len > 0 && syntax.streq(fn_.str, "main")
|
||||
&& fn_.imported == 0 && c.sepisdep == 0) {
|
||||
emitline("\tCALL\t");
|
||||
emitbytes(c.initdispatchsymbol.ptr, c.initdispatchsymbol.len: u64);
|
||||
emitline("(SB)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
// Iterate the fn body's statements directly rather than dispatching
|
||||
// the outermost N_BLOCK through cgstmt — cgblock now save/restores
|
||||
@@ -594,11 +600,13 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = {
|
||||
// (path-carrying `//ww:module-reset`, #57); only the root/link-entry
|
||||
// unit (wwiout==nil, #69) keeps the bare label.
|
||||
emitline("TEXT ");
|
||||
if (syntax.streq(fn_.str, "main") && fn_.imported == 0 && c.sepisdep == 0) {
|
||||
if (fn_.linksym.len > 0) {
|
||||
emitbytes(fn_.linksym.ptr, fn_.linksym.len: u64);
|
||||
} else { if (syntax.streq(fn_.str, "main") && fn_.imported == 0 && c.sepisdep == 0) {
|
||||
emitbytes(fn_.str.ptr, fn_.str.len: u64);
|
||||
} else {
|
||||
emitfnname(c, fn_.str, fn_.nmod);
|
||||
};
|
||||
}; };
|
||||
emitline(",$");
|
||||
emitint(frame: i64);
|
||||
emitline("\n");
|
||||
@@ -612,8 +620,9 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = {
|
||||
cgout_flush();
|
||||
};
|
||||
|
||||
fn cgfile(c: *cgen, file: *syntax.node) void = {
|
||||
if (file == nil) { return; };
|
||||
fn cgfile(c: *cgen, file: *syntax.node) i32 = {
|
||||
cgwritefailed = 0;
|
||||
if (file == nil) { return 0; };
|
||||
c.strlits = nil;
|
||||
c.strlitseq = 0;
|
||||
collectaliases(c, file);
|
||||
@@ -647,5 +656,7 @@ fn cgfile(c: *cgen, file: *syntax.node) void = {
|
||||
letpreintern(c, file);
|
||||
emitdatasection(c);
|
||||
emitdefconstants(c, file);
|
||||
emitinitbackings(file.list);
|
||||
emitletdataw(c, file);
|
||||
return cgwritefailed;
|
||||
};
|
||||
|
||||
@@ -8698,7 +8698,11 @@ fn cgcall(c: *cgen, n: *syntax.node) void = {
|
||||
emitline("\tCALL\tAX\n");
|
||||
} else {
|
||||
emitline("\tCALL\t");
|
||||
if (callee != nil) {
|
||||
if (callee != nil && callee.refdecl != nil
|
||||
&& callee.refdecl.linksym.len > 0) {
|
||||
emitbytes(callee.refdecl.linksym.ptr,
|
||||
callee.refdecl.linksym.len: u64);
|
||||
} else { if (callee != nil) {
|
||||
if (callee.kind == syntax.nkind.N_IDENT) {
|
||||
// Bare `f()` — same-module by ww's resolver,
|
||||
// so c.curmod is the disambiguation hint.
|
||||
@@ -8718,7 +8722,7 @@ fn cgcall(c: *cgen, n: *syntax.node) void = {
|
||||
};
|
||||
emitfnname(c, calleename, hint);
|
||||
};};
|
||||
};
|
||||
}; };
|
||||
emitline("(SB)\n");
|
||||
};
|
||||
// Caller cleanup for stack-passed args (args 7+, or any
|
||||
|
||||
@@ -2173,6 +2173,284 @@ fn cgarrlitfillbp(c: *cgen, arrtn: *syntax.node, rhs: *syntax.node, off: i32) vo
|
||||
};
|
||||
};
|
||||
|
||||
// Compiler-generated package-variable helpers need a memory-directed literal
|
||||
// path. It bypasses the finite tuple cursor, recursively fills nested
|
||||
// aggregates, and moves runtime slice-literal backing into canonical writable
|
||||
// package storage before the helper returns. The hook is gated solely by the
|
||||
// checker-owned N_LET.initsynthetic bit, so ordinary locals are unchanged.
|
||||
fn cginitfatal(msg: str) void = {
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.write(2, "\n".ptr, 1u64);
|
||||
os.exit(1);
|
||||
};
|
||||
|
||||
fn cginitzerobp(c: *cgen, off: i32, sz: i32) void = {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let k: i32 = 0;
|
||||
for (k + 8 <= sz) {
|
||||
emitline("\tMOVQ\tAX, "); emitoff((off + k): i64);
|
||||
emitline("(BP)\n"); k += 8;
|
||||
};
|
||||
if (k + 4 <= sz) {
|
||||
emitline("\tMOVL\tAX, "); emitoff((off + k): i64);
|
||||
emitline("(BP)\n"); k += 4;
|
||||
};
|
||||
if (k + 2 <= sz) {
|
||||
emitline("\tMOVW\tAX, "); emitoff((off + k): i64);
|
||||
emitline("(BP)\n"); k += 2;
|
||||
};
|
||||
if (k + 1 <= sz) {
|
||||
emitline("\tMOVB\tAX, "); emitoff((off + k): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
|
||||
fn cginitcopybp(c: *cgen, src: i32, dst: i32, sz: i32) void = {
|
||||
emitline("\tLEAQ\t"); emitoff(src: i64); emitline("(BP), SI\n");
|
||||
emitline("\tLEAQ\t"); emitoff(dst: i64); emitline("(BP), BX\n");
|
||||
aggcopy(c, sz);
|
||||
};
|
||||
|
||||
fn cginitstripcast(n: *syntax.node) *syntax.node = {
|
||||
for (n != nil && n.kind == syntax.nkind.N_CAST) { n = n.lhs; };
|
||||
return n;
|
||||
};
|
||||
|
||||
fn cginitfield(u: *syntax.tinfo, name: str) *syntax.tfield = {
|
||||
let f: *syntax.tfield = nil;
|
||||
if (u != nil) { f = u.fields; };
|
||||
for (f != nil) {
|
||||
if (syntax.streq(name, f.name)) { return f; };
|
||||
f = f.tnext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
fn cginitarraybp(c: *cgen, u: *syntax.tinfo, lit: *syntax.node,
|
||||
off: i32) void = {
|
||||
let et: *syntax.tinfo = u.sub;
|
||||
let eu: *syntax.tinfo = tichase(et);
|
||||
let esz: i32 = 1;
|
||||
if (eu != nil) { esz = eu.size: i32; };
|
||||
let total: i32 = u.alen: i32;
|
||||
cginitzerobp(c, off, u.size: i32);
|
||||
let idx: i32 = 0;
|
||||
let lastoff: i32 = 0;
|
||||
let e: *syntax.node = lit.list;
|
||||
for (e != nil) {
|
||||
if (e.kind == syntax.nkind.N_FIELD
|
||||
&& syntax.streq(e.str, "...")) {
|
||||
if (idx == 0) {
|
||||
cginitfatal("package initializer array repeat has no value");
|
||||
};
|
||||
for (idx < total) {
|
||||
cginitcopybp(c, lastoff, off + idx * esz, esz);
|
||||
idx += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (idx >= total) {
|
||||
cginitfatal("package initializer array literal exceeds destination");
|
||||
};
|
||||
lastoff = off + idx * esz;
|
||||
cginitvaluebp(c, et, e, lastoff);
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
|
||||
fn cginitstructbp(c: *cgen, u: *syntax.tinfo, lit: *syntax.node,
|
||||
off: i32) void = {
|
||||
cginitzerobp(c, off, u.size: i32);
|
||||
let e: *syntax.node = lit.list;
|
||||
for (e != nil) {
|
||||
let f: *syntax.tfield = cginitfield(u, e.str);
|
||||
if (f != nil) {
|
||||
cginitvaluebp(c, f.type_, e.lhs, off + (f.offset: i32));
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
|
||||
fn cginittuplebp(c: *cgen, u: *syntax.tinfo, lit: *syntax.node,
|
||||
off: i32) void = {
|
||||
cginitzerobp(c, off, u.size: i32);
|
||||
let te: *syntax.ttupleelem = u.tupleelems;
|
||||
let e: *syntax.node = lit.list;
|
||||
for (te != nil && e != nil) {
|
||||
cginitvaluebp(c, te.type_, e, off + (te.offset: i32));
|
||||
te = te.tnext;
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
|
||||
fn cginitslicebp(c: *cgen, u: *syntax.tinfo, lit: *syntax.node,
|
||||
off: i32) void = {
|
||||
if (lit.linksym.len == 0) {
|
||||
cginitfatal("runtime package slice literal has no canonical backing");
|
||||
};
|
||||
let at: *syntax.tinfo = tichase(lit.type_: *syntax.tinfo);
|
||||
if (at == nil || at.kind != syntax.tykind.TY_ARRAY || at.sub == nil) {
|
||||
cginitfatal("runtime package slice literal has no backing type");
|
||||
};
|
||||
let count: i32 = at.alen: i32;
|
||||
let bsz: i32 = at.size: i32;
|
||||
if (bsz != 0) {
|
||||
let scr: i32 = localalloc(c, "@initbacking", bsz, nil);
|
||||
cginitarraybp(c, at, lit, scr);
|
||||
emitline("\tLEAQ\t"); emitoff(scr: i64); emitline("(BP), SI\n");
|
||||
emitline("\tLEAQ\t"); emitbytes(lit.linksym.ptr,
|
||||
lit.linksym.len: u64); emitline("(SB), BX\n");
|
||||
aggcopy(c, bsz);
|
||||
};
|
||||
emitline("\tLEAQ\t"); emitbytes(lit.linksym.ptr,
|
||||
lit.linksym.len: u64); emitline("(SB), AX\n");
|
||||
emitline("\tMOVQ\tAX, "); emitoff(off: i64); emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t$"); emitint(count: i64); emitline(", ");
|
||||
emitoff((off + 8): i64); emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t$"); emitint(count: i64); emitline(", ");
|
||||
emitoff((off + 16): i64); emitline("(BP)\n");
|
||||
};
|
||||
|
||||
fn cginitcursorstore(c: *cgen, ti: *syntax.tinfo, gpcur: i32,
|
||||
ssecur: i32, off: i32) void = {
|
||||
let eslot: i32 = tupeslot(ti);
|
||||
if (eslot == 0) { return; };
|
||||
if (eslot > 8) {
|
||||
let k: i32 = 0;
|
||||
for (k < eslot / 8) {
|
||||
emitline("\tMOVQ\t"); emitline(tupreg(gpcur + k));
|
||||
emitline(", "); emitoff((off + k * 8): i64);
|
||||
emitline("(BP)\n"); k += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (syntax.typeisfloat(ti)) {
|
||||
let op: str = "MOVSD";
|
||||
if (syntax.typeisf32(ti)) { op = "MOVSS"; };
|
||||
emitline("\t"); emitline(op); emitline("\t");
|
||||
emitline(tupsse(ssecur)); emitline(", ");
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t"); emitline(tupreg(gpcur)); emitline(", ");
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
};
|
||||
|
||||
fn cginitaggregatebp(c: *cgen, ti: *syntax.tinfo, u: *syntax.tinfo,
|
||||
expr: *syntax.node, off: i32) void = {
|
||||
let sz: i32 = u.size: i32;
|
||||
if (expr.kind == syntax.nkind.N_CALL && sretretsizetn(c, ti) > 0) {
|
||||
c.sretdestoff = off;
|
||||
cgexpr(c, expr);
|
||||
c.sretdestoff = 0;
|
||||
return;
|
||||
};
|
||||
if (u.kind == syntax.tykind.TY_TUPLE && sretretsizetn(c, ti) == 0) {
|
||||
cgexpr(c, expr);
|
||||
let gpcur: i32 = 0;
|
||||
let ssecur: i32 = 0;
|
||||
let te: *syntax.ttupleelem = u.tupleelems;
|
||||
for (te != nil) {
|
||||
cginitcursorstore(c, te.type_, gpcur, ssecur,
|
||||
off + (te.offset: i32));
|
||||
if (syntax.typeisfloat(te.type_)) { ssecur += 1; }
|
||||
else { gpcur += tupeslot(te.type_) / 8; };
|
||||
te = te.tnext;
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (expr.kind == syntax.nkind.N_CALL
|
||||
&& u.kind == syntax.tykind.TY_STRUCT) {
|
||||
let fc: i32 = structfloatclassti(u);
|
||||
if (fc != 0) {
|
||||
cgexpr(c, expr);
|
||||
let nb: i32 = fc & 15;
|
||||
let gp: i32 = 0;
|
||||
let sse: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < nb) {
|
||||
let issse: bool = false;
|
||||
if (i == 0 && (fc & 16) != 0) { issse = true; };
|
||||
if (i == 1 && (fc & 32) != 0) { issse = true; };
|
||||
if (issse) {
|
||||
emitline("\tMOVSD\t"); emitline(tupsse(sse));
|
||||
emitline(", "); sse += 1;
|
||||
} else {
|
||||
emitline("\tMOVQ\t"); emitline(tupreg(gp));
|
||||
emitline(", "); gp += 1;
|
||||
};
|
||||
emitoff((off + i * 8): i64); emitline("(BP)\n");
|
||||
i += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (expr.kind == syntax.nkind.N_CALL && sz <= 24) {
|
||||
cgexpr(c, expr);
|
||||
cgaggregstore(c, "BP", off, sz, true);
|
||||
return;
|
||||
};
|
||||
if (aggargsrcaddr(c, expr, "SI")) {
|
||||
emitline("\tLEAQ\t"); emitoff(off: i64); emitline("(BP), BX\n");
|
||||
aggcopy(c, sz);
|
||||
return;
|
||||
};
|
||||
cginitfatal("package initializer aggregate expression shape unsupported");
|
||||
};
|
||||
|
||||
fn cginitvaluebp(c: *cgen, ti: *syntax.tinfo, expr: *syntax.node,
|
||||
off: i32) void = {
|
||||
let u: *syntax.tinfo = tichase(ti);
|
||||
let r: *syntax.node = cginitstripcast(expr);
|
||||
if (u == nil || r == nil) {
|
||||
cginitfatal("package initializer value has no type or expression");
|
||||
};
|
||||
if (u.kind == syntax.tykind.TY_ARRAY
|
||||
&& r.kind == syntax.nkind.N_ARRLIT) {
|
||||
cginitarraybp(c, u, r, off); return;
|
||||
};
|
||||
if (u.kind == syntax.tykind.TY_STRUCT
|
||||
&& r.kind == syntax.nkind.N_STRUCTLIT) {
|
||||
cginitstructbp(c, u, r, off); return;
|
||||
};
|
||||
if (u.kind == syntax.tykind.TY_TUPLE
|
||||
&& r.kind == syntax.nkind.N_TUPLE) {
|
||||
cginittuplebp(c, u, r, off); return;
|
||||
};
|
||||
if (u.kind == syntax.tykind.TY_SLICE
|
||||
&& r.kind == syntax.nkind.N_ARRLIT) {
|
||||
cginitslicebp(c, u, r, off); return;
|
||||
};
|
||||
if (u.kind == syntax.tykind.TY_TAGGED) {
|
||||
cgwidentaggedstore(c, u, expr, "BP", off, u.size: i32);
|
||||
return;
|
||||
};
|
||||
if (syntax.typeisstr(ti) || u.kind == syntax.tykind.TY_SLICE) {
|
||||
cgexpr(c, expr);
|
||||
emitline("\tMOVQ\tAX, "); emitoff(off: i64); emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, "); emitoff((off + 8): i64); emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, "); emitoff((off + 16): i64); emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
if (u.kind == syntax.tykind.TY_ARRAY
|
||||
|| u.kind == syntax.tykind.TY_STRUCT
|
||||
|| u.kind == syntax.tykind.TY_TUPLE) {
|
||||
cginitaggregatebp(c, ti, u, r, off); return;
|
||||
};
|
||||
cgexpr(c, expr);
|
||||
if (syntax.typeisfloat(ti)) {
|
||||
let op: str = "MOVSD";
|
||||
if (syntax.typeisf32(ti)) { op = "MOVSS"; };
|
||||
emitline("\t"); emitline(op); emitline("\tX0, ");
|
||||
emitoff(off: i64); emitline("(BP)\n"); return;
|
||||
};
|
||||
let sz: i32 = u.size: i32;
|
||||
if (sz != 1 && sz != 2 && sz != 4) { sz = 8; };
|
||||
emitline("\t"); emitline(storeopsz(sz)); emitline("\tAX, ");
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
};
|
||||
|
||||
// #152: reserve the let's frame slot, emit its initializer against the
|
||||
// PRE-binding locals chain, then link the binding. A self-shadowing init
|
||||
// (`let x = f(x)`) resolves x in the OUTER scope because nm is not yet in
|
||||
@@ -2205,6 +2483,20 @@ fn cgletbody(c: *cgen, n: *syntax.node, off: i32) void = {
|
||||
if (tn == nil) { tn = inferletcalltype(c, n.rhs); };
|
||||
if (n.rhs != nil) {
|
||||
let rhs: *syntax.node = n.rhs;
|
||||
let initlit: *syntax.node = cginitstripcast(rhs);
|
||||
if (n.initsynthetic != 0 && initlit != nil
|
||||
&& (initlit.kind == syntax.nkind.N_ARRLIT
|
||||
|| initlit.kind == syntax.nkind.N_STRUCTLIT
|
||||
|| initlit.kind == syntax.nkind.N_TUPLE)) {
|
||||
let iti: *syntax.tinfo = nil;
|
||||
if (tn != nil) { iti = tn.type_: *syntax.tinfo; };
|
||||
if (iti == nil && n.type_ != nil) {
|
||||
iti = n.type_: *syntax.tinfo;
|
||||
};
|
||||
cginitvaluebp(c, iti, rhs, off);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
// `let s: []T = alloc([], n)!;` / `?` shortcut (#32, #45).
|
||||
// Mirror of cstage cgen.c N_LET arrlit-empty branch: allocate
|
||||
// n*esz bytes via rt_malloc, then build the {ptr, 0, n} slice
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,14 +24,26 @@ import os;
|
||||
import syntax;
|
||||
import strconv;
|
||||
|
||||
let wwiwritefailed: i32 = 0;
|
||||
|
||||
fn wwrite(fd: i32, p: *u8, n: u64) void = {
|
||||
if (wwiwritefailed != 0) { return; };
|
||||
match (os.writeall(fd, p, n)) {
|
||||
case let wrote: i64 => {
|
||||
if (wrote < 0 || (wrote: u64) != n) { wwiwritefailed = 1; };
|
||||
};
|
||||
case let e: os.oserror => wwiwritefailed = 1;
|
||||
};
|
||||
};
|
||||
|
||||
fn wputs(fd: i32, s: str) void = {
|
||||
os.write(fd, s.ptr, s.len: u64);
|
||||
wwrite(fd, s.ptr, s.len: u64);
|
||||
};
|
||||
|
||||
fn wputb(fd: i32, b: u8) void = {
|
||||
let buf: [1]u8;
|
||||
buf[0] = b;
|
||||
os.write(fd, buf.ptr, 1u64);
|
||||
wwrite(fd, buf.ptr, 1u64);
|
||||
};
|
||||
|
||||
fn wquote(fd: i32, s: str) void = {
|
||||
@@ -59,7 +71,7 @@ fn wquote(fd: i32, s: str) void = {
|
||||
buf[1] = 120u8;
|
||||
buf[2] = h;
|
||||
buf[3] = l;
|
||||
os.write(fd, buf.ptr, 4u64);
|
||||
wwrite(fd, buf.ptr, 4u64);
|
||||
} else {
|
||||
wputb(fd, c);
|
||||
};};};};};
|
||||
@@ -85,7 +97,8 @@ fn wwimodeq(a: str, b: str) bool = {
|
||||
fn wwiusepath(c: *checker, owner: str, source: i32, alias: str) str = {
|
||||
let u: *syntax.node = c.file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == syntax.nkind.N_USE && u.sourceid == source
|
||||
if (u.kind == syntax.nkind.N_USE && u.useblank == 0
|
||||
&& u.sourceid == source
|
||||
&& syntax.streq(u.str, alias)) {
|
||||
let same: bool = false;
|
||||
if (owner.len == 0) {
|
||||
@@ -301,7 +314,7 @@ fn wwirune(fd: i32, cp: u64) void = {
|
||||
buf[1] = 120u8;
|
||||
buf[2] = h;
|
||||
buf[3] = l;
|
||||
os.write(fd, buf.ptr, 4u64);
|
||||
wwrite(fd, buf.ptr, 4u64);
|
||||
} else {
|
||||
wputb(fd, c);
|
||||
};};};};};
|
||||
@@ -591,6 +604,7 @@ fn wwiprimary(n: *syntax.node) bool = {
|
||||
};
|
||||
|
||||
fn wwiisdecl(d: *syntax.node) bool = {
|
||||
if (d.initfn != 0 || d.initsynthetic != 0) { return false; };
|
||||
return d.kind == syntax.nkind.N_FNDECL || d.kind == syntax.nkind.N_TYPEDECL ||
|
||||
d.kind == syntax.nkind.N_DEF || d.kind == syntax.nkind.N_LET;
|
||||
};
|
||||
@@ -887,7 +901,7 @@ fn wwisortfacts(fs: *wwifactset) void = {
|
||||
};
|
||||
|
||||
fn wwiowneduse(u: *syntax.node, owner: str, source: i32) bool = {
|
||||
return u.kind == syntax.nkind.N_USE && u.imported != 0
|
||||
return u.kind == syntax.nkind.N_USE && u.useblank == 0 && u.imported != 0
|
||||
&& u.sourceid == source && wwimodeq(u.nmod, owner);
|
||||
};
|
||||
|
||||
@@ -900,7 +914,8 @@ fn wwiemitimports(fd: i32, file: *syntax.node, owner: str, source: i32,
|
||||
if (imported) {
|
||||
owned = wwiowneduse(u, owner, source);
|
||||
} else {
|
||||
owned = u.kind == syntax.nkind.N_USE && u.imported == 0
|
||||
owned = u.kind == syntax.nkind.N_USE && u.useblank == 0
|
||||
&& u.imported == 0
|
||||
&& u.sourceid == source;
|
||||
};
|
||||
if (owned) { nuse += 1; };
|
||||
@@ -916,7 +931,8 @@ fn wwiemitimports(fd: i32, file: *syntax.node, owner: str, source: i32,
|
||||
if (imported) {
|
||||
owned = wwiowneduse(u, owner, source);
|
||||
} else {
|
||||
owned = u.kind == syntax.nkind.N_USE && u.imported == 0
|
||||
owned = u.kind == syntax.nkind.N_USE && u.useblank == 0
|
||||
&& u.imported == 0
|
||||
&& u.sourceid == source;
|
||||
};
|
||||
if (owned) {
|
||||
@@ -944,7 +960,7 @@ fn wwiprimarysectionhas(c: *checker, file: *syntax.node,
|
||||
source: i32) bool = {
|
||||
let u: *syntax.node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == syntax.nkind.N_USE && u.imported == 0
|
||||
if (u.kind == syntax.nkind.N_USE && u.useblank == 0 && u.imported == 0
|
||||
&& u.sourceid == source) { return true; };
|
||||
u = u.next;
|
||||
};
|
||||
@@ -1025,7 +1041,8 @@ fn wwiemitprimarysection(c: *checker, fd: i32, file: *syntax.node,
|
||||
};
|
||||
};
|
||||
|
||||
fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
|
||||
fn wwiemitfd(c: *checker, file: *syntax.node, fd: i32) i32 = {
|
||||
wwiwritefailed = 0;
|
||||
// §5: check_exported_type FIRST, before any byte — a producer
|
||||
// without it can emit a dangling `.wwi`.
|
||||
let bad: i32 = 0;
|
||||
@@ -1101,15 +1118,6 @@ fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
|
||||
wwisortdecls(dkeys, dnodes, ndecl);
|
||||
};
|
||||
|
||||
let fd: i32 = os.open(path,
|
||||
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (fd < 0) {
|
||||
wputs(2, "w6c: cannot open ");
|
||||
wputs(2, path);
|
||||
wputs(2, "\n");
|
||||
return 1i32;
|
||||
};
|
||||
|
||||
// Canonical ownership, declared name, and lexical source scope are
|
||||
// independent export facts. Repeated owner sections preserve the file
|
||||
// that owns each binding while every symbol/action remains keyed by owner.
|
||||
@@ -1154,6 +1162,5 @@ fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
|
||||
fi += 1;
|
||||
};
|
||||
|
||||
os.close(fd);
|
||||
return 0i32;
|
||||
return wwiwritefailed;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -148,7 +148,8 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
// silently). Mirrors w6c main.ww:162 / cmd/w6c/main.c.
|
||||
if (l.errs > 0 || ps.errs > 0) { return 1; };
|
||||
let empty: str;
|
||||
if (wcc.compilefile(f, 0, 0, empty, empty, 0, empty, 0) != 0) { return 1; };
|
||||
if (wcc.compilefile(f, 0, 0, empty, empty, 0, -1, 0, 0,
|
||||
empty, empty) != 0) { return 1; };
|
||||
};};};};
|
||||
|
||||
if (l.errs > 0) { return 1; };
|
||||
|
||||
Reference in New Issue
Block a user