Files
ww/selfhost/cmd/w6c/main.ww

589 lines
16 KiB
Plaintext

// Port of cmd/w6c/main.c.
package main;
import os;
import strings;
import syntax;
import wcc;
fn cstreq(a: *u8, lit: str) bool = {
let n: u64 = lit.len: u64;
let i: u64 = 0u64;
for (i < n) {
let li: i32 = i: i32;
if (a[i] != lit[li]) { return false; };
i += 1u64;
};
if (a[i] != 0u8) { return false; };
return true;
};
fn cstrlen(p: *u8) u64 = {
let n: u64 = 0u64;
for (p[n] != 0u8) { n += 1u64; };
return n;
};
// pathstr — view a NUL-terminated *u8 as a str. lib/os entrypoints
// take str post-task-#23; this bridges call sites that still hold
// C-string paths (argv entries, arena-allocated buffers).
fn pathstr(p: *u8) str = {
let r: str;
r.ptr = p;
r.len = cstrlen(p): i32;
return r;
};
fn slurp(path: *u8) (*u8, u64) = {
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil, 0u64; };
let szr: (i64 | os.oserror) = os.filesize(fd);
let n: i64 = 0i64;
match (szr) {
case let v: i64 => n = v;
case let e: os.oserror => { os.close(fd); return nil, 0u64; };
};
let nz: u64 = n: u64;
let buf: []u8 = alloc([], nz + 1u64)!;
buf.len = (nz + 1u64): i32;
let rr: (i64 | os.oserror) = os.readall(fd, buf.ptr, nz);
os.close(fd);
let got: i64 = 0i64;
match (rr) {
case let v: i64 => got = v;
case let e: os.oserror => return nil, 0u64;
};
if (got != n) { return nil, 0u64; };
buf[nz] = 0u8;
return buf.ptr, nz;
};
fn exportownermatches(buf: *u8, n: u64, path: *u8) bool = {
let prefix: str = "//ww:module ";
let pn: u64 = cstrlen(path);
let poff: u64 = prefix.len: u64;
let need: u64 = poff + pn + 1u64;
if (n < need) { return false; };
let i: u64 = 0u64;
for (i < poff) {
if (buf[i] != prefix.ptr[i]) { return false; };
i += 1u64;
};
i = 0u64;
for (i < pn) {
if (buf[poff + i] != path[i]) { return false; };
i += 1u64;
};
return buf[need - 1u64] == 10u8;
};
type importmap = struct {
source: *u8,
path: *u8,
seen: bool,
};
fn allocimportmaps(count: i32) ([]importmap | nomem) = {
let value: []importmap = alloc([], count: u64)?;
return value;
};
fn allocimportptrs(count: i32) ([]*u8 | nomem) = {
let value: []*u8 = alloc([], count: u64)?;
return value;
};
fn allocnodeptrs(count: i32) ([]*syntax.node | nomem) = {
let value: []*syntax.node = alloc([], count: u64)?;
return value;
};
// Export data carries canonical owner and declared name independently. Each
// direct interface's package-clause markers also describe its reachable fact
// closure, so search all parsed interfaces for a canonical owner.
fn importpkgname(asts: []*syntax.node, nasts: i32, path: str,
primary: *syntax.node, conflict: *bool) str = {
let name: str;
let i: i32 = 0;
for (i < nasts) {
let f: *syntax.node = asts[i];
let p: *syntax.node = nil;
if (f != nil) { p = f.body; };
for (p != nil) {
if (p.nmod.len > 0 && p.pkgname.len > 0
&& syntax.streq(p.nmod, path)) {
if (name.len > 0 && !syntax.streq(name, p.pkgname)) {
*conflict = true;
let empty: str;
return empty;
};
name = p.pkgname;
};
p = p.next;
};
i += 1;
};
let p: *syntax.node = nil;
if (primary != nil) { p = primary.body; };
for (p != nil) {
if (p.nmod.len > 0 && p.pkgname.len > 0
&& syntax.streq(p.nmod, path)) {
if (name.len > 0 && !syntax.streq(name, p.pkgname)) {
*conflict = true;
let empty: str;
return empty;
};
name = p.pkgname;
};
p = p.next;
};
return name;
};
fn bindimportnames(list: *syntax.node, asts: []*syntax.node, nasts: i32,
primary: *syntax.node, testsupport: *u8) bool = {
let u: *syntax.node = list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && u.usepath.len > 0) {
let reserved: bool = testsupport != nil
&& cstreq(testsupport, "__wwtest")
&& syntax.streq(u.usepath, "__wwtest");
if (!reserved) {
let conflict: bool = false;
let name: str = importpkgname(asts, nasts, u.usepath,
primary, &conflict);
if (conflict) {
let pre: str = "w6c: package ";
let post: str = " has conflicting declared names in export data\n";
os.write(2, pre.ptr, pre.len: u64);
os.write(2, u.usepath.ptr, u.usepath.len: u64);
os.write(2, post.ptr, post.len: u64);
return false;
};
if (name.len > 0) {
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";
os.write(2, pre.ptr, pre.len: u64);
os.write(2, u.usepath.ptr, u.usepath.len: u64);
os.write(2, post.ptr, post.len: u64);
return false;
} else {
// A closure-only import may have no declarations in this
// interface. Its canonical path must never become a leaf
// qualifier by fallback.
u.str = u.usepath;
}; };
};
};
u = u.next;
};
return true;
};
export fn main(argc: i32, argv: **u8) i32 = {
let src: *u8 = nil;
let out: *u8 = nil;
let wwiout: *u8 = nil; // -I <out.wwi>: M2 export-data producer
let testsupport: *u8 = nil;
let testtarget: *u8 = nil;
let testmode: i32 = 0i32; // #15: `-T` test-mode
let testpackage: i32 = 0i32;
let commandpackage: i32 = 0i32;
let entrymode: i32 = 0i32;
let sepmode: i32 = 0i32; // -c: #22 M3 separate-compile / primary-
// only codegen (emit imported==0 decls
// only; treat `.wwi` deps as external)
let pathallocation: ([]*u8 | nomem) = allocimportptrs(argc);
let fileallocation: ([]*u8 | nomem) = allocimportptrs(argc);
let importpaths: []*u8;
let importfiles: []*u8;
let astallocation: ([]*syntax.node | nomem) = allocnodeptrs(argc);
let importasts: []*syntax.node;
match (pathallocation) {
case let value: []*u8 => importpaths = value;
case nomem => {
let m: str = "w6c: out of memory\n";
os.write(2, m.ptr, m.len: u64);
return 1;
};
};
match (fileallocation) {
case let value: []*u8 => importfiles = value;
case nomem => {
let m: str = "w6c: out of memory\n";
os.write(2, m.ptr, m.len: u64);
return 1;
};
};
importpaths.len = argc;
importfiles.len = argc;
match (astallocation) {
case let value: []*syntax.node => importasts = value;
case nomem => {
let m: str = "w6c: out of memory\n";
os.write(2, m.ptr, m.len: u64);
return 1;
};
};
importasts.len = argc;
let nimports: i32 = 0;
let mapallocation: ([]importmap | nomem) = allocimportmaps(argc);
let importmaps: []importmap;
match (mapallocation) {
case let value: []importmap => importmaps = value;
case nomem => {
let m: str = "w6c: out of memory\n";
os.write(2, m.ptr, m.len: u64);
return 1;
};
};
importmaps.len = argc;
let nmaps: i32 = 0;
let i: i32 = 1;
for (i < argc) {
let a: *u8 = argv[i];
if (cstreq(a, "-o")) {
i += 1;
if (i >= argc) {
let m: str = "w6c: -o requires arg\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
out = argv[i];
} else { if (cstreq(a, "-I")) {
i += 1;
if (i >= argc) {
let m: str = "w6c: -I requires arg\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
wwiout = argv[i];
} else { if (cstreq(a, "-T")) {
testmode = 1i32;
} else { if (cstreq(a, "--test-package")) {
testpackage = 1i32;
} else { if (cstreq(a, "--command-package")) {
commandpackage = 1i32;
} else { if (cstreq(a, "--entry")) {
entrymode = 1i32;
} else { if (cstreq(a, "--test-support-module")) {
i += 1;
if (i >= argc) {
let m: str = "w6c: --test-support-module requires arg\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
testsupport = argv[i];
} else { if (cstreq(a, "--test-target-package")) {
i += 1;
if (i >= argc) {
let m: str = "w6c: --test-target-package requires arg\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
testtarget = argv[i];
} else { if (cstreq(a, "-c")) {
sepmode = 1i32;
} else { if (cstreq(a, "--import")) {
if (i + 2 >= argc) {
let m: str = "w6c: --import requires path and file\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
importpaths[nimports] = argv[i + 1];
importfiles[nimports] = argv[i + 2];
nimports += 1;
i += 2;
} else { if (cstreq(a, "--import-map")) {
if (i + 2 >= argc) {
let m: str = "w6c: --import-map requires source and path\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
importmaps[nmaps].source = argv[i + 1];
importmaps[nmaps].path = argv[i + 2];
importmaps[nmaps].seen = false;
nmaps += 1;
i += 2;
} else { if (a[0u64] == 45u8) {
let m: str = "w6c: unknown flag\n";
os.write(2, m.ptr, m.len: u64);
return 2;
} else {
if (src != nil) {
let m: str = "w6c: only one input\n";
os.write(2, m.ptr, m.len: u64);
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";
os.write(2, m.ptr, m.len: u64);
return 2;
};
if (nimports > 0 && sepmode == 0) {
let m: str = "w6c: --import requires -c\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
if (nmaps > 0 && sepmode == 0) {
let m: str = "w6c: --import-map requires -c\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
if ((entrymode != 0 || testpackage != 0 || commandpackage != 0)
&& sepmode == 0) {
let m: str = "w6c: --entry, --test-package, and --command-package require -c\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);
return 2;
};
let importi: i32 = 0;
for (importi < nimports) {
if (importpaths[importi][0u64] == 0u8) {
let m: str = "w6c: --import path is empty\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
if (importi > 0 && strings.compare(pathstr(importpaths[importi - 1]),
pathstr(importpaths[importi])) >= 0) {
let m: str = "w6c: --import paths must be sorted and unique\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
importi += 1;
};
let mapi: i32 = 0;
for (mapi < nmaps) {
if (importmaps[mapi].source[0u64] == 0u8
|| importmaps[mapi].path[0u64] == 0u8) {
let m: str = "w6c: --import-map path is empty\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
if (mapi > 0 && strings.compare(pathstr(importmaps[mapi - 1].source),
pathstr(importmaps[mapi].source)) >= 0) {
let m: str = "w6c: --import-map sources must be sorted and unique\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
let direct: bool = false;
let directi: i32 = 0;
for (directi < nimports) {
if (cstreq(importmaps[mapi].path,
pathstr(importpaths[directi]))) {
direct = true;
break;
};
directi += 1;
};
if (!direct) {
let m: str = "w6c: --import-map target is not a direct import\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
mapi += 1;
};
if (testsupport != nil && (sepmode == 0
|| (!cstreq(testsupport, "test")
&& !cstreq(testsupport, "__wwtest")))) {
let m: str = "w6c: invalid --test-support-module\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
if (testtarget != nil && (sepmode == 0 || testmode == 0
|| testtarget[0u64] == 0u8)) {
let m: str = "w6c: invalid --test-target-package\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
if (testtarget != nil) {
let direct: bool = false;
importi = 0;
for (importi < nimports) {
if (cstreq(importpaths[importi], pathstr(testtarget))) {
direct = true;
};
importi += 1;
};
if (!direct) {
let m: str = "w6c: --test-target-package is not a direct import\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
};
let importhead: *node = nil;
let importtail: *node = nil;
importi = 0;
for (importi < nimports) {
let ibuf: *u8;
let ilen: u64;
ibuf, ilen = slurp(importfiles[importi]);
if (ibuf == nil) {
let pre: str = "w6c: import ";
let mid: str = ": cannot read ";
let nl: str = "\n";
let ipath: str = pathstr(importpaths[importi]);
let ifile: str = pathstr(importfiles[importi]);
os.write(2, pre.ptr, pre.len: u64);
os.write(2, ipath.ptr, ipath.len: u64);
os.write(2, mid.ptr, mid.len: u64);
os.write(2, ifile.ptr, ifile.len: u64);
os.write(2, nl.ptr, nl.len: u64);
return 1;
};
if (!exportownermatches(ibuf, ilen, importpaths[importi])) {
let pre: str = "w6c: import ";
let mid: str = ": export owner mismatch in ";
let nl: str = "\n";
os.write(2, pre.ptr, pre.len: u64);
os.write(2, importpaths[importi], cstrlen(importpaths[importi]));
os.write(2, mid.ptr, mid.len: u64);
os.write(2, importfiles[importi], cstrlen(importfiles[importi]));
os.write(2, nl.ptr, nl.len: u64);
return 1;
};
let il: lex;
lexinit(&il, strings.dup(pathstr(importfiles[importi])), ibuf, ilen);
let ips: parser;
parserinit(&ips, &il);
let imod: str = pathstr(importpaths[importi]);
ips.pathmod = imod;
ips.curmod = imod;
if (testsupport != nil) { ips.testmodule = pathstr(testsupport); };
let imported: *node = parsefile(&ips);
if (il.errs > 0 || ips.errs > 0) { return 1; };
importasts[importi] = imported;
if (!bindimportnames(imported.list, importasts, importi + 1,
nil, testsupport)) { return 1; };
let d: *node = imported.list;
if (d != nil) {
if (importhead == nil) { importhead = d; }
else { importtail.next = d; };
for (d.next != nil) { d = d.next; };
importtail = d;
};
importi += 1;
};
let buf: *u8;
let blen: u64;
buf, blen = slurp(src);
if (buf == nil) {
let m: str = "w6c: cannot read input\n";
os.write(2, m.ptr, m.len: u64);
return 1;
};
let l: lex;
lexinit(&l, strings.dup(pathstr(src)), buf, blen);
let ps: parser;
parserinit(&ps, &l);
if (testsupport != nil) { ps.testmodule = pathstr(testsupport); };
// Entry compilation classifies an ordinary command package. The explicit
// marker carries only that parser fact for non-entry command test variants.
ps.commandpackage = commandpackage != 0 || entrymode != 0;
let f: *node = parsefile(&ps);
// Gate cgen on parse-stage errors. Mirrors cmd/w6c/main.c's
// `if (l.errs || p.errs) return 1;` — broken AST otherwise reaches
// cgen and emits junk asm with a zero exit (silent miscompile).
if (l.errs > 0 || ps.errs > 0) { return 1; };
let use: *node = f.list;
for (use != nil) {
if (use.kind == syntax.nkind.N_USE && use.usepath.len != 0) {
mapi = 0;
for (mapi < nmaps) {
if (syntax.streq(use.usepath,
pathstr(importmaps[mapi].source))) {
use.usepath = pathstr(importmaps[mapi].path);
importmaps[mapi].seen = true;
break;
};
mapi += 1;
};
};
use = use.next;
};
mapi = 0;
for (mapi < nmaps) {
if (!importmaps[mapi].seen) {
let m: str = "w6c: --import-map source is not in primary input\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
mapi += 1;
};
// A later direct interface may supply metadata for an origin section used
// by an earlier interface, so bind once more against the complete set.
importi = 0;
for (importi < nimports) {
if (!bindimportnames(importasts[importi].list, importasts, nimports,
nil, testsupport)) { return 1; };
importi += 1;
};
if (!bindimportnames(f.list, importasts, nimports, f, testsupport)) {
return 1;
};
if (testtarget != nil) {
let seen: i32 = 0;
let targetuse: *syntax.node = f.list;
for (targetuse != nil) {
if (targetuse.kind == syntax.nkind.N_USE
&& targetuse.imported == 0
&& syntax.streq(targetuse.usepath, pathstr(testtarget))) {
targetuse.str = targetuse.usepath;
seen += 1;
};
targetuse = targetuse.next;
};
if (seen != 1) {
let m: str = "w6c: generated test target import is not unique\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
};
if (importhead != nil) {
importtail.next = f.list;
f.list = importhead;
};
// cgen writes directly to fd 1; dup2 keeps that implementation private.
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);
return 1;
};
if (os.dup2(ofd, 1i32) < 0) {
let m: str = "w6c: dup2 failed\n";
os.write(2, m.ptr, m.len: u64);
os.close(ofd);
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);
};