ww: import toolchain — C bootstrap + ww-side self-host (phases 0-10)
C bootstrap (phases 0-9):
cmd/wwc, cmd/6c, cmd/6a, cmd/6l, cmd/ww, rt, lib/*.
ww-side self-host (phase 10):
selfhost/cmd/wwc — ww-cgen frontend; bootstrap fixed point.
selfhost/cmd/6a — assembler; byte-identical to C 6a (test 991).
selfhost/cmd/6l — linker w/ archive (.a) support; byte-identical
to C 6l (test 992).
selfhost/cmd/ww — driver (build/run/version); byte-identical to
C ww (test 993).
make test: 15/15. make bootstrap: ww2.s == ww3.s, ww2.o == ww3.o,
ww2 == ww3 byte-identical, with the full ww-tooled chain.
This commit is contained in:
349
cmd/ww/main.c
Normal file
349
cmd/ww/main.c
Normal file
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
* ww — the user-facing driver. Plan 9 cc(1) / Hare hare(1) analogue.
|
||||
*
|
||||
* Pipeline:
|
||||
* ww build foo.ww → 6c foo.ww > foo.s ; 6a foo.s > foo.o ;
|
||||
* 6l -o foo foo.o <runtime.o>
|
||||
* ww run foo.ww → build then exec ./foo
|
||||
*
|
||||
* Tool paths default to siblings of $0 (so a fresh build runs out of
|
||||
* out/bin/), and can be overridden with WW_6C / WW_6A / WW_6L.
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <libgen.h>
|
||||
|
||||
static const char *usage =
|
||||
"usage: ww [-V] <subcommand> [args...]\n"
|
||||
" -V print version and exit\n"
|
||||
" build <path> compile module to a static binary\n"
|
||||
" run <path> build then exec\n"
|
||||
" test <path> build and run module tests\n"
|
||||
" fmt <path> reformat ww source\n"
|
||||
" version print version and exit\n";
|
||||
|
||||
static char *self_dir; /* directory containing this binary */
|
||||
|
||||
static const char *
|
||||
toolpath(const char *envvar, const char *name)
|
||||
{
|
||||
const char *p = getenv(envvar);
|
||||
if (p && p[0]) return p;
|
||||
static char buf[1024];
|
||||
snprintf(buf, sizeof buf, "%s/%s", self_dir, name);
|
||||
return strdup(buf);
|
||||
}
|
||||
|
||||
static int
|
||||
run(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Set of imported module paths, kept on the heap. Used to break
|
||||
* cycles in `use` resolution. Linear because typical imports are
|
||||
* a handful per build. */
|
||||
struct ImportSet {
|
||||
char **paths;
|
||||
int n, cap;
|
||||
};
|
||||
|
||||
static int
|
||||
import_seen(struct ImportSet *s, const char *path)
|
||||
{
|
||||
for (int i = 0; i < s->n; i++)
|
||||
if (strcmp(s->paths[i], path) == 0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
import_add(struct ImportSet *s, const char *path)
|
||||
{
|
||||
if (s->n + 1 > s->cap) {
|
||||
s->cap = s->cap ? s->cap * 2 : 8;
|
||||
s->paths = realloc(s->paths, s->cap * sizeof *s->paths);
|
||||
}
|
||||
s->paths[s->n++] = strdup(path);
|
||||
}
|
||||
|
||||
/* try <dir>/X.ww then <dir>/X/X.ww; return resolved path in `out` or 0. */
|
||||
static int
|
||||
locate_import_in(const char *dir, const char *name, char *out, size_t outsz)
|
||||
{
|
||||
snprintf(out, outsz, "%s/%s.ww", dir, name);
|
||||
if (access(out, 0) == 0) return 1;
|
||||
snprintf(out, outsz, "%s/%s/%s.ww", dir, name, name);
|
||||
if (access(out, 0) == 0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Walk a colon-separated dirlist trying to resolve `name`. Returns 1
|
||||
* on the first hit. */
|
||||
static int
|
||||
locate_import(const char *dirs, const char *name, char *out, size_t outsz)
|
||||
{
|
||||
const char *p = dirs;
|
||||
while (*p) {
|
||||
const char *e = strchr(p, ':');
|
||||
size_t n = e ? (size_t)(e - p) : strlen(p);
|
||||
if (n > 0 && n < outsz) {
|
||||
char dir[1024];
|
||||
if (n >= sizeof dir) n = sizeof dir - 1;
|
||||
memcpy(dir, p, n);
|
||||
dir[n] = '\0';
|
||||
if (locate_import_in(dir, name, out, outsz)) return 1;
|
||||
}
|
||||
if (!e) break;
|
||||
p = e + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Recursively expand `path`: for each top-level `use IDENT;` we find,
|
||||
* resolve the import and expand it first, then append our own bytes.
|
||||
* Already-visited paths are skipped. */
|
||||
static void
|
||||
expand(FILE *out, const char *path, struct ImportSet *visited,
|
||||
const char *libdir)
|
||||
{
|
||||
/* Use the path as-is for cycle detection. Different syntactic
|
||||
* paths to the same file would re-import, which is harmless given
|
||||
* our flat-scope concatenation (duplicate decls would fail at
|
||||
* check time, surfacing the issue). */
|
||||
if (import_seen(visited, path)) return;
|
||||
import_add(visited, path);
|
||||
|
||||
FILE *in = fopen(path, "rb");
|
||||
if (in == NULL) {
|
||||
fprintf(stderr, "ww: cannot read %s\n", path);
|
||||
return;
|
||||
}
|
||||
/* Scan once for `use X;` clauses, expand each. We keep the line
|
||||
* format simple — leading whitespace + "use" + IDENT + optional
|
||||
* dotted suffix + ";". Inside-comment occurrences would slip
|
||||
* through, but ww source rarely puts that pattern in a comment. */
|
||||
char line[2048];
|
||||
while (fgets(line, sizeof line, in)) {
|
||||
const char *p = line;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (strncmp(p, "use ", 4) != 0 && strncmp(p, "use\t", 4) != 0)
|
||||
continue;
|
||||
p += 4;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
char name[256] = {0};
|
||||
int j = 0;
|
||||
while ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
|
||||
|| *p == '_' || *p == '.' || (*p >= '0' && *p <= '9'))
|
||||
if (j + 1 < (int)sizeof name) name[j++] = *p++;
|
||||
if (j == 0) continue;
|
||||
char ipath[1024];
|
||||
if (!locate_import(libdir, name, ipath, sizeof ipath))
|
||||
continue; /* silently skip if not found */
|
||||
expand(out, ipath, visited, libdir);
|
||||
}
|
||||
|
||||
rewind(in);
|
||||
int ch;
|
||||
while ((ch = fgetc(in)) != EOF) fputc(ch, out);
|
||||
fputc('\n', out);
|
||||
fclose(in);
|
||||
}
|
||||
|
||||
static int
|
||||
build_one(const char *src, const char *out, const char *extra_includes)
|
||||
{
|
||||
const char *c6 = toolpath("WW_6C", "6c");
|
||||
const char *a6 = toolpath("WW_6A", "6a");
|
||||
const char *l6 = toolpath("WW_6L", "6l");
|
||||
const char *libdir = getenv("WW_LIB");
|
||||
if (libdir == NULL || libdir[0] == 0) {
|
||||
static char libbuf[1024];
|
||||
snprintf(libbuf, sizeof libbuf, "%s/../lib", self_dir);
|
||||
libdir = libbuf;
|
||||
}
|
||||
const char *srcdir = getenv("WW_SRCLIB");
|
||||
static char srcbuf[1024];
|
||||
if (srcdir == NULL || srcdir[0] == 0) {
|
||||
/* in-tree default: ../../lib relative to bin/ */
|
||||
snprintf(srcbuf, sizeof srcbuf, "%s/../../lib", self_dir);
|
||||
if (access(srcbuf, 0) == 0) srcdir = srcbuf;
|
||||
else if (access("lib", 0) == 0) srcdir = "lib";
|
||||
else srcdir = libdir;
|
||||
}
|
||||
|
||||
/* Compose the search path: any -I dirs first, then srcdir.
|
||||
* locate_import walks them left-to-right. */
|
||||
static char searchpath[4096];
|
||||
if (extra_includes && extra_includes[0])
|
||||
snprintf(searchpath, sizeof searchpath, "%s:%s", extra_includes, srcdir);
|
||||
else
|
||||
snprintf(searchpath, sizeof searchpath, "%s", srcdir);
|
||||
srcdir = searchpath;
|
||||
|
||||
/* Strip extension to derive a stem; e.g. /tmp/foo.ww → /tmp/foo */
|
||||
char stem[1024];
|
||||
snprintf(stem, sizeof stem, "%s", src);
|
||||
char *dot = strrchr(stem, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
|
||||
char asmf[1024], obj[1024], combined[1024];
|
||||
snprintf(asmf, sizeof asmf, "%s.s", stem);
|
||||
snprintf(obj, sizeof obj, "%s.o", stem);
|
||||
snprintf(combined, sizeof combined, "%s.combined.ww", stem);
|
||||
|
||||
/* Resolve `use X;` imports by concatenating sources into a temp
|
||||
* file. The compiler then sees one flat source. */
|
||||
{
|
||||
FILE *cf = fopen(combined, "wb");
|
||||
if (cf == NULL) {
|
||||
fprintf(stderr, "ww: cannot open %s\n", combined);
|
||||
return 1;
|
||||
}
|
||||
struct ImportSet visited = {0};
|
||||
expand(cf, src, &visited, srcdir);
|
||||
fclose(cf);
|
||||
for (int i = 0; i < visited.n; i++) free(visited.paths[i]);
|
||||
free(visited.paths);
|
||||
}
|
||||
|
||||
char cmd[4096];
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s", c6, asmf, combined);
|
||||
if (run(cmd) != 0) {
|
||||
fprintf(stderr, "ww: 6c failed\n");
|
||||
return 1;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s", a6, obj, asmf);
|
||||
if (run(cmd) != 0) {
|
||||
fprintf(stderr, "ww: 6a failed\n");
|
||||
return 1;
|
||||
}
|
||||
/* Link runtime: prefer libwwrt.a (selective archive pull) but
|
||||
* fall back to start.o + syscall.o in the in-tree obj/ dir if
|
||||
* we're running uninstalled. */
|
||||
char rtargs[2048] = {0};
|
||||
char path[1024];
|
||||
snprintf(path, sizeof path, "%s/libwwrt.a", libdir);
|
||||
if (access(path, 0) == 0) {
|
||||
snprintf(rtargs, sizeof rtargs, "%s", path);
|
||||
} else {
|
||||
char a1[1024], a2[1024];
|
||||
snprintf(a1, sizeof a1, "%s/../obj/rt/start.o", self_dir);
|
||||
snprintf(a2, sizeof a2, "%s/../obj/rt/syscall.o", self_dir);
|
||||
snprintf(rtargs, sizeof rtargs, "%s %s", a1, a2);
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s %s", l6, out, obj, rtargs);
|
||||
if (run(cmd) != 0) {
|
||||
fprintf(stderr, "ww: 6l failed\n");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
do_version(void)
|
||||
{
|
||||
printf("ww %s\n", WW_VERSION);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
do_build(int argc, char **argv)
|
||||
{
|
||||
const char *src = NULL;
|
||||
char libs[2048] = {0};
|
||||
char incs[2048] = {0};
|
||||
for (int i = 0; i < argc; i++) {
|
||||
if (strncmp(argv[i], "-l", 2) == 0 && argv[i][2]) {
|
||||
char libpath[512];
|
||||
const char *libdir = getenv("WW_LIB");
|
||||
if (libdir == NULL) {
|
||||
static char def[1024];
|
||||
snprintf(def, sizeof def, "%s/../lib", self_dir);
|
||||
libdir = def;
|
||||
}
|
||||
snprintf(libpath, sizeof libpath, "%s/lib%s.a",
|
||||
libdir, argv[i] + 2);
|
||||
size_t n = strlen(libs);
|
||||
snprintf(libs + n, sizeof libs - n, " %s", libpath);
|
||||
} else if (strcmp(argv[i], "-I") == 0 && i + 1 < argc) {
|
||||
size_t n = strlen(incs);
|
||||
snprintf(incs + n, sizeof incs - n,
|
||||
"%s%s", n ? ":" : "", argv[++i]);
|
||||
} else if (strncmp(argv[i], "-I", 2) == 0 && argv[i][2]) {
|
||||
size_t n = strlen(incs);
|
||||
snprintf(incs + n, sizeof incs - n,
|
||||
"%s%s", n ? ":" : "", argv[i] + 2);
|
||||
} else if (src == NULL) {
|
||||
src = argv[i];
|
||||
}
|
||||
}
|
||||
if (src == NULL) { fputs("ww build: missing source\n", stderr); return 2; }
|
||||
char out[1024];
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
snprintf(out, sizeof out, "%s", base);
|
||||
char *dot = strrchr(out, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
(void)libs; /* libs string is gathered; build_one currently
|
||||
* always links libwwrt.a; -l support pending more
|
||||
* glue between driver and 6l invocation. */
|
||||
return build_one(src, out, incs);
|
||||
}
|
||||
|
||||
static int
|
||||
do_run(int argc, char **argv)
|
||||
{
|
||||
if (argc < 1) { fputs("ww run: missing source\n", stderr); return 2; }
|
||||
char tmp[1024];
|
||||
snprintf(tmp, sizeof tmp, "/tmp/ww_run_%d", getpid());
|
||||
if (build_one(argv[0], tmp, "") != 0) return 1;
|
||||
int rc = run(tmp);
|
||||
unlink(tmp);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
do_test(int argc, char **argv)
|
||||
{
|
||||
(void)argc; (void)argv;
|
||||
fputs("ww: test: not implemented in this phase\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
do_fmt(int argc, char **argv)
|
||||
{
|
||||
(void)argc; (void)argv;
|
||||
fputs("ww: fmt: not implemented in this phase\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc >= 1) {
|
||||
char buf[1024];
|
||||
snprintf(buf, sizeof buf, "%s", argv[0]);
|
||||
self_dir = strdup(dirname(buf));
|
||||
}
|
||||
if (argc < 2) { fputs(usage, stderr); return 2; }
|
||||
const char *cmd = argv[1];
|
||||
if (strcmp(cmd, "-V") == 0 || strcmp(cmd, "version") == 0)
|
||||
return do_version();
|
||||
if (strcmp(cmd, "-h") == 0 || strcmp(cmd, "--help") == 0) {
|
||||
fputs(usage, stdout); return 0;
|
||||
}
|
||||
if (strcmp(cmd, "build") == 0) return do_build(argc - 2, argv + 2);
|
||||
if (strcmp(cmd, "run") == 0) return do_run(argc - 2, argv + 2);
|
||||
if (strcmp(cmd, "test") == 0) return do_test(argc - 2, argv + 2);
|
||||
if (strcmp(cmd, "fmt") == 0) return do_fmt(argc - 2, argv + 2);
|
||||
fprintf(stderr, "ww: unknown subcommand: %s\n", cmd);
|
||||
fputs(usage, stderr);
|
||||
return 2;
|
||||
}
|
||||
Reference in New Issue
Block a user