toolchain: banner purge + WHY-only comment sweep (rule 8)
selfhost/, cmd/, internal/ join the tree-wide sweep: every section banner dies (91 selfhost + the cmd C-style dividers -> 0); narration and stale contracts deleted (pre-#22 bundler notes, retired single-PT_LOAD and no-archive claims, superseded ABI tables); every ref/harec/qbe cite, task cite, encoding/ELF contract, and rule-10 twin pointer kept; lost lifetime/rationale lines restored where the sweep over-cut (elf_globals ownership, kwtab linear-scan). Comment- only proven: all five wwstage tool binaries byte-identical across the sweep; test-commit, test-byteid (161+1399, 0 pinned-divergent), and test-bootstrap (fixed point + 991-995 byte-id) all exit 0. The read-through banked 66 latent-bug leads (checkpoint).
This commit is contained in:
13
cmd/w6a/a.h
13
cmd/w6a/a.h
@@ -1,10 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* a.h — w6a-private header. Modelled on Plan 9 cmd/6a/a.h, trimmed
|
* Modelled on Plan 9 cmd/6a/a.h, trimmed to the instruction subset
|
||||||
* to the instruction subset that w6c emits.
|
* that w6c emits. Line-oriented, no preprocessor: each non-blank,
|
||||||
*
|
* non-label line is one instruction.
|
||||||
* w6a is line-oriented and has no preprocessor: each non-blank, non-
|
|
||||||
* label line is one instruction. We read the whole file into a list
|
|
||||||
* of `Aprog`s, then encode and emit ELF64.
|
|
||||||
*/
|
*/
|
||||||
#ifndef SIX_A_H
|
#ifndef SIX_A_H
|
||||||
#define SIX_A_H
|
#define SIX_A_H
|
||||||
@@ -69,17 +66,14 @@ struct Areloc {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Asm {
|
struct Asm {
|
||||||
/* parser state */
|
|
||||||
const char *file;
|
const char *file;
|
||||||
const char *src;
|
const char *src;
|
||||||
u64 srclen;
|
u64 srclen;
|
||||||
u64 pos;
|
u64 pos;
|
||||||
int line;
|
int line;
|
||||||
|
|
||||||
/* program list */
|
|
||||||
Aprog *head, *tail;
|
Aprog *head, *tail;
|
||||||
|
|
||||||
/* output text section */
|
|
||||||
u8 *text;
|
u8 *text;
|
||||||
u64 textcap, textlen;
|
u64 textcap, textlen;
|
||||||
|
|
||||||
@@ -89,7 +83,6 @@ struct Asm {
|
|||||||
u8 *data;
|
u8 *data;
|
||||||
u64 datacap, datalen;
|
u64 datacap, datalen;
|
||||||
|
|
||||||
/* symbols */
|
|
||||||
Asym *syms;
|
Asym *syms;
|
||||||
Areloc *relocs;
|
Areloc *relocs;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* asm.c — encode the parsed Aprog list into amd64 machine bytes,
|
|
||||||
* appending to Asm.text. Relocations for CALL/branch targets that
|
|
||||||
* resolve to externals are queued in Asm.relocs.
|
|
||||||
*
|
|
||||||
* Encoding subset: the instructions cgen emits today. Operand shapes
|
* Encoding subset: the instructions cgen emits today. Operand shapes
|
||||||
* we accept:
|
* we accept:
|
||||||
* MOVQ $imm, reg — C7 /0 imm32 (REX.W) [imm fits in i32]
|
* MOVQ $imm, reg — C7 /0 imm32 (REX.W) [imm fits in i32]
|
||||||
@@ -95,8 +91,6 @@ a_addreloc_data(Asm *a, u64 off, int kind, Asym *s, i64 add)
|
|||||||
a->relocs = r;
|
a->relocs = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------ register codes ------------------------------------------- */
|
|
||||||
|
|
||||||
/* low 3 bits of register encoding */
|
/* low 3 bits of register encoding */
|
||||||
static int
|
static int
|
||||||
rcode(int r)
|
rcode(int r)
|
||||||
@@ -137,14 +131,12 @@ is_xmm(int r)
|
|||||||
return r >= D_X0 && r <= D_X15;
|
return r >= D_X0 && r <= D_X15;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ModR/M byte */
|
|
||||||
static u8
|
static u8
|
||||||
modrm(int mod, int reg, int rm)
|
modrm(int mod, int reg, int rm)
|
||||||
{
|
{
|
||||||
return (u8)(((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7));
|
return (u8)(((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* emit REX with W=1 plus optional R/B for high regs */
|
|
||||||
static void
|
static void
|
||||||
emit_rex(Asm *a, int regbit, int rmbit, int w)
|
emit_rex(Asm *a, int regbit, int rmbit, int w)
|
||||||
{
|
{
|
||||||
@@ -155,9 +147,7 @@ emit_rex(Asm *a, int regbit, int rmbit, int w)
|
|||||||
if (b != 0x40 || w) a_emit_byte(a, b);
|
if (b != 0x40 || w) a_emit_byte(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* encode mod/disp for [base+disp]; returns 0 on ok.
|
/* Special-cases SP (needs SIB) and BP (forces disp). */
|
||||||
* Special-cases SP (needs SIB) and BP (forces disp).
|
|
||||||
*/
|
|
||||||
static void
|
static void
|
||||||
emit_modrm_mem(Asm *a, int reg_field, int base, i64 disp)
|
emit_modrm_mem(Asm *a, int reg_field, int base, i64 disp)
|
||||||
{
|
{
|
||||||
@@ -191,7 +181,6 @@ encode_rr(Asm *a, u8 opcode, int src, int dst)
|
|||||||
a_emit_byte(a, modrm(3, rcode(src), rcode(dst)));
|
a_emit_byte(a, modrm(3, rcode(src), rcode(dst)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* MOVQ src reg → mem(base, disp). opcode = 0x89 */
|
|
||||||
static void
|
static void
|
||||||
encode_rm(Asm *a, u8 opcode, int src_reg, int base, i64 disp)
|
encode_rm(Asm *a, u8 opcode, int src_reg, int base, i64 disp)
|
||||||
{
|
{
|
||||||
@@ -200,7 +189,6 @@ encode_rm(Asm *a, u8 opcode, int src_reg, int base, i64 disp)
|
|||||||
emit_modrm_mem(a, rcode(src_reg), base, disp);
|
emit_modrm_mem(a, rcode(src_reg), base, disp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* MOVQ mem(base, disp) → reg. opcode = 0x8B */
|
|
||||||
static void
|
static void
|
||||||
encode_mr(Asm *a, u8 opcode, int dst_reg, int base, i64 disp)
|
encode_mr(Asm *a, u8 opcode, int dst_reg, int base, i64 disp)
|
||||||
{
|
{
|
||||||
@@ -250,7 +238,7 @@ sse_mr_load(Asm *a, u8 prefix, u8 op2, int reg_op, int base, i64 disp)
|
|||||||
emit_modrm_mem(a, rcode(reg_op), base, disp);
|
emit_modrm_mem(a, rcode(reg_op), base, disp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* like sse_mr_load but encoded with REX.W (used by CVTTSD2SI / CVTSI2SD
|
/* like sse_rr but encoded with REX.W (used by CVTTSD2SI / CVTSI2SD
|
||||||
* which target/source 64-bit integer regs) */
|
* which target/source 64-bit integer regs) */
|
||||||
static void
|
static void
|
||||||
sse_rr_w(Asm *a, u8 prefix, u8 op2, int reg_op, int rm_op)
|
sse_rr_w(Asm *a, u8 prefix, u8 op2, int reg_op, int rm_op)
|
||||||
@@ -262,8 +250,6 @@ sse_rr_w(Asm *a, u8 prefix, u8 op2, int reg_op, int rm_op)
|
|||||||
a_emit_byte(a, modrm(3, rcode(reg_op), rcode(rm_op)));
|
a_emit_byte(a, modrm(3, rcode(reg_op), rcode(rm_op)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------ second-pass helper: resolve labels to addresses ---------- */
|
|
||||||
|
|
||||||
static u64
|
static u64
|
||||||
resolve_label(Asm *a, const char *name)
|
resolve_label(Asm *a, const char *name)
|
||||||
{
|
{
|
||||||
@@ -281,8 +267,6 @@ label_defined(Asm *a, const char *name)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------ first pass: encode ---------------------------------------- */
|
|
||||||
|
|
||||||
/* For local labels, we record a "fixup" — an offset in .text that
|
/* For local labels, we record a "fixup" — an offset in .text that
|
||||||
* needs to be patched once the label is resolved at end of pass. */
|
* needs to be patched once the label is resolved at end of pass. */
|
||||||
typedef struct Fixup Fixup;
|
typedef struct Fixup Fixup;
|
||||||
@@ -307,10 +291,9 @@ int
|
|||||||
a_encode(Asm *a)
|
a_encode(Asm *a)
|
||||||
{
|
{
|
||||||
fixups = NULL;
|
fixups = NULL;
|
||||||
const char *cur_text = NULL; /* current TEXT name */
|
const char *cur_text = NULL;
|
||||||
(void)cur_text;
|
(void)cur_text;
|
||||||
for (Aprog *p = a->head; p; p = p->link) {
|
for (Aprog *p = a->head; p; p = p->link) {
|
||||||
/* Define any pending label at the current PC */
|
|
||||||
if (p->label) {
|
if (p->label) {
|
||||||
Asym *s = a_intern(a, p->label);
|
Asym *s = a_intern(a, p->label);
|
||||||
s->defined = 1;
|
s->defined = 1;
|
||||||
@@ -800,7 +783,6 @@ a_encode(Asm *a)
|
|||||||
/* R_X86_64_PLT32 (4); addend -4 */
|
/* R_X86_64_PLT32 (4); addend -4 */
|
||||||
a_addreloc(a, reloff, 4, s, -4);
|
a_addreloc(a, reloff, 4, s, -4);
|
||||||
} else if (p->to.type == D_BRANCH) {
|
} else if (p->to.type == D_BRANCH) {
|
||||||
/* local call to a label */
|
|
||||||
a_emit_byte(a, 0xE8);
|
a_emit_byte(a, 0xE8);
|
||||||
add_fixup(a->textlen, p->to.sym);
|
add_fixup(a->textlen, p->to.sym);
|
||||||
a_emit_u32(a, 0);
|
a_emit_u32(a, 0);
|
||||||
@@ -846,7 +828,6 @@ a_encode(Asm *a)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* second pass: patch fixups */
|
|
||||||
for (Fixup *f = fixups; f; f = f->next) {
|
for (Fixup *f = fixups; f; f = f->next) {
|
||||||
if (!label_defined(a, f->label)) {
|
if (!label_defined(a, f->label)) {
|
||||||
fprintf(stderr, "w6a: undefined label '%s'\n", f->label);
|
fprintf(stderr, "w6a: undefined label '%s'\n", f->label);
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
/*
|
|
||||||
* lex.c — character-level helpers for w6a's line-oriented parser.
|
|
||||||
* The parser itself lives in parse.c; here we keep the tokenisers
|
|
||||||
* for identifiers and numbers so parse.c stays focused on syntax.
|
|
||||||
*/
|
|
||||||
#include "a.h"
|
#include "a.h"
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
/*
|
|
||||||
* w6a — amd64 assembler driver. Read .s, parse, encode, emit ELF .o.
|
|
||||||
*/
|
|
||||||
#include "a.h"
|
#include "a.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* obj.c — emit a tiny ELF64 relocatable object.
|
|
||||||
*
|
|
||||||
* Layout (in file order):
|
* Layout (in file order):
|
||||||
* [0] ELF header
|
* [0] ELF header
|
||||||
* [1] Section .text (program bytes)
|
* [1] Section .text (program bytes)
|
||||||
@@ -24,7 +22,6 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
/* ELF constants */
|
|
||||||
#define ELFMAG "\x7f""ELF"
|
#define ELFMAG "\x7f""ELF"
|
||||||
#define ELFCLASS64 2
|
#define ELFCLASS64 2
|
||||||
#define ELFDATA2LSB 1
|
#define ELFDATA2LSB 1
|
||||||
@@ -54,7 +51,6 @@
|
|||||||
#define R_X86_64_PLT32 4
|
#define R_X86_64_PLT32 4
|
||||||
#define ELF64_R_INFO(s,t) (((u64)(s) << 32) | ((u64)(t) & 0xffffffff))
|
#define ELF64_R_INFO(s,t) (((u64)(s) << 32) | ((u64)(t) & 0xffffffff))
|
||||||
|
|
||||||
/* growable byte buffer */
|
|
||||||
typedef struct Buf Buf;
|
typedef struct Buf Buf;
|
||||||
struct Buf { u8 *p; size_t n, cap; };
|
struct Buf { u8 *p; size_t n, cap; };
|
||||||
|
|
||||||
@@ -157,10 +153,9 @@ a_emit_elf(Asm *a, FILE *f)
|
|||||||
bput(&sym, &z, sizeof z);
|
bput(&sym, &z, sizeof z);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Build symbols (defined = global; undefined = global UND). Data
|
/* Data symbols carry STT_OBJECT and st_shndx=SH_DATA; everything
|
||||||
* symbols carry STT_OBJECT and st_shndx=SH_DATA; everything else
|
* else keeps the legacy STT_FUNC/SH_TEXT shape so non-DATAW
|
||||||
* keeps the legacy STT_FUNC/SH_TEXT shape so non-DATAW outputs
|
* outputs stay byte-identical. */
|
||||||
* stay byte-identical. */
|
|
||||||
int idx = 1;
|
int idx = 1;
|
||||||
for (Asym *s = a->syms; s; s = s->next) {
|
for (Asym *s = a->syms; s; s = s->next) {
|
||||||
Sym64 e = {0};
|
Sym64 e = {0};
|
||||||
@@ -193,7 +188,6 @@ a_emit_elf(Asm *a, FILE *f)
|
|||||||
bput(r->section == 1 ? &relad : &rela, &re, sizeof re);
|
bput(r->section == 1 ? &relad : &rela, &re, sizeof re);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Layout offsets in the file */
|
|
||||||
u64 off = sizeof(Ehdr);
|
u64 off = sizeof(Ehdr);
|
||||||
u64 off_text = off; off += a->textlen;
|
u64 off_text = off; off += a->textlen;
|
||||||
u64 off_rela = off; off += rela.n;
|
u64 off_rela = off; off += rela.n;
|
||||||
@@ -202,7 +196,6 @@ a_emit_elf(Asm *a, FILE *f)
|
|||||||
u64 off_sym = off; off += sym.n;
|
u64 off_sym = off; off += sym.n;
|
||||||
u64 off_str = off; off += str.n;
|
u64 off_str = off; off += str.n;
|
||||||
u64 off_shstr= off; off += shstr.n;
|
u64 off_shstr= off; off += shstr.n;
|
||||||
/* align to 8 */
|
|
||||||
while (off % 8) off++;
|
while (off % 8) off++;
|
||||||
u64 off_shdr = off;
|
u64 off_shdr = off;
|
||||||
const int NSECT = has_data
|
const int NSECT = has_data
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* parse.c — line-oriented parser for the asm subset emitted by w6c.
|
|
||||||
*
|
|
||||||
* Grammar:
|
* Grammar:
|
||||||
* line := blank | comment | label | text | instr
|
* line := blank | comment | label | text | instr
|
||||||
* blank := /^\s*$/
|
* blank := /^\s*$/
|
||||||
@@ -52,7 +50,6 @@ a_intern(Asm *a, const char *name)
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* line iterator: returns the next line as a NUL-terminated buffer in
|
/* line iterator: returns the next line as a NUL-terminated buffer in
|
||||||
* line/llen pointers, advances pos. Returns 0 on EOF.
|
* line/llen pointers, advances pos. Returns 0 on EOF.
|
||||||
*/
|
*/
|
||||||
@@ -70,7 +67,6 @@ nextline(Asm *a, char **line, size_t *llen, char *buf, size_t bufsz)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* skip leading whitespace */
|
|
||||||
static const char *
|
static const char *
|
||||||
skipws(const char *p)
|
skipws(const char *p)
|
||||||
{
|
{
|
||||||
@@ -168,7 +164,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (REG) form */
|
|
||||||
if (*s == '(') {
|
if (*s == '(') {
|
||||||
s++;
|
s++;
|
||||||
char rbuf[8] = {0};
|
char rbuf[8] = {0};
|
||||||
@@ -183,7 +178,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* number(REG) form, or label form, or REG */
|
|
||||||
const char *p = s;
|
const char *p = s;
|
||||||
int sign = 1;
|
int sign = 1;
|
||||||
if (*p == '-') { sign = -1; p++; }
|
if (*p == '-') { sign = -1; p++; }
|
||||||
@@ -208,7 +202,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IDENT — register or symbol-or-label */
|
|
||||||
if (a_isidstart((unsigned char)*s)) {
|
if (a_isidstart((unsigned char)*s)) {
|
||||||
char buf[256] = {0};
|
char buf[256] = {0};
|
||||||
int n = 0;
|
int n = 0;
|
||||||
@@ -226,7 +219,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
|
|||||||
s = end;
|
s = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ID(SB) means external symbol */
|
|
||||||
if (*s == '(') {
|
if (*s == '(') {
|
||||||
char rbuf[8] = {0};
|
char rbuf[8] = {0};
|
||||||
int rn = 0;
|
int rn = 0;
|
||||||
@@ -253,7 +245,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
|
|||||||
out->type = r;
|
out->type = r;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
/* otherwise it's a branch target */
|
|
||||||
out->type = D_BRANCH;
|
out->type = D_BRANCH;
|
||||||
out->sym = strdup(buf);
|
out->sym = strdup(buf);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -281,7 +272,6 @@ a_parse(Asm *a)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* label? */
|
|
||||||
if (a_isidstart((unsigned char)*p) && line[0] != '\t') {
|
if (a_isidstart((unsigned char)*p) && line[0] != '\t') {
|
||||||
const char *q = p;
|
const char *q = p;
|
||||||
while (a_isidcont((unsigned char)*q)) q++;
|
while (a_isidcont((unsigned char)*q)) q++;
|
||||||
@@ -308,7 +298,6 @@ a_parse(Asm *a)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TEXT or instruction */
|
|
||||||
const char *m = p;
|
const char *m = p;
|
||||||
char mnem[16] = {0};
|
char mnem[16] = {0};
|
||||||
int n = 0;
|
int n = 0;
|
||||||
@@ -332,7 +321,6 @@ a_parse(Asm *a)
|
|||||||
const char *rest = m;
|
const char *rest = m;
|
||||||
|
|
||||||
if (op == A_TEXT) {
|
if (op == A_TEXT) {
|
||||||
/* TEXT name,$framesize */
|
|
||||||
char nbuf[256] = {0};
|
char nbuf[256] = {0};
|
||||||
int nn = 0;
|
int nn = 0;
|
||||||
while (*m && *m != ',' && nn < 255) nbuf[nn++] = *m++;
|
while (*m && *m != ',' && nn < 255) nbuf[nn++] = *m++;
|
||||||
@@ -363,7 +351,6 @@ a_parse(Asm *a)
|
|||||||
prg->nbytes = 0;
|
prg->nbytes = 0;
|
||||||
} else {
|
} else {
|
||||||
m++;
|
m++;
|
||||||
/* parse escapes into a fresh buffer */
|
|
||||||
size_t cap = 32, len = 0;
|
size_t cap = 32, len = 0;
|
||||||
u8 *buf = malloc(cap);
|
u8 *buf = malloc(cap);
|
||||||
while (*m && *m != '"') {
|
while (*m && *m != '"') {
|
||||||
@@ -398,7 +385,6 @@ a_parse(Asm *a)
|
|||||||
prg->nbytes = len;
|
prg->nbytes = len;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* split rest at top-level comma */
|
|
||||||
const char *comma = NULL;
|
const char *comma = NULL;
|
||||||
for (const char *q = rest; *q; q++)
|
for (const char *q = rest; *q; q++)
|
||||||
if (*q == ',' && comma == NULL) comma = q;
|
if (*q == ',' && comma == NULL) comma = q;
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ enum {
|
|||||||
A_LAST
|
A_LAST
|
||||||
};
|
};
|
||||||
|
|
||||||
const char *anames(int); /* opcode -> mnemonic */
|
const char *anames(int);
|
||||||
const char *rnames(int); /* register -> name */
|
const char *rnames(int);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1366,9 +1366,9 @@ let_emit_size(Type *t)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Is the unwrapped type a str? Used by the load/store paths so the
|
/* Used by the load/store paths so the (AX, BX) pair convention is
|
||||||
* (AX, BX) pair convention is preserved for str globals, mirroring
|
* preserved for str globals, mirroring what we already do for str
|
||||||
* what we already do for str locals. */
|
* locals. */
|
||||||
static int
|
static int
|
||||||
let_isstr(Type *t)
|
let_isstr(Type *t)
|
||||||
{
|
{
|
||||||
@@ -1377,8 +1377,8 @@ let_isstr(Type *t)
|
|||||||
return u && u->kind == TY_STR;
|
return u && u->kind == TY_STR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Is the unwrapped type a slice? Slice globals flow as the (AX, BX,
|
/* Slice globals flow as the (AX, BX, CX) triple — same as the local
|
||||||
* CX) triple — same as the local ABI. */
|
* ABI. */
|
||||||
static int
|
static int
|
||||||
let_isslice(Type *t)
|
let_isslice(Type *t)
|
||||||
{
|
{
|
||||||
@@ -1387,9 +1387,9 @@ let_isslice(Type *t)
|
|||||||
return u && u->kind == TY_SLICE;
|
return u && u->kind == TY_SLICE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Is the unwrapped type a struct? Struct globals only support field
|
/* Struct globals only support field access (read + plain `=` write
|
||||||
* access (read + plain `=` write for scalar fields). Whole-struct
|
* for scalar fields). Whole-struct by-value flow through expressions
|
||||||
* by-value flow through expressions isn't wired. */
|
* isn't wired. */
|
||||||
static int
|
static int
|
||||||
let_isstruct(Type *t)
|
let_isstruct(Type *t)
|
||||||
{
|
{
|
||||||
@@ -1398,9 +1398,8 @@ let_isstruct(Type *t)
|
|||||||
return u && u->kind == TY_STRUCT;
|
return u && u->kind == TY_STRUCT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Is the unwrapped type a fixed-length array? Array globals are
|
/* Array globals are zero-init DATAW slots; cgindex addresses them as
|
||||||
* zero-init DATAW slots; cgindex addresses them as LEAQ name(SB)
|
* LEAQ name(SB) and lets the element load/store run as usual. */
|
||||||
* and lets the element load/store run as usual. */
|
|
||||||
static int
|
static int
|
||||||
let_isarray(Type *t)
|
let_isarray(Type *t)
|
||||||
{
|
{
|
||||||
@@ -1409,9 +1408,9 @@ let_isarray(Type *t)
|
|||||||
return u && u->kind == TY_ARRAY;
|
return u && u->kind == TY_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Is the unwrapped type a float (f32 or f64)? Float globals flow
|
/* Float globals flow through X0 — load/store goes LEAQ name(SB),CX →
|
||||||
* through X0 — load/store goes LEAQ name(SB),CX → MOVSS/MOVSD via the
|
* MOVSS/MOVSD via the indirect, since the asm has no D_EXTERN form
|
||||||
* indirect, since the asm has no D_EXTERN form for SSE moves yet. */
|
* for SSE moves yet. */
|
||||||
static int
|
static int
|
||||||
let_isfloat(Type *t)
|
let_isfloat(Type *t)
|
||||||
{
|
{
|
||||||
@@ -1722,7 +1721,6 @@ let_var_type(const char *name)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Glue `<module>.<ident>` into a fresh arena buffer. */
|
|
||||||
static const char *
|
static const char *
|
||||||
mod_join(Cg *c, const char *mod, const char *ident)
|
mod_join(Cg *c, const char *mod, const char *ident)
|
||||||
{
|
{
|
||||||
@@ -1944,9 +1942,6 @@ cgslicehdr(Cg *c, int base)
|
|||||||
else if (base == D_AX) ins2(c, A_MOVQ, amem(base, 0), areg(D_AX));
|
else if (base == D_AX) ins2(c, A_MOVQ, amem(base, 0), areg(D_AX));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* per-fn local table: name → stack offset (positive = below FP) */
|
|
||||||
|
|
||||||
typedef struct Local Local;
|
typedef struct Local Local;
|
||||||
struct Local {
|
struct Local {
|
||||||
const char *name;
|
const char *name;
|
||||||
@@ -2074,8 +2069,7 @@ cg_base_cap(Cg *c, Node *base, Type *bu, Local *locals, int dst)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* expressions: result lands in AX. */
|
||||||
/* expressions: result lands in AX. Returns 1 on success. */
|
|
||||||
|
|
||||||
static void cgexpr(Cg*, Node*, Local*);
|
static void cgexpr(Cg*, Node*, Local*);
|
||||||
static void cgstmt(Cg*, Node*, Local**, int*);
|
static void cgstmt(Cg*, Node*, Local**, int*);
|
||||||
@@ -4678,7 +4672,6 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (amped) break;
|
if (amped) break;
|
||||||
/* Fall through to silent-drop fallback below. */
|
|
||||||
}
|
}
|
||||||
if (opnd && opnd->kind == N_INDEX) {
|
if (opnd && opnd->kind == N_INDEX) {
|
||||||
/* &base[i] = base + i*esz, no dereference.
|
/* &base[i] = base + i*esz, no dereference.
|
||||||
@@ -5436,7 +5429,6 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
}
|
}
|
||||||
after_dot_assign:
|
after_dot_assign:
|
||||||
if (u && u->kind == TY_STRUCT) {
|
if (u && u->kind == TY_STRUCT) {
|
||||||
/* find field metadata */
|
|
||||||
Tfield *f = NULL;
|
Tfield *f = NULL;
|
||||||
for (Tfield *fl = u->fields; fl; fl = fl->next)
|
for (Tfield *fl = u->fields; fl; fl = fl->next)
|
||||||
if (strcmp(fl->name, n->lhs->str) == 0)
|
if (strcmp(fl->name, n->lhs->str) == 0)
|
||||||
@@ -5725,7 +5717,6 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* now store AX into target */
|
|
||||||
if (via_ptr) {
|
if (via_ptr) {
|
||||||
if (boff == 0 && let_islet(base->str)) {
|
if (boff == 0 && let_islet(base->str)) {
|
||||||
/* #47 (inverse): a GLOBAL *struct
|
/* #47 (inverse): a GLOBAL *struct
|
||||||
@@ -15147,7 +15138,6 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
baseoff = localoff(c, locals, bname, 8, frame);
|
baseoff = localoff(c, locals, bname, 8, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* allocate per-name slots */
|
|
||||||
struct { int off, sz, foff; Type *ftype; } binds[8] = {0};
|
struct { int off, sz, foff; Type *ftype; } binds[8] = {0};
|
||||||
int nbinds = 0;
|
int nbinds = 0;
|
||||||
if (destruct) {
|
if (destruct) {
|
||||||
@@ -15733,7 +15723,6 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
|||||||
{
|
{
|
||||||
if (fn->body == NULL) return; /* extern decl, no body */
|
if (fn->body == NULL) return; /* extern decl, no body */
|
||||||
|
|
||||||
/* fresh per-fn state */
|
|
||||||
c->head = c->tail = NULL;
|
c->head = c->tail = NULL;
|
||||||
c->fnname = fn->str;
|
c->fnname = fn->str;
|
||||||
c->cur_mod = (fn->module && fn->module[0]) ? fn->module : NULL;
|
c->cur_mod = (fn->module && fn->module[0]) ? fn->module : NULL;
|
||||||
@@ -15788,7 +15777,6 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
|||||||
text->from.offset = 0; /* framesize patched below */
|
text->from.offset = 0; /* framesize patched below */
|
||||||
emit(c, text);
|
emit(c, text);
|
||||||
|
|
||||||
/* prologue */
|
|
||||||
ins1(c, A_PUSHQ, areg(D_BP));
|
ins1(c, A_PUSHQ, areg(D_BP));
|
||||||
ins2(c, A_MOVQ, areg(D_SP), areg(D_BP));
|
ins2(c, A_MOVQ, areg(D_SP), areg(D_BP));
|
||||||
Prog *subsp = newprog(c, A_SUBQ);
|
Prog *subsp = newprog(c, A_SUBQ);
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
/*
|
/* Plan 9 cmd/6c/gc.h shape, trimmed. */
|
||||||
* gc.h — w6c-private header: Prog/Adr structs, scratch register set,
|
|
||||||
* stack-frame state. Plan 9 cmd/6c/gc.h shape, trimmed.
|
|
||||||
*/
|
|
||||||
#ifndef SIX_GC_H
|
#ifndef SIX_GC_H
|
||||||
#define SIX_GC_H
|
#define SIX_GC_H
|
||||||
|
|
||||||
@@ -73,9 +70,7 @@ void emit(Cg*, Prog*);
|
|||||||
/* txt.c */
|
/* txt.c */
|
||||||
void txt_emit(FILE*, Prog *head);
|
void txt_emit(FILE*, Prog *head);
|
||||||
|
|
||||||
/* wwi.c — `.wwi` export-data producer (w6c -I). M2 dead-code: writes a
|
/* wwi.c — non-zero return: check_exported_type rejected a dangling export. */
|
||||||
* re-parseable ww-prototype rendering of the package's exported surface.
|
|
||||||
* Returns non-zero if check_exported_type rejects a dangling export. */
|
|
||||||
int wwi_emit(Checker *c, FILE *of, Node *file);
|
int wwi_emit(Checker *c, FILE *of, Node *file);
|
||||||
|
|
||||||
/* swt.c, peep.c, reg.c — placeholders for now */
|
/* swt.c, peep.c, reg.c — placeholders for now */
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
/*
|
|
||||||
* w6c — amd64 compiler driver. Reads a .ww source file, runs the
|
|
||||||
* libwcc frontend (lex → parse → check), then walks the typed AST
|
|
||||||
* via cgen.c and writes Plan 9-flavoured amd64 asm to stdout (or
|
|
||||||
* the file given by -o).
|
|
||||||
*/
|
|
||||||
#include "gc.h"
|
#include "gc.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* peep.c — peephole pass. Currently a no-op; reserved for the kind of
|
* Currently a no-op; reserved for the kind of cleanup Plan 9 6c does
|
||||||
* cleanup Plan 9 6c does (folding adjacent moves, removing redundant
|
* (folding adjacent moves, removing redundant compares). Wire in
|
||||||
* compares). Wire in `peephole(c)` from cgen.c after the main walk.
|
* `peephole(c)` from cgen.c after the main walk.
|
||||||
*/
|
*/
|
||||||
#include "gc.h"
|
#include "gc.h"
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* reg.c — register allocator. The current cgen pins everything to AX
|
* The current cgen pins everything to AX with BX as a scratch
|
||||||
* with BX as a scratch top-of-stack — no real allocation. This file
|
* top-of-stack — no real allocation. This file is the seam where a
|
||||||
* is the seam where a linear-scan or graph-colouring pass would land
|
* linear-scan or graph-colouring pass would land later; today it's
|
||||||
* later; today it's empty.
|
* empty.
|
||||||
*/
|
*/
|
||||||
#include "gc.h"
|
#include "gc.h"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* swt.c — switch-statement lowering. Stub for now: cgen falls
|
* Stub for now: cgen falls through to a no-op for N_SWITCH. When we
|
||||||
* through to a no-op for N_SWITCH. When we add a real lowering, it
|
* add a real lowering, it will live here, mirroring Plan 9 6c's
|
||||||
* will live here, mirroring Plan 9 6c's pswt.c.
|
* pswt.c.
|
||||||
*/
|
*/
|
||||||
#include "gc.h"
|
#include "gc.h"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* txt.c — print a Prog list as Plan 9-flavoured amd64 asm text.
|
|
||||||
*
|
|
||||||
* Format we emit (and that w6a expects):
|
* Format we emit (and that w6a expects):
|
||||||
* TEXT name<framesize>
|
* TEXT name<framesize>
|
||||||
* MOVQ $1, AX
|
* MOVQ $1, AX
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ wwi_primary(Node *n)
|
|||||||
return n && n->imported == 0;
|
return n && n->imported == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- check_exported_type (drew) ------------------------------------- *
|
/* check_exported_type (drew): resolve an N_TNAME to its type sym WITHOUT
|
||||||
* Resolve an N_TNAME to its type sym WITHOUT the side effects of
|
* the side effects of resolve_typename (no on-demand resolve, no double
|
||||||
* resolve_typename (no on-demand resolve, no double "unknown type"
|
* "unknown type" error). A primitive/keyword resolves to no SK_TYPE →
|
||||||
* error). A primitive/keyword resolves to no SK_TYPE → leaf. By the
|
* leaf. By the time the producer runs, check_file has finished and
|
||||||
* time the producer runs, check_file has finished and c->cur == c->top.
|
* c->cur == c->top.
|
||||||
*/
|
*/
|
||||||
static Sym *
|
static Sym *
|
||||||
wwi_typesym(Checker *c, const char *nm)
|
wwi_typesym(Checker *c, const char *nm)
|
||||||
@@ -141,7 +141,7 @@ wwi_check_decl(Checker *c, Node *d)
|
|||||||
return bad;
|
return bad;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- type-expr + const-expr unparser (rob §2.2/§2.4) ---------------- */
|
/* type-expr unparse per rob §2.2; const-expr per rob §2.4. */
|
||||||
|
|
||||||
static void wwi_expr(FILE *of, Node *e);
|
static void wwi_expr(FILE *of, Node *e);
|
||||||
static void wwi_type(FILE *of, Node *t);
|
static void wwi_type(FILE *of, Node *t);
|
||||||
@@ -447,7 +447,7 @@ wwi_decl(FILE *of, Node *d)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- deterministic ordering (rob §3) -------------------------------- */
|
/* deterministic ordering per rob §3. */
|
||||||
|
|
||||||
struct declent { Node *d; int idx; };
|
struct declent { Node *d; int idx; };
|
||||||
struct useent { const char *path; int idx; };
|
struct useent { const char *path; int idx; };
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* dyn.c — load a shared object (ET_DYN) so the linker knows which
|
* We do not pull bytes from the .so; the dynamic loader maps it at
|
||||||
* symbols it exports and which DT_NEEDED entry to record. We do not
|
* runtime.
|
||||||
* pull bytes from the .so; the dynamic loader maps it at runtime.
|
|
||||||
*
|
|
||||||
* Each call appends one Lso to lnk->sos. `l_so_provides` answers
|
|
||||||
* "does this .so export the named symbol?" — l_resolve uses that to
|
|
||||||
* promote unresolved references to dynamic.
|
|
||||||
*/
|
*/
|
||||||
#include "l.h"
|
#include "l.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* dynout.c — emit a dynamic-linked ELF executable.
|
|
||||||
*
|
|
||||||
* The shape we produce is the simplest valid one: PT_INTERP +
|
* The shape we produce is the simplest valid one: PT_INTERP +
|
||||||
* PT_DYNAMIC + DT_BIND_NOW so the loader resolves every PLT slot at
|
* PT_DYNAMIC + DT_BIND_NOW so the loader resolves every PLT slot at
|
||||||
* startup (no lazy binding, no PLT0 trampoline). Symbol versioning
|
* startup (no lazy binding, no PLT0 trampoline). SysV .hash, not
|
||||||
* is omitted; modern glibc tolerates unversioned references by
|
|
||||||
* binding to each symbol's "default" version. SysV .hash, not
|
|
||||||
* .gnu.hash. Non-PIE, fixed base.
|
* .gnu.hash. Non-PIE, fixed base.
|
||||||
*
|
*
|
||||||
* File layout:
|
* File layout:
|
||||||
@@ -31,7 +27,6 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
/* ELF constants */
|
|
||||||
#define ET_EXEC 2
|
#define ET_EXEC 2
|
||||||
#define EM_X86_64 62
|
#define EM_X86_64 62
|
||||||
#define EV_CURRENT 1
|
#define EV_CURRENT 1
|
||||||
@@ -127,7 +122,6 @@ elf_hash(const char *name)
|
|||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Patch a 4-byte little-endian field in `buf` at offset `off`. */
|
|
||||||
static void
|
static void
|
||||||
poke32(u8 *buf, u64 off, u32 v)
|
poke32(u8 *buf, u64 off, u32 v)
|
||||||
{
|
{
|
||||||
@@ -151,8 +145,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
|
|
||||||
const int N = l->dyn_n;
|
const int N = l->dyn_n;
|
||||||
|
|
||||||
/* ---- Pass 1: collect dynamic symbol names + .dynstr layout ---- */
|
|
||||||
|
|
||||||
/* dynstr layout: [0]='\0', then DT_NEEDED soname strings, then
|
/* dynstr layout: [0]='\0', then DT_NEEDED soname strings, then
|
||||||
* one symbol name per dynamic Lsym. We index dyn syms by
|
* one symbol name per dynamic Lsym. We index dyn syms by
|
||||||
* plt_idx (assigned in l_resolve). Build an array sorted by
|
* plt_idx (assigned in l_resolve). Build an array sorted by
|
||||||
@@ -189,7 +181,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Build .dynstr in a growable buffer. */
|
|
||||||
u8 *dynstr = NULL;
|
u8 *dynstr = NULL;
|
||||||
u64 dynstr_cap = 0, dynstr_len = 0;
|
u64 dynstr_cap = 0, dynstr_len = 0;
|
||||||
#define DSTR_PUT(s) do { \
|
#define DSTR_PUT(s) do { \
|
||||||
@@ -216,9 +207,7 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
DSTR_PUT(dynsyms[i]->name);
|
DSTR_PUT(dynsyms[i]->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Versioning: group dyn syms by (lib, version) ----
|
/* For every sym whose dyn_version is non-NULL, there's a
|
||||||
*
|
|
||||||
* For every sym whose dyn_version is non-NULL, there's a
|
|
||||||
* Vernaux record under that lib's Verneed. The vna_other
|
* Vernaux record under that lib's Verneed. The vna_other
|
||||||
* value (assigned starting at 2; 1 is reserved for "global,
|
* value (assigned starting at 2; 1 is reserved for "global,
|
||||||
* unversioned") becomes that sym's .gnu.version entry.
|
* unversioned") becomes that sym's .gnu.version entry.
|
||||||
@@ -263,13 +252,11 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
n_vlibs++;
|
n_vlibs++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Assign vna_other indices starting at 2. */
|
|
||||||
u16 next_vna = 2;
|
u16 next_vna = 2;
|
||||||
for (int i = 0; i < n_vlibs; i++)
|
for (int i = 0; i < n_vlibs; i++)
|
||||||
for (int k = 0; k < vlibs[i].n_versions; k++)
|
for (int k = 0; k < vlibs[i].n_versions; k++)
|
||||||
vlibs[i].versions[k].vna_other = next_vna++;
|
vlibs[i].versions[k].vna_other = next_vna++;
|
||||||
|
|
||||||
/* Add version name strings to .dynstr. */
|
|
||||||
for (int i = 0; i < n_vlibs; i++) {
|
for (int i = 0; i < n_vlibs; i++) {
|
||||||
for (int k = 0; k < vlibs[i].n_versions; k++) {
|
for (int k = 0; k < vlibs[i].n_versions; k++) {
|
||||||
vlibs[i].versions[k].dynstr_off = (u32)dynstr_len;
|
vlibs[i].versions[k].dynstr_off = (u32)dynstr_len;
|
||||||
@@ -301,8 +288,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Pass 2: compute byte sizes of every section ---- */
|
|
||||||
|
|
||||||
const u64 ehdr_sz = sizeof(Ehdr);
|
const u64 ehdr_sz = sizeof(Ehdr);
|
||||||
const int n_phdrs = 4;
|
const int n_phdrs = 4;
|
||||||
const u64 phdr_sz = (u64)n_phdrs * sizeof(Phdr);
|
const u64 phdr_sz = (u64)n_phdrs * sizeof(Phdr);
|
||||||
@@ -343,8 +328,7 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
const u64 ndyn = (u64)nsos + 11 + (with_ver ? 3 : 0);
|
const u64 ndyn = (u64)nsos + 11 + (with_ver ? 3 : 0);
|
||||||
const u64 dynamic_sz = ndyn * sizeof(Dyn64);
|
const u64 dynamic_sz = ndyn * sizeof(Dyn64);
|
||||||
|
|
||||||
/* ---- Pass 3: assign file offsets and virtual addresses ----
|
/* Everything from the Ehdr through .text+.plt is in the R+X
|
||||||
* Everything from the Ehdr through .text+.plt is in the R+X
|
|
||||||
* load segment at base+0..text_end. .got.plt and .dynamic land
|
* load segment at base+0..text_end. .got.plt and .dynamic land
|
||||||
* in the R+W segment at the next page boundary. */
|
* in the R+W segment at the next page boundary. */
|
||||||
|
|
||||||
@@ -409,9 +393,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
const u64 data_file_len = l->datalen - bsslen;
|
const u64 data_file_len = l->datalen - bsslen;
|
||||||
const u64 file_data_end = data_off + data_file_len;
|
const u64 file_data_end = data_off + data_file_len;
|
||||||
|
|
||||||
/* ---- Pass 4: build each section into a buffer ---- */
|
|
||||||
|
|
||||||
/* .dynsym */
|
|
||||||
Sym64 *dynsym = calloc((size_t)nsyms_total, sizeof *dynsym);
|
Sym64 *dynsym = calloc((size_t)nsyms_total, sizeof *dynsym);
|
||||||
for (int i = 0; i < N; i++) {
|
for (int i = 0; i < N; i++) {
|
||||||
Sym64 *e = &dynsym[1 + i];
|
Sym64 *e = &dynsym[1 + i];
|
||||||
@@ -438,7 +419,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
}
|
}
|
||||||
/* elf_hash is also used by .gnu.version_r for vna_hash below. */
|
/* elf_hash is also used by .gnu.version_r for vna_hash below. */
|
||||||
|
|
||||||
/* .rela.plt */
|
|
||||||
Rela64 *relaplt = calloc((size_t)N, sizeof *relaplt);
|
Rela64 *relaplt = calloc((size_t)N, sizeof *relaplt);
|
||||||
for (int i = 0; i < N; i++) {
|
for (int i = 0; i < N; i++) {
|
||||||
relaplt[i].r_offset = gotplt_va + (3 + (u64)i) * 8;
|
relaplt[i].r_offset = gotplt_va + (3 + (u64)i) * 8;
|
||||||
@@ -522,7 +502,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
}
|
}
|
||||||
/* [3..3+N-1] left zero; loader fills via R_X86_64_JUMP_SLOT. */
|
/* [3..3+N-1] left zero; loader fills via R_X86_64_JUMP_SLOT. */
|
||||||
|
|
||||||
/* .dynamic */
|
|
||||||
Dyn64 *dynamic = calloc((size_t)ndyn, sizeof *dynamic);
|
Dyn64 *dynamic = calloc((size_t)ndyn, sizeof *dynamic);
|
||||||
{
|
{
|
||||||
int k = 0;
|
int k = 0;
|
||||||
@@ -559,9 +538,9 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Pass 5: patch .text relocations targeting dynamic syms ---
|
/* Patch .text relocations targeting dynamic syms: the site is
|
||||||
* The site is the existing PC32/PLT32 displacement field. Target
|
* the existing PC32/PLT32 displacement field, the target the
|
||||||
* is the address of the symbol's PLT stub. */
|
* address of the symbol's PLT stub. */
|
||||||
for (Lrel *r = l->rels; r; r = r->next) {
|
for (Lrel *r = l->rels; r; r = r->next) {
|
||||||
if (r->sym == NULL || !r->sym->is_dyn) continue;
|
if (r->sym == NULL || !r->sym->is_dyn) continue;
|
||||||
if (r->kind != R_X86_64_PC32 && r->kind != R_X86_64_PLT32) {
|
if (r->kind != R_X86_64_PC32 && r->kind != R_X86_64_PLT32) {
|
||||||
@@ -579,8 +558,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
poke32(l->text, r->off, (u32)(i32)disp);
|
poke32(l->text, r->off, (u32)(i32)disp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Pass 6: emit ---- */
|
|
||||||
|
|
||||||
Ehdr eh = {0};
|
Ehdr eh = {0};
|
||||||
memcpy(eh.e_ident, "\x7f""ELF", 4);
|
memcpy(eh.e_ident, "\x7f""ELF", 4);
|
||||||
eh.e_ident[4] = ELFCLASS64;
|
eh.e_ident[4] = ELFCLASS64;
|
||||||
@@ -625,7 +602,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
ph[1].p_memsz = file_end - gotplt_off;
|
ph[1].p_memsz = file_end - gotplt_off;
|
||||||
ph[1].p_align = page;
|
ph[1].p_align = page;
|
||||||
|
|
||||||
/* PT_INTERP. */
|
|
||||||
ph[2].p_type = PT_INTERP;
|
ph[2].p_type = PT_INTERP;
|
||||||
ph[2].p_flags = PF_R;
|
ph[2].p_flags = PF_R;
|
||||||
ph[2].p_offset = interp_off;
|
ph[2].p_offset = interp_off;
|
||||||
@@ -635,7 +611,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
ph[2].p_memsz = interp_sz;
|
ph[2].p_memsz = interp_sz;
|
||||||
ph[2].p_align = 1;
|
ph[2].p_align = 1;
|
||||||
|
|
||||||
/* PT_DYNAMIC. */
|
|
||||||
ph[3].p_type = PT_DYNAMIC;
|
ph[3].p_type = PT_DYNAMIC;
|
||||||
ph[3].p_flags = PF_R | PF_W;
|
ph[3].p_flags = PF_R | PF_W;
|
||||||
ph[3].p_offset = dynamic_off;
|
ph[3].p_offset = dynamic_off;
|
||||||
@@ -648,7 +623,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
fwrite(&eh, 1, sizeof eh, f);
|
fwrite(&eh, 1, sizeof eh, f);
|
||||||
fwrite(ph, 1, sizeof ph, f);
|
fwrite(ph, 1, sizeof ph, f);
|
||||||
|
|
||||||
/* helper: pad to absolute offset `to` */
|
|
||||||
#define PAD_TO(to) do { \
|
#define PAD_TO(to) do { \
|
||||||
long _here = ftell(f); \
|
long _here = ftell(f); \
|
||||||
for (long _i = _here; _i < (long)(to); _i++) fputc(0, f); \
|
for (long _i = _here; _i < (long)(to); _i++) fputc(0, f); \
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
/*
|
|
||||||
* l.h — w6l-private header. Loads relocatable ELF64 .o files (the
|
|
||||||
* format produced by w6a) and links them into a static executable.
|
|
||||||
*
|
|
||||||
* No archives yet (phase 8). No dynamic linking ever.
|
|
||||||
*/
|
|
||||||
#ifndef SIX_L_H
|
#ifndef SIX_L_H
|
||||||
#define SIX_L_H
|
#define SIX_L_H
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* w6l — amd64 linker. Reads relocatable ELF .o files (from w6a) plus
|
|
||||||
* .a archives, resolves, relocates, writes a static ELF executable.
|
|
||||||
* Dynamic linking against .so files is the next increment; the -L/-l
|
|
||||||
* flag plumbing here is its first step.
|
|
||||||
*
|
|
||||||
* w6l -o out [-L<dir>...] [-l<name>...] file1.o file2.o ...
|
* w6l -o out [-L<dir>...] [-l<name>...] file1.o file2.o ...
|
||||||
*
|
*
|
||||||
* The first symbol named "_start" defined among the inputs becomes
|
* The first symbol named "_start" defined among the inputs becomes
|
||||||
@@ -134,7 +129,6 @@ main(int argc, char **argv)
|
|||||||
int rc = l_emit_elf(&l, f, base, base + 0x1000 + entry->val);
|
int rc = l_emit_elf(&l, f, base, base + 0x1000 + entry->val);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
if (rc == 0) {
|
if (rc == 0) {
|
||||||
/* chmod +x */
|
|
||||||
char cmd[1024];
|
char cmd[1024];
|
||||||
snprintf(cmd, sizeof cmd, "chmod +x %s", out);
|
snprintf(cmd, sizeof cmd, "chmod +x %s", out);
|
||||||
(void)system(cmd);
|
(void)system(cmd);
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
/*
|
|
||||||
* obj.c — load an ELF64 relocatable object emitted by w6a, append its
|
|
||||||
* .text bytes to the combined image, and pull its symbols and
|
|
||||||
* relocations into the global tables (with offsets adjusted to the
|
|
||||||
* combined section).
|
|
||||||
*/
|
|
||||||
#include "l.h"
|
#include "l.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -97,11 +91,8 @@ emit_data(Lnk *l, const u8 *src, u64 n)
|
|||||||
l->datalen += n;
|
l->datalen += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Internal: load a single ELF .o image already in memory. The caller
|
/* The caller gives us the bytes (we own them) and a path tag for
|
||||||
* gives us the bytes (we own them) and a path tag for diagnostics.
|
* diagnostics. */
|
||||||
* If the bytes look like an archive (magic "!<arch>\n") we recurse
|
|
||||||
* over each member instead.
|
|
||||||
*/
|
|
||||||
static int load_image(Lnk *l, const char *path, u8 *buf, u64 len);
|
static int load_image(Lnk *l, const char *path, u8 *buf, u64 len);
|
||||||
|
|
||||||
static u64
|
static u64
|
||||||
@@ -119,9 +110,8 @@ ar_field(const u8 *p, int n)
|
|||||||
|
|
||||||
/* Read an ELF .o image's globally-defined symbol names without
|
/* Read an ELF .o image's globally-defined symbol names without
|
||||||
* actually appending it to the link. Returns a heap-allocated
|
* actually appending it to the link. Returns a heap-allocated
|
||||||
* NULL-terminated array; caller frees the array (not the strings,
|
* NULL-terminated array; caller frees the array, not the strings,
|
||||||
* which point into the .o image and must remain alive).
|
* which point into the .o image and need it kept alive. */
|
||||||
*/
|
|
||||||
static char **
|
static char **
|
||||||
elf_globals(const u8 *buf, u64 len)
|
elf_globals(const u8 *buf, u64 len)
|
||||||
{
|
{
|
||||||
@@ -299,8 +289,6 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len)
|
|||||||
if (eh->e_shstrndx >= eh->e_shnum) { free(buf); return -1; }
|
if (eh->e_shstrndx >= eh->e_shnum) { free(buf); return -1; }
|
||||||
const char *shstr = (const char *)(buf + sh[eh->e_shstrndx].sh_offset);
|
const char *shstr = (const char *)(buf + sh[eh->e_shstrndx].sh_offset);
|
||||||
|
|
||||||
/* find .text, .data (optional), .symtab, .strtab, .rela.text,
|
|
||||||
* .rela.data (optional) */
|
|
||||||
int idx_text = -1, idx_data = -1, idx_symtab = -1, idx_strtab = -1;
|
int idx_text = -1, idx_data = -1, idx_symtab = -1, idx_strtab = -1;
|
||||||
int idx_rela = -1, idx_relad = -1;
|
int idx_rela = -1, idx_relad = -1;
|
||||||
for (u16 i = 0; i < eh->e_shnum; i++) {
|
for (u16 i = 0; i < eh->e_shnum; i++) {
|
||||||
@@ -334,12 +322,10 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len)
|
|||||||
ob->next = l->objs;
|
ob->next = l->objs;
|
||||||
l->objs = ob;
|
l->objs = ob;
|
||||||
|
|
||||||
/* append .text and (if present) .data */
|
|
||||||
emit_text(l, buf + sh[idx_text].sh_offset, sh[idx_text].sh_size);
|
emit_text(l, buf + sh[idx_text].sh_offset, sh[idx_text].sh_size);
|
||||||
if (idx_data >= 0 && sh[idx_data].sh_size > 0)
|
if (idx_data >= 0 && sh[idx_data].sh_size > 0)
|
||||||
emit_data(l, buf + sh[idx_data].sh_offset, sh[idx_data].sh_size);
|
emit_data(l, buf + sh[idx_data].sh_offset, sh[idx_data].sh_size);
|
||||||
|
|
||||||
/* per-object: load symbols */
|
|
||||||
Sym64 *symtab = (Sym64 *)(buf + sh[idx_symtab].sh_offset);
|
Sym64 *symtab = (Sym64 *)(buf + sh[idx_symtab].sh_offset);
|
||||||
u64 nsyms = sh[idx_symtab].sh_size / sizeof(Sym64);
|
u64 nsyms = sh[idx_symtab].sh_size / sizeof(Sym64);
|
||||||
const char *str = (const char *)(buf + sh[idx_strtab].sh_offset);
|
const char *str = (const char *)(buf + sh[idx_strtab].sh_offset);
|
||||||
@@ -373,7 +359,6 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len)
|
|||||||
map[i] = gs;
|
map[i] = gs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* per-object: collect relocations */
|
|
||||||
if (idx_rela >= 0) {
|
if (idx_rela >= 0) {
|
||||||
Rela64 *rt = (Rela64 *)(buf + sh[idx_rela].sh_offset);
|
Rela64 *rt = (Rela64 *)(buf + sh[idx_rela].sh_offset);
|
||||||
u64 nrel = sh[idx_rela].sh_size / sizeof(Rela64);
|
u64 nrel = sh[idx_rela].sh_size / sizeof(Rela64);
|
||||||
@@ -389,8 +374,7 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len)
|
|||||||
l->rels = r;
|
l->rels = r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* per-object: collect data relocations from .rela.data. The
|
/* The .data section in the .o starts at a per-object 0; we shift
|
||||||
* .data section in the .o starts at a per-object 0; we shift
|
|
||||||
* by ob->data_off so r->off indexes the combined .data buffer. */
|
* by ob->data_off so r->off indexes the combined .data buffer. */
|
||||||
if (idx_relad >= 0) {
|
if (idx_relad >= 0) {
|
||||||
Rela64 *rt = (Rela64 *)(buf + sh[idx_relad].sh_offset);
|
Rela64 *rt = (Rela64 *)(buf + sh[idx_relad].sh_offset);
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* out.c — emit a static ELF64 executable.
|
|
||||||
*
|
|
||||||
* Layout (file order) without .data:
|
* Layout (file order) without .data:
|
||||||
* [0..64) ELF header
|
* [0..64) ELF header
|
||||||
* [64..120) one program header (PT_LOAD R+X)
|
* [64..120) one program header (PT_LOAD R+X)
|
||||||
@@ -13,9 +11,6 @@
|
|||||||
* [176..0x1000) zero pad
|
* [176..0x1000) zero pad
|
||||||
* [0x1000..) .text bytes
|
* [0x1000..) .text bytes
|
||||||
* [data_off..) .data bytes (file offset and vaddr page-aligned)
|
* [data_off..) .data bytes (file offset and vaddr page-aligned)
|
||||||
*
|
|
||||||
* No interpreter, no dynamic, no .bss yet. Entry point is the address
|
|
||||||
* of the symbol named "_start" (or whatever main supplies via -e).
|
|
||||||
*/
|
*/
|
||||||
#include "l.h"
|
#include "l.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -102,9 +97,6 @@ l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
eh.e_phentsize = sizeof(Phdr);
|
eh.e_phentsize = sizeof(Phdr);
|
||||||
eh.e_phnum = has_data ? 2 : 1;
|
eh.e_phnum = has_data ? 2 : 1;
|
||||||
|
|
||||||
/* R+X load covering [0, rx_end). When .data is present we still
|
|
||||||
* round up to a page in memsz so the loader doesn't try to give
|
|
||||||
* the same page both R+X and R+W permissions. */
|
|
||||||
Phdr phx = {0};
|
Phdr phx = {0};
|
||||||
phx.p_type = PT_LOAD;
|
phx.p_type = PT_LOAD;
|
||||||
phx.p_flags = PF_R | PF_X;
|
phx.p_flags = PF_R | PF_X;
|
||||||
@@ -131,14 +123,12 @@ l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
|||||||
fwrite(&phx, 1, sizeof phx, f);
|
fwrite(&phx, 1, sizeof phx, f);
|
||||||
if (has_data) fwrite(&phw, 1, sizeof phw, f);
|
if (has_data) fwrite(&phw, 1, sizeof phw, f);
|
||||||
|
|
||||||
/* pad to text_off */
|
|
||||||
long here = ftell(f);
|
long here = ftell(f);
|
||||||
for (long i = here; i < (long)text_off; i++) fputc(0, f);
|
for (long i = here; i < (long)text_off; i++) fputc(0, f);
|
||||||
|
|
||||||
if (l->textlen) fwrite(l->text, 1, l->textlen, f);
|
if (l->textlen) fwrite(l->text, 1, l->textlen, f);
|
||||||
|
|
||||||
if (has_data && data_file_len > 0) {
|
if (has_data && data_file_len > 0) {
|
||||||
/* pad to data_off */
|
|
||||||
here = ftell(f);
|
here = ftell(f);
|
||||||
for (long i = here; i < (long)data_off; i++) fputc(0, f);
|
for (long i = here; i < (long)data_off; i++) fputc(0, f);
|
||||||
fwrite(l->data, 1, data_file_len, f);
|
fwrite(l->data, 1, data_file_len, f);
|
||||||
|
|||||||
@@ -1,14 +1,3 @@
|
|||||||
/*
|
|
||||||
* pass.c — resolution + relocation. After all objects are loaded:
|
|
||||||
*
|
|
||||||
* l_resolve : check that every symbol referenced by a relocation
|
|
||||||
* is defined somewhere. Errors get logged.
|
|
||||||
* l_relocate: with the final virtual base address known, walk the
|
|
||||||
* relocation list and patch the .text bytes in place.
|
|
||||||
*
|
|
||||||
* Supported relocation kinds: PC32 (2), PLT32 (4). Both are PC-relative
|
|
||||||
* 32-bit displacements; for static linking PLT32 collapses to PC32.
|
|
||||||
*/
|
|
||||||
#include "l.h"
|
#include "l.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -34,7 +23,7 @@ l_resolve(Lnk *l)
|
|||||||
* across runs (rels are pushed onto the head as objects load). */
|
* across runs (rels are pushed onto the head as objects load). */
|
||||||
for (Lrel *r = l->rels; r; r = r->next) {
|
for (Lrel *r = l->rels; r; r = r->next) {
|
||||||
if (r->sym == NULL || r->sym->defined) continue;
|
if (r->sym == NULL || r->sym->defined) continue;
|
||||||
if (r->sym->is_dyn) continue; /* already promoted */
|
if (r->sym->is_dyn) continue;
|
||||||
for (Lso *so = l->sos; so; so = so->next) {
|
for (Lso *so = l->sos; so; so = so->next) {
|
||||||
const char *ver = NULL;
|
const char *ver = NULL;
|
||||||
if (l_so_provides_v(so, r->sym->name, &ver)) {
|
if (l_so_provides_v(so, r->sym->name, &ver)) {
|
||||||
@@ -47,7 +36,6 @@ l_resolve(Lnk *l)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* What remains undefined truly is undefined. */
|
|
||||||
for (Lrel *r = l->rels; r; r = r->next) {
|
for (Lrel *r = l->rels; r; r = r->next) {
|
||||||
if (r->sym == NULL) continue;
|
if (r->sym == NULL) continue;
|
||||||
if (!r->sym->defined && !r->sym->is_dyn) {
|
if (!r->sym->defined && !r->sym->is_dyn) {
|
||||||
@@ -90,7 +78,8 @@ l_relocate(Lnk *l, u64 text_va, u64 data_va)
|
|||||||
switch (r->kind) {
|
switch (r->kind) {
|
||||||
case R_X86_64_PC32:
|
case R_X86_64_PC32:
|
||||||
case R_X86_64_PLT32: {
|
case R_X86_64_PLT32: {
|
||||||
/* PC-relative 32-bit displacement; lands in .text. */
|
/* PC-relative 32-bit displacement; lands in .text.
|
||||||
|
* For static linking PLT32 collapses to PC32. */
|
||||||
u64 site = text_va + r->off;
|
u64 site = text_va + r->off;
|
||||||
i64 rel = (i64)sym_va - (i64)site + r->addend;
|
i64 rel = (i64)sym_va - (i64)site + r->addend;
|
||||||
patch_u32(l->text + r->off, (u32)(i32)rel);
|
patch_u32(l->text + r->off, (u32)(i32)rel);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* sym.c — global symbol table for the linker. Plain singly-linked
|
* Plain singly-linked list; usually a few hundred entries, hashing
|
||||||
* list; usually a few hundred entries, hashing isn't worth it yet.
|
* isn't worth it yet.
|
||||||
*/
|
*/
|
||||||
#include "l.h"
|
#include "l.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* ast.c — Node constructor + s-expression printer.
|
* Printer output is rigid and deterministic so golden tests can diff.
|
||||||
*
|
|
||||||
* Constructor zeroes everything past kind/pos. Printer is rigid and
|
|
||||||
* deterministic so golden tests can diff. One node per logical line,
|
|
||||||
* children indented by 2 spaces.
|
|
||||||
*/
|
*/
|
||||||
#include "ww.h"
|
#include "ww.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* check.c — name resolution + type checking pass.
|
|
||||||
*
|
|
||||||
* Two-stage:
|
* Two-stage:
|
||||||
* 1) collect: walk top-level decls and install Syms with stub types.
|
* 1) collect: walk top-level decls and install Syms with stub types.
|
||||||
* 2) resolve: expand types, check fn bodies and def initialisers.
|
* 2) resolve: expand types, check fn bodies and def initialisers.
|
||||||
@@ -1046,8 +1044,6 @@ resolve_type(Checker *c, Node *n)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- expressions -------------------------------------------------- */
|
|
||||||
|
|
||||||
static Type *
|
static Type *
|
||||||
unify_arith(Checker *c, Pos p, Type *a, Type *b)
|
unify_arith(Checker *c, Pos p, Type *a, Type *b)
|
||||||
{
|
{
|
||||||
@@ -2007,7 +2003,6 @@ cexpr(Checker *c, Node *n)
|
|||||||
(void)cexpr(c, n->rhs);
|
(void)cexpr(c, n->rhs);
|
||||||
return n->type = ty_void;
|
return n->type = ty_void;
|
||||||
}
|
}
|
||||||
/* Reject assignment to a const-bound name. */
|
|
||||||
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str) {
|
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str) {
|
||||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod,
|
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod,
|
||||||
n->lhs->str);
|
n->lhs->str);
|
||||||
@@ -2387,8 +2382,6 @@ cexpr(Checker *c, Node *n)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- statements --------------------------------------------------- */
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
clet(Checker *c, Node *n)
|
clet(Checker *c, Node *n)
|
||||||
{
|
{
|
||||||
@@ -2731,8 +2724,6 @@ cstmt(Checker *c, Node *n)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- top-level ---------------------------------------------------- */
|
|
||||||
|
|
||||||
static Type *
|
static Type *
|
||||||
build_fn_type(Checker *c, Node *fn)
|
build_fn_type(Checker *c, Node *fn)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* err.c — diagnostics.
|
|
||||||
*
|
|
||||||
* fatal prints, sets exit(1).
|
|
||||||
* errorf prints with source location, increments nerrors.
|
|
||||||
* warnf prints with source location, increments nwarnings.
|
|
||||||
*
|
|
||||||
* Plan 9 style: short, no levels beyond fatal/error/warn, no colour.
|
* Plan 9 style: short, no levels beyond fatal/error/warn, no colour.
|
||||||
*/
|
*/
|
||||||
#include "ww.h"
|
#include "ww.h"
|
||||||
|
|||||||
@@ -1,16 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* lex.c — hand-rolled DFA. UTF-8 source, ASCII operators.
|
|
||||||
*
|
|
||||||
* Comments: //... and (slash-star ... star-slash). Both stripped.
|
|
||||||
* Whitespace: space, tab, CR, NL.
|
|
||||||
* Identifiers: [A-Za-z_][A-Za-z0-9_]* — also matches keywords; we
|
|
||||||
* look up the kw table after lexing the run.
|
|
||||||
* Integer: 0x[0-9a-fA-F_]+, 0o[0-7_]+, 0b[01_]+, [0-9][0-9_]*
|
|
||||||
* Float: [0-9]+'.'[0-9]+([eE][+-]?[0-9]+)?
|
|
||||||
* Rune: 'x' with C-like escapes
|
|
||||||
* String: "..." with C-like escapes
|
|
||||||
* Operators: longest match.
|
|
||||||
*
|
|
||||||
* No automatic semicolon insertion (Hare rule). The lexer only emits
|
* No automatic semicolon insertion (Hare rule). The lexer only emits
|
||||||
* what is in the source; the parser is responsible for non-empty rules.
|
* what is in the source; the parser is responsible for non-empty rules.
|
||||||
*/
|
*/
|
||||||
@@ -81,7 +69,6 @@ ishex(int c)
|
|||||||
(c >= 'A' && c <= 'F');
|
(c >= 'A' && c <= 'F');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* skip whitespace and comments. returns 0 on EOF, else 1. */
|
|
||||||
static int
|
static int
|
||||||
skipws(Lex *l)
|
skipws(Lex *l)
|
||||||
{
|
{
|
||||||
@@ -94,7 +81,7 @@ skipws(Lex *l)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (c == '/' && lpeek(l, 1) == '/') {
|
if (c == '/' && lpeek(l, 1) == '/') {
|
||||||
lget(l); lget(l); /* consume '//' */
|
lget(l); lget(l);
|
||||||
/* #16 option-B: the driver emits `//ww:module-reset`
|
/* #16 option-B: the driver emits `//ww:module-reset`
|
||||||
* before a package-less file's bytes; recognize the
|
* before a package-less file's bytes; recognize the
|
||||||
* whole-line directive (without consuming differently)
|
* whole-line directive (without consuming differently)
|
||||||
@@ -379,10 +366,7 @@ lexnum(Lex *l, Pos start)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Typed suffix: i8/i16/i32/i64, u8/u16/u32/u64, f32/f64.
|
/* A typed suffix must be glued (no whitespace) to the digits. */
|
||||||
* Must be glued (no whitespace) to the digits. We grab the
|
|
||||||
* adjacent identifier-like run and accept it only if it's one
|
|
||||||
* of the recognised type names. */
|
|
||||||
if (isidstart(lpeek(l, 0))) {
|
if (isidstart(lpeek(l, 0))) {
|
||||||
u64 sb = l->pos;
|
u64 sb = l->pos;
|
||||||
while (isidcont(lpeek(l, 0))) lget(l);
|
while (isidcont(lpeek(l, 0))) lget(l);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* mem.c — arena allocator. No free per allocation; freearena releases
|
* No free per allocation; freearena releases the whole chain.
|
||||||
* the whole chain. Aligned to 16 so structs with 8-byte fields and
|
* Aligned to 16 so structs with 8-byte fields and doubles are happy.
|
||||||
* doubles are happy.
|
* The chunk size doubles up to a cap so we don't fragment on huge
|
||||||
*
|
* inputs.
|
||||||
* Hot allocations in the compiler land in arenas: tokens, AST nodes,
|
|
||||||
* symbols, types. The chunk size doubles up to a cap so we don't
|
|
||||||
* fragment on huge inputs.
|
|
||||||
*/
|
*/
|
||||||
#include "ww.h"
|
#include "ww.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -43,7 +40,6 @@ grow(Arena *a, u64 need)
|
|||||||
if (ncap < need)
|
if (ncap < need)
|
||||||
ncap = roundup(need, ALIGN);
|
ncap = roundup(need, ALIGN);
|
||||||
|
|
||||||
/* push current chunk onto chain, allocate fresh head */
|
|
||||||
Arena *old = malloc(sizeof *old);
|
Arena *old = malloc(sizeof *old);
|
||||||
if (old == NULL)
|
if (old == NULL)
|
||||||
fatal("arena: oom");
|
fatal("arena: oom");
|
||||||
|
|||||||
@@ -100,8 +100,6 @@ static Node *parsetype(Parser *p);
|
|||||||
static Node *parseblock(Parser *p);
|
static Node *parseblock(Parser *p);
|
||||||
static Node *parsestmt(Parser *p);
|
static Node *parsestmt(Parser *p);
|
||||||
|
|
||||||
/* ------- type expressions ------------------------------------------ */
|
|
||||||
|
|
||||||
static Node *
|
static Node *
|
||||||
parseparams(Parser *p)
|
parseparams(Parser *p)
|
||||||
{
|
{
|
||||||
@@ -376,8 +374,6 @@ parsetype(Parser *p)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------- expressions (Pratt) ---------------------------------------- */
|
|
||||||
|
|
||||||
/* binary precedence; 0 = not a binary op */
|
/* binary precedence; 0 = not a binary op */
|
||||||
static int
|
static int
|
||||||
bprec(Tkind k)
|
bprec(Tkind k)
|
||||||
@@ -663,10 +659,9 @@ parseprimary(Parser *p)
|
|||||||
n->str = t.text;
|
n->str = t.text;
|
||||||
n->strlen = t.tlen;
|
n->strlen = t.tlen;
|
||||||
advance(p);
|
advance(p);
|
||||||
/* dotted ident chain folded into one IDENT for type-ish refs */
|
|
||||||
while (p->cur.kind == TK_DOT && peek(p).kind == TK_IDENT) {
|
while (p->cur.kind == TK_DOT && peek(p).kind == TK_IDENT) {
|
||||||
advance(p);
|
advance(p);
|
||||||
n = (Node*)n; /* keep stable */
|
n = (Node*)n;
|
||||||
Node *mr = newnode(p->a, N_DOT, pp);
|
Node *mr = newnode(p->a, N_DOT, pp);
|
||||||
mr->lhs = n;
|
mr->lhs = n;
|
||||||
mr->str = p->cur.text;
|
mr->str = p->cur.text;
|
||||||
@@ -674,7 +669,6 @@ parseprimary(Parser *p)
|
|||||||
advance(p);
|
advance(p);
|
||||||
n = mr;
|
n = mr;
|
||||||
}
|
}
|
||||||
/* struct literal: ident '{' ... '}' (only if ident-shaped) */
|
|
||||||
if (p->cur.kind == TK_LBRACE) {
|
if (p->cur.kind == TK_LBRACE) {
|
||||||
/* #76: bare `Foo{}` keeps the N_IDENT fast-path; a
|
/* #76: bare `Foo{}` keeps the N_IDENT fast-path; a
|
||||||
* qualified `pkg.Type{}` (N_DOT chain) flattens first. */
|
* qualified `pkg.Type{}` (N_DOT chain) flattens first. */
|
||||||
@@ -902,8 +896,6 @@ parseexpr_top(Parser *p)
|
|||||||
return parseexpr(p);
|
return parseexpr(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------- statements ------------------------------------------------- */
|
|
||||||
|
|
||||||
static Node *
|
static Node *
|
||||||
parselet(Parser *p, int top)
|
parselet(Parser *p, int top)
|
||||||
{
|
{
|
||||||
@@ -944,7 +936,6 @@ parselet(Parser *p, int top)
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* parse first binding */
|
|
||||||
Pos lp = p->cur.pos;
|
Pos lp = p->cur.pos;
|
||||||
Node *first = newnode(p->a, N_LET, lp);
|
Node *first = newnode(p->a, N_LET, lp);
|
||||||
first->str = expectbindname(p);
|
first->str = expectbindname(p);
|
||||||
@@ -952,7 +943,6 @@ parselet(Parser *p, int top)
|
|||||||
first->lhs = parsetype(p);
|
first->lhs = parsetype(p);
|
||||||
|
|
||||||
if (p->cur.kind == TK_COMMA) {
|
if (p->cur.kind == TK_COMMA) {
|
||||||
/* multi-let: collect (name, type) pairs, then '=' rhs */
|
|
||||||
Node *m = newnode(p->a, N_MLET, pp);
|
Node *m = newnode(p->a, N_MLET, pp);
|
||||||
Node *head = first, *tail = first;
|
Node *head = first, *tail = first;
|
||||||
while (accept(p, TK_COMMA)) {
|
while (accept(p, TK_COMMA)) {
|
||||||
@@ -1038,7 +1028,7 @@ parsefor(Parser *p)
|
|||||||
* Tuple destructure: `let (a, b) .. expr`. */
|
* Tuple destructure: `let (a, b) .. expr`. */
|
||||||
Tok save_cur = p->cur;
|
Tok save_cur = p->cur;
|
||||||
(void)save_cur;
|
(void)save_cur;
|
||||||
advance(p); /* consume LET */
|
advance(p);
|
||||||
if (p->cur.kind == TK_LPAREN) {
|
if (p->cur.kind == TK_LPAREN) {
|
||||||
advance(p);
|
advance(p);
|
||||||
Node *names = NULL, *tail = NULL;
|
Node *names = NULL, *tail = NULL;
|
||||||
@@ -1067,8 +1057,8 @@ parsefor(Parser *p)
|
|||||||
int isunder = p->cur.kind == TK_UNDER;
|
int isunder = p->cur.kind == TK_UNDER;
|
||||||
Tok la = peek(p);
|
Tok la = peek(p);
|
||||||
if (la.kind == TK_DOTDOT) {
|
if (la.kind == TK_DOTDOT) {
|
||||||
advance(p); /* consume IDENT/UNDER */
|
advance(p);
|
||||||
advance(p); /* consume DOTDOT */
|
advance(p);
|
||||||
Node *rng = newnode(p->a, N_FORRANGE, pp);
|
Node *rng = newnode(p->a, N_FORRANGE, pp);
|
||||||
rng->str = isunder ? "" : nm;
|
rng->str = isunder ? "" : nm;
|
||||||
rng->lhs = parseexpr(p);
|
rng->lhs = parseexpr(p);
|
||||||
@@ -1283,8 +1273,6 @@ parseblock(Parser *p)
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------- top-level decls ------------------------------------------- */
|
|
||||||
|
|
||||||
/* `import encoding.utf8;` — the driver resolves the dotted path to a
|
/* `import encoding.utf8;` — the driver resolves the dotted path to a
|
||||||
* directory; the checker only needs the leaf (`utf8`) as the module
|
* directory; the checker only needs the leaf (`utf8`) as the module
|
||||||
* bareword for n_use→decl disambiguation, mirroring Hare's
|
* bareword for n_use→decl disambiguation, mirroring Hare's
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
/*
|
|
||||||
* sym.c — symbol table. Plan 9-flavoured: a per-scope hashtable
|
|
||||||
* chained to the parent scope. Lookup walks up. Duplicate definitions
|
|
||||||
* within the same scope are flagged by the caller (we just refuse the
|
|
||||||
* insert and return the first one).
|
|
||||||
*/
|
|
||||||
#include "ww.h"
|
#include "ww.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@@ -54,13 +48,6 @@ scope_lookup(Scope *s, const char *name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* scope_lookup_in_module — module-filtered chain walk.
|
|
||||||
*
|
|
||||||
* Same FNV bucket + hashnext chain + parent walk as scope_lookup,
|
|
||||||
* plus a (b->mod != NULL && strcmp(b->mod, mod) == 0) filter. When
|
|
||||||
* `mod` is NULL we fall back to unfiltered scope_lookup semantics,
|
|
||||||
* so callers that don't care about disambiguation get the default.
|
|
||||||
*
|
|
||||||
* Used by resolve_typename and the cexpr N_DOT branch to pick the
|
* Used by resolve_typename and the cexpr N_DOT branch to pick the
|
||||||
* right same-leaf-name type when two imports each export it
|
* right same-leaf-name type when two imports each export it
|
||||||
* (`bufio.stream` vs `io.stream`).
|
* (`bufio.stream` vs `io.stream`).
|
||||||
@@ -80,19 +67,13 @@ scope_lookup_in_module(Scope *s, const char *mod, const char *name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* scope_lookup_prefer — bare-leaf lookup with same-module preference.
|
* Within each scope's bucket: Pass 1 prefers entries whose `sym.mod`
|
||||||
*
|
* matches the caller's `mod`; Pass 2 falls back to the first match
|
||||||
* Walks the same FNV bucket + hashnext chain + parent walk scope_lookup
|
* regardless of mod (the existing scope_lookup semantics). We only
|
||||||
* uses. Within each scope's bucket: Pass 1 prefers entries whose
|
* descend to the parent scope when the current scope has no matching
|
||||||
* `sym.mod` matches the caller's `mod`; Pass 2 falls back to the first
|
* entry at all — so a local binding in a closer scope still shadows a
|
||||||
* match regardless of mod (the existing scope_lookup semantics). We
|
* same-name fn from a parent scope, even when the parent entry
|
||||||
* only descend to the parent scope when the current scope has no
|
* mod-matches.
|
||||||
* matching entry at all — so a local binding in a closer scope still
|
|
||||||
* shadows a same-name fn from a parent scope, even when the parent
|
|
||||||
* entry mod-matches.
|
|
||||||
*
|
|
||||||
* When `mod` is NULL we just call scope_lookup — there's no module
|
|
||||||
* identity to prefer.
|
|
||||||
*
|
*
|
||||||
* Used at bare-leaf lookup sites inside a known current module so that
|
* Used at bare-leaf lookup sites inside a known current module so that
|
||||||
* a bare `read` inside lib/os resolves to os.read rather than the
|
* a bare `read` inside lib/os resolves to os.read rather than the
|
||||||
@@ -117,15 +98,12 @@ scope_lookup_prefer(Scope *s, const char *mod, const char *name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* scope_lookup_type — kind-filtered bare-leaf lookup for type position.
|
* Skips every Sym whose kind isn't SK_TYPE and KEEPS scanning — so it
|
||||||
*
|
* returns the innermost SK_TYPE of `name`, looking past a same-named
|
||||||
* Same FNV bucket + hashnext chain + parent walk and same-module
|
* value binding (SK_VAR/SK_PARAM/SK_FN) that shadows it in a closer
|
||||||
* preference as scope_lookup_prefer, but skips every Sym whose kind
|
* scope. ww keeps type and value namespaces separate (wwstage already
|
||||||
* isn't SK_TYPE and KEEPS scanning — so it returns the innermost
|
* does; #225 conformance gap): a param `off` must not hide the global
|
||||||
* SK_TYPE of `name`, looking past a same-named value binding (SK_VAR/
|
* `type off`.
|
||||||
* SK_PARAM/SK_FN) that shadows it in a closer scope. ww keeps type and
|
|
||||||
* value namespaces separate (wwstage already does; #225 conformance
|
|
||||||
* gap): a param `off` must not hide the global `type off`.
|
|
||||||
*/
|
*/
|
||||||
Sym *
|
Sym *
|
||||||
scope_lookup_type(Scope *s, const char *mod, const char *name)
|
scope_lookup_type(Scope *s, const char *mod, const char *name)
|
||||||
@@ -151,10 +129,8 @@ scope_define(Scope *s, const char *name, Skind k, Type *t, Node *decl)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* scope_define_in_module — bucket insert with per-mod dedup.
|
* The duplicate-rejection key is (name, mod) rather than name alone.
|
||||||
*
|
* This lets two imports each
|
||||||
* Same insertion as scope_define, but the duplicate-rejection key is
|
|
||||||
* (name, mod) rather than name alone. This lets two imports each
|
|
||||||
* register their own `stream` SK_TYPE in the flat scope, and lets the
|
* register their own `stream` SK_TYPE in the flat scope, and lets the
|
||||||
* primary register `stream` (mod=NULL) alongside imported `stream`s.
|
* primary register `stream` (mod=NULL) alongside imported `stream`s.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
/*
|
|
||||||
* tok.c — token names, keyword lookup, debug printer.
|
|
||||||
*
|
|
||||||
* One table-of-records keyed by kind. The keyword subset is also
|
|
||||||
* scanned linearly during lexing — fewer than 25 entries, a hash
|
|
||||||
* isn't worth it.
|
|
||||||
*/
|
|
||||||
#include "ww.h"
|
#include "ww.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@@ -13,7 +6,8 @@ struct kwent {
|
|||||||
Tkind kind;
|
Tkind kind;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* keep alphabetised, so kwlookup is easy to read. */
|
/* keep alphabetised, so kwlookup is easy to read. Scanned linearly:
|
||||||
|
* fewer than 25 entries, a hash isn't worth it. */
|
||||||
static const struct kwent kwtab[] = {
|
static const struct kwent kwtab[] = {
|
||||||
{ "as", TK_AS },
|
{ "as", TK_AS },
|
||||||
{ "break", TK_BREAK },
|
{ "break", TK_BREAK },
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* type.c — Type values and structural equality.
|
|
||||||
*
|
|
||||||
* Built-in types are constructed once and exposed as globals so the
|
* Built-in types are constructed once and exposed as globals so the
|
||||||
* rest of the compiler can `==`-compare them. Compound types (ptr,
|
* rest of the compiler can `==`-compare them.
|
||||||
* slice, array, fn, struct, chan) are constructed on demand and
|
|
||||||
* de-duplicated when equality is cheap (only ptr/slice for now).
|
|
||||||
*/
|
*/
|
||||||
#include "ww.h"
|
#include "ww.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -147,8 +143,7 @@ type_named(Arena *a, const char *name, Type *under)
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* type_chase_named — walk the TY_NAMED.under chain to the deepest non-
|
/* Chain-of-aliases (#22): `type b = a; type a = struct;`
|
||||||
* named type. Chain-of-aliases (#22): `type b = a; type a = struct;`
|
|
||||||
* stacks two TY_NAMED layers — a single peel leaves `t` pointing at
|
* stacks two TY_NAMED layers — a single peel leaves `t` pointing at
|
||||||
* the inner alias (still TY_NAMED), so kind-gated arms (TY_STRUCT,
|
* the inner alias (still TY_NAMED), so kind-gated arms (TY_STRUCT,
|
||||||
* TY_SLICE, TY_TAGGED, TY_PTR) miss and the consumer silently falls
|
* TY_SLICE, TY_TAGGED, TY_PTR) miss and the consumer silently falls
|
||||||
@@ -372,7 +367,6 @@ type_assignable(Type *dst, Type *src)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Untyped → typed: only if the typed kind can hold the value. */
|
|
||||||
if (type_isuntyped(src)) {
|
if (type_isuntyped(src)) {
|
||||||
Type *du = type_chase_named(dst);
|
Type *du = type_chase_named(dst);
|
||||||
if (src->kind == TY_UNTYPED_INT && type_isnum(dst)) return 1;
|
if (src->kind == TY_UNTYPED_INT && type_isnum(dst)) return 1;
|
||||||
|
|||||||
14
cmd/wcc/ww.h
14
cmd/wcc/ww.h
@@ -1,6 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* ww.h — central header for libwcc.a (the ww frontend library).
|
|
||||||
*
|
|
||||||
* Plan 9 in spirit. This file mirrors cc/cc.h's role: one shared
|
* Plan 9 in spirit. This file mirrors cc/cc.h's role: one shared
|
||||||
* header that declares everything every translation unit in the
|
* header that declares everything every translation unit in the
|
||||||
* frontend cares about.
|
* frontend cares about.
|
||||||
@@ -19,7 +17,6 @@
|
|||||||
/* version banner — printed by `ww -V` */
|
/* version banner — printed by `ww -V` */
|
||||||
#define WW_VERSION "0.0"
|
#define WW_VERSION "0.0"
|
||||||
|
|
||||||
/* short integer aliases, Plan 9 / Hare-flavoured */
|
|
||||||
typedef int8_t i8;
|
typedef int8_t i8;
|
||||||
typedef int16_t i16;
|
typedef int16_t i16;
|
||||||
typedef int32_t i32;
|
typedef int32_t i32;
|
||||||
@@ -29,7 +26,6 @@ typedef uint16_t u16;
|
|||||||
typedef uint32_t u32;
|
typedef uint32_t u32;
|
||||||
typedef uint64_t u64;
|
typedef uint64_t u64;
|
||||||
|
|
||||||
/* forward decls — concrete shapes appear in their phases. */
|
|
||||||
typedef struct Tok Tok;
|
typedef struct Tok Tok;
|
||||||
typedef struct Lex Lex;
|
typedef struct Lex Lex;
|
||||||
typedef struct Node Node;
|
typedef struct Node Node;
|
||||||
@@ -54,8 +50,6 @@ char *astrndup(Arena*, const char*, u64);
|
|||||||
char *aprintf(Arena*, const char*, ...);
|
char *aprintf(Arena*, const char*, ...);
|
||||||
void freearena(Arena*);
|
void freearena(Arena*);
|
||||||
|
|
||||||
/* err.c — diagnostics. Phase 0 has only fatal/warn; later phases add
|
|
||||||
* source-location-bearing variants. */
|
|
||||||
typedef struct Pos Pos;
|
typedef struct Pos Pos;
|
||||||
struct Pos {
|
struct Pos {
|
||||||
const char *file;
|
const char *file;
|
||||||
@@ -72,15 +66,12 @@ void fatal(const char*, ...) __attribute__((noreturn, format(printf, 1, 2)));
|
|||||||
void errorf(Pos, const char*, ...) __attribute__((format(printf, 2, 3)));
|
void errorf(Pos, const char*, ...) __attribute__((format(printf, 2, 3)));
|
||||||
void warnf(Pos, const char*, ...) __attribute__((format(printf, 2, 3)));
|
void warnf(Pos, const char*, ...) __attribute__((format(printf, 2, 3)));
|
||||||
|
|
||||||
/* tiny helpers */
|
|
||||||
#define nelem(a) ((sizeof(a) / sizeof((a)[0])))
|
#define nelem(a) ((sizeof(a) / sizeof((a)[0])))
|
||||||
|
|
||||||
/* ---- lexer (lex.c, tok.c) ----------------------------------------- */
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
/* zero is "no token" so memset-zero structs read sane */
|
/* zero is "no token" so memset-zero structs read sane */
|
||||||
TK_NONE = 0,
|
TK_NONE = 0,
|
||||||
|
|
||||||
/* trivial */
|
|
||||||
TK_EOF,
|
TK_EOF,
|
||||||
TK_ERR,
|
TK_ERR,
|
||||||
TK_IDENT,
|
TK_IDENT,
|
||||||
@@ -117,7 +108,6 @@ typedef enum {
|
|||||||
TK_CONST, /* const binding */
|
TK_CONST, /* const binding */
|
||||||
TK_UNDER, /* bare '_' discard */
|
TK_UNDER, /* bare '_' discard */
|
||||||
|
|
||||||
/* punct + operators */
|
|
||||||
TK_LPAREN, /* ( */
|
TK_LPAREN, /* ( */
|
||||||
TK_RPAREN, /* ) */
|
TK_RPAREN, /* ) */
|
||||||
TK_LBRACE, /* { */
|
TK_LBRACE, /* { */
|
||||||
@@ -233,7 +223,6 @@ const char *tokname(Tkind); /* canonical spelling, e.g. "fn", "+=" */
|
|||||||
void tokprint(FILE*, Tok); /* one line, "%s:%d:%d: %s %q" */
|
void tokprint(FILE*, Tok); /* one line, "%s:%d:%d: %s %q" */
|
||||||
Tkind kwlookup(const char *s, u64 n); /* TK_NONE if not a keyword */
|
Tkind kwlookup(const char *s, u64 n); /* TK_NONE if not a keyword */
|
||||||
|
|
||||||
/* ---- AST (ast.c, parse.c) ----------------------------------------- */
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
N_NONE = 0,
|
N_NONE = 0,
|
||||||
|
|
||||||
@@ -399,7 +388,6 @@ void parserinit(Parser*, Arena*, Lex*);
|
|||||||
Node *parsefile(Parser*);
|
Node *parsefile(Parser*);
|
||||||
Node *parseexpr_top(Parser*); /* for testing: parse one expression */
|
Node *parseexpr_top(Parser*); /* for testing: parse one expression */
|
||||||
|
|
||||||
/* ---- types (type.c) ----------------------------------------------- */
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
TY_NONE = 0,
|
TY_NONE = 0,
|
||||||
TY_VOID,
|
TY_VOID,
|
||||||
@@ -525,7 +513,6 @@ int type_isuntyped(Type *t);
|
|||||||
int type_assignable(Type *dst, Type *src);
|
int type_assignable(Type *dst, Type *src);
|
||||||
Type *type_default(Type *t); /* untyped → default concrete */
|
Type *type_default(Type *t); /* untyped → default concrete */
|
||||||
|
|
||||||
/* ---- symbols (sym.c) ---------------------------------------------- */
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SK_NONE = 0,
|
SK_NONE = 0,
|
||||||
SK_VAR,
|
SK_VAR,
|
||||||
@@ -580,7 +567,6 @@ Sym *scope_lookup_in_module(Scope*, const char *mod, const char *name);
|
|||||||
Sym *scope_lookup_prefer(Scope*, const char *mod, const char *name);
|
Sym *scope_lookup_prefer(Scope*, const char *mod, const char *name);
|
||||||
Sym *scope_lookup_type(Scope*, const char *mod, const char *name);
|
Sym *scope_lookup_type(Scope*, const char *mod, const char *name);
|
||||||
|
|
||||||
/* ---- checker (check.c) -------------------------------------------- */
|
|
||||||
typedef struct Checker Checker;
|
typedef struct Checker Checker;
|
||||||
struct Checker {
|
struct Checker {
|
||||||
Arena *a;
|
Arena *a;
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ww — the user-facing driver. Plan 9 cc(1) / Hare hare(1) analogue.
|
* ww — the user-facing driver. Plan 9 cc(1) / Hare hare(1) analogue.
|
||||||
*
|
*
|
||||||
* Pipeline:
|
|
||||||
* ww build foo.ww → w6c foo.ww > foo.s ; w6a foo.s > foo.o ;
|
|
||||||
* w6l -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
|
* Tool paths default to siblings of $0 (so a fresh build runs out of
|
||||||
* out/bin/), and can be overridden with WW_W6C / WW_W6A / WW_W6L.
|
* out/bin/), and can be overridden with WW_W6C / WW_W6A / WW_W6L.
|
||||||
*/
|
*/
|
||||||
@@ -35,7 +30,7 @@ static const char *usage =
|
|||||||
" lib/... every package under lib, recursively (test only)\n"
|
" lib/... every package under lib, recursively (test only)\n"
|
||||||
" . build the cwd's <basename>.ww\n";
|
" . build the cwd's <basename>.ww\n";
|
||||||
|
|
||||||
static char *self_dir; /* directory containing this binary */
|
static char *self_dir;
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
toolpath(const char *envvar, const char *name)
|
toolpath(const char *envvar, const char *name)
|
||||||
@@ -125,8 +120,7 @@ exec_package_tests(int argc, char **argv, const char *target,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set of imported module paths, kept on the heap. Used to break
|
/* Breaks cycles in `use` resolution. Linear because typical imports are
|
||||||
* cycles in `use` resolution. Linear because typical imports are
|
|
||||||
* a handful per build. */
|
* a handful per build. */
|
||||||
struct ImportSet {
|
struct ImportSet {
|
||||||
char **paths;
|
char **paths;
|
||||||
@@ -151,8 +145,7 @@ import_add(struct ImportSet *s, const char *path)
|
|||||||
s->paths[s->n++] = strdup(path);
|
s->paths[s->n++] = strdup(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Translate dots in an `import` name to slashes for path lookup.
|
/* `encoding.utf8` → `encoding/utf8`. Mirrors Hare hare(1)'s
|
||||||
* `encoding.utf8` → `encoding/utf8`. Mirrors Hare hare(1)'s
|
|
||||||
* use-path → fs-path mapping (ref/hare/hare/module/srcs.ha:78
|
* use-path → fs-path mapping (ref/hare/hare/module/srcs.ha:78
|
||||||
* builds the same shape via path::push per ident part). */
|
* builds the same shape via path::push per ident part). */
|
||||||
static void
|
static void
|
||||||
@@ -164,12 +157,11 @@ import_path_form(const char *name, char *out, size_t outsz)
|
|||||||
out[i] = '\0';
|
out[i] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* try <dir>/<path>/ as a directory (want_dir), else <dir>/<path>.ww as
|
/* Symmetric with wwstage locatein for byte-id driver output (rule 10).
|
||||||
* a file. Sets *is_dir on hit. Symmetric with wwstage locatein for
|
* The legacy <dir>/<name>/<name>.ww form was dropped in task #22 —
|
||||||
* byte-id driver output (rule 10). The legacy <dir>/<name>/<name>.ww
|
* directory-as-module enumeration replaces it, mirroring
|
||||||
* form was dropped in task #22 — directory-as-module enumeration
|
* ref/hare/hare/module/srcs.ha (Hare has no fallback matching
|
||||||
* replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no
|
* `foo/foo.ha`; a module IS the directory). */
|
||||||
* fallback matching `foo/foo.ha`; a module IS the directory). */
|
|
||||||
static int
|
static int
|
||||||
locate_import_in(const char *dir, const char *path_form, char *out,
|
locate_import_in(const char *dir, const char *path_form, char *out,
|
||||||
size_t outsz, int *is_dir, int want_dir)
|
size_t outsz, int *is_dir, int want_dir)
|
||||||
@@ -191,10 +183,7 @@ locate_import_in(const char *dir, const char *path_form, char *out,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Walk a colon-separated dirlist trying to resolve `path_form`. Returns
|
/* #98: "a module IS the directory" — a directory-package on ANY entry
|
||||||
* 1 on the first hit and writes the concrete path + dir/file marker.
|
|
||||||
*
|
|
||||||
* #98: "a module IS the directory" — a directory-package on ANY entry
|
|
||||||
* wins over a same-named sibling FILE on an EARLIER entry. The driver
|
* wins over a same-named sibling FILE on an EARLIER entry. The driver
|
||||||
* builds the searchpath srcd-first; a co-located `lib/<mod>/<x>_test.ww`
|
* builds the searchpath srcd-first; a co-located `lib/<mod>/<x>_test.ww`
|
||||||
* entry makes srcd = lib/<mod>, so a self-named `import <mod>` would
|
* entry makes srcd = lib/<mod>, so a self-named `import <mod>` would
|
||||||
@@ -231,10 +220,10 @@ locate_import(const char *dirs, const char *path_form, char *out,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* memcmp-based string compare for qsort. Byte-wise total order is
|
/* Byte-wise total order is locale-independent; rule-10 byte-id requires
|
||||||
* locale-independent; rule-10 byte-id requires the two stages sort
|
* the two stages sort the same way. strcmp diverges from Hare's memcmp
|
||||||
* the same way. (strcmp would work today but Hare-fidelity points
|
* (ref/hare/sort/cmp/cmp.ha:9); the order is identical for NUL-free
|
||||||
* at memcmp via ref/hare/sort/cmp/cmp.ha:9.) */
|
* filenames. */
|
||||||
static int
|
static int
|
||||||
strs_cmp(const void *a, const void *b)
|
strs_cmp(const void *a, const void *b)
|
||||||
{
|
{
|
||||||
@@ -266,11 +255,9 @@ file_has_line_test(const char *path)
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* enumerate_dir_ww — collect production *.ww paths in `dirpath`, excluding
|
/* A line-leading @test in a production source is diagnosed here and
|
||||||
* *_test.ww test sources, then sort byte-wise. A line-leading @test in
|
* returns -2. This is the sole directory-membership discovery path; the
|
||||||
* any other source is diagnosed here and returns -2. This is the sole
|
* owning seppkg retains the returned list. */
|
||||||
* directory-membership discovery path; the owning seppkg retains the
|
|
||||||
* returned list. */
|
|
||||||
static int
|
static int
|
||||||
enumerate_dir_ww(const char *dirpath, char ***out_files)
|
enumerate_dir_ww(const char *dirpath, char ***out_files)
|
||||||
{
|
{
|
||||||
@@ -310,9 +297,8 @@ enumerate_dir_ww(const char *dirpath, char ***out_files)
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ====================================================================
|
/* ww build — separate-compilation driver (task #46/c3).
|
||||||
* ww build — separate-compilation driver (task #46/c3).
|
*
|
||||||
* ====================================================================
|
|
||||||
* This is the SOLE build path (E3-C1 flip, task #87): the legacy
|
* This is the SOLE build path (E3-C1 flip, task #87): the legacy
|
||||||
* single-file amalgamator is gone. Each imported
|
* single-file amalgamator is gone. Each imported
|
||||||
* package's `.wwi` interface is materialized and every package is
|
* package's `.wwi` interface is materialized and every package is
|
||||||
@@ -357,7 +343,6 @@ struct sepgraph {
|
|||||||
int n;
|
int n;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Find a package by dotted path, or add it. Returns its index, -1 full. */
|
|
||||||
static int
|
static int
|
||||||
sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
|
sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
|
||||||
int is_dir)
|
int is_dir)
|
||||||
@@ -395,8 +380,7 @@ sep_graph_free(struct sepgraph *g)
|
|||||||
free(g);
|
free(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sanitize a package's dotted path into a scratch-file basename. Dots
|
/* Dots stay (legal in filenames); the root's empty path becomes "__root". */
|
||||||
* stay (legal in filenames); the root's empty path becomes "__root". */
|
|
||||||
static void
|
static void
|
||||||
sep_fname(const struct sepgraph *g, int pi, const char *scratch,
|
sep_fname(const struct sepgraph *g, int pi, const char *scratch,
|
||||||
const char *suffix, char *out, size_t outsz)
|
const char *suffix, char *out, size_t outsz)
|
||||||
@@ -451,9 +435,8 @@ sep_ident_continue(int c)
|
|||||||
return sep_ident_start(c) || (c >= '0' && c <= '9');
|
return sep_ident_start(c) || (c >= '0' && c <= '9');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Skip the whitespace and comments accepted before and within the leading
|
/* Deliberately only the loader's small header grammar for the leading
|
||||||
* package clause. This is deliberately only the loader's small header
|
* package clause, not a second compiler lexer. */
|
||||||
* grammar, not a second compiler lexer. */
|
|
||||||
static int
|
static int
|
||||||
sep_skip_space(const char *src, size_t n, size_t *off)
|
sep_skip_space(const char *src, size_t n, size_t *off)
|
||||||
{
|
{
|
||||||
@@ -481,7 +464,6 @@ sep_skip_space(const char *src, size_t n, size_t *off)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parse exactly the leading loader grammar `package ident;`. */
|
|
||||||
static int
|
static int
|
||||||
sep_package_clause(const char *src, size_t n, char *name, size_t namesz)
|
sep_package_clause(const char *src, size_t n, char *name, size_t namesz)
|
||||||
{
|
{
|
||||||
@@ -509,9 +491,7 @@ sep_package_clause(const char *src, size_t n, char *name, size_t namesz)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scan one source file's already-selected bytes for its leading package
|
/* A DIRECTORY import is a package boundary: add it as a direct dep of pkg
|
||||||
* clause (when it is an owned directory source) and top-level imports. A
|
|
||||||
* DIRECTORY import is a package boundary: add it as a direct dep of pkg
|
|
||||||
* `pi`. A FILE import is an intra-package split — fold its imports into
|
* `pi`. A FILE import is an intra-package split — fold its imports into
|
||||||
* `pi` (its bytes join pi's body at emit time). Collects package PATHS
|
* `pi` (its bytes join pi's body at emit time). Collects package PATHS
|
||||||
* rather than concatenating bytes the way the legacy amalgamator did
|
* rather than concatenating bytes the way the legacy amalgamator did
|
||||||
@@ -715,7 +695,6 @@ sep_topo_visit(struct sepgraph *g, int pi, int *order, int *no,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mark pi's transitive deps (excluding pi) in inset[]. */
|
|
||||||
static void
|
static void
|
||||||
sep_mark_deps(struct sepgraph *g, int pi, char *inset)
|
sep_mark_deps(struct sepgraph *g, int pi, char *inset)
|
||||||
{
|
{
|
||||||
@@ -881,9 +860,9 @@ archive_o(const char *objpath, const char *apath)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- -w workdir freshness ---------------------------------------------
|
/* -w workdir freshness: a `-w DIR` workdir is a caller-owned persistent
|
||||||
* A `-w DIR` workdir is a caller-owned persistent package-artifact tree
|
* package-artifact tree that replaces the fresh `.sepwork` scratch.
|
||||||
* that replaces the fresh `.sepwork` scratch. Staleness is pure content
|
* Staleness is pure content
|
||||||
* identity, never mtime: a package is reused only when its freshly
|
* identity, never mtime: a package is reused only when its freshly
|
||||||
* composed unit byte-equals the committed unit AND the tool copies
|
* composed unit byte-equals the committed unit AND the tool copies
|
||||||
* recorded in the dir byte-equal the live tools — every decision is
|
* recorded in the dir byte-equal the live tools — every decision is
|
||||||
@@ -997,7 +976,6 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
|||||||
else if (access("lib", 0) == 0) srcdir = "lib";
|
else if (access("lib", 0) == 0) srcdir = "lib";
|
||||||
else srcdir = libdir;
|
else srcdir = libdir;
|
||||||
}
|
}
|
||||||
/* search path: source-dir, then -I dirs, then srcdir. */
|
|
||||||
char srcd[1024];
|
char srcd[1024];
|
||||||
if (entry_is_dir) {
|
if (entry_is_dir) {
|
||||||
snprintf(srcd, sizeof srcd, "%s", src);
|
snprintf(srcd, sizeof srcd, "%s", src);
|
||||||
@@ -1110,7 +1088,6 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
|||||||
}
|
}
|
||||||
free(stack);
|
free(stack);
|
||||||
|
|
||||||
/* producer loop — dep-first, one `w6c -c -I` pass per package. */
|
|
||||||
for (int oi = 0; oi < norder; oi++) {
|
for (int oi = 0; oi < norder; oi++) {
|
||||||
int pi = order[oi];
|
int pi = order[oi];
|
||||||
char unitf[1024], wwi[1024], asmf[1024], obj[1024], apath[1024];
|
char unitf[1024], wwi[1024], asmf[1024], obj[1024], apath[1024];
|
||||||
@@ -1250,7 +1227,6 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
|||||||
char objs[8192] = {0};
|
char objs[8192] = {0};
|
||||||
for (int oi = norder - 1; oi >= 0; oi--) {
|
for (int oi = norder - 1; oi >= 0; oi--) {
|
||||||
char path[1024];
|
char path[1024];
|
||||||
/* root: positional `.o` (force-load); deps: `.a` (selective). */
|
|
||||||
sep_fname(g, order[oi], scratch,
|
sep_fname(g, order[oi], scratch,
|
||||||
order[oi] == root ? ".o" : ".a", path, sizeof path);
|
order[oi] == root ? ".o" : ".a", path, sizeof path);
|
||||||
size_t n = strlen(objs);
|
size_t n = strlen(objs);
|
||||||
@@ -1345,7 +1321,6 @@ search_path(const char *extra, char *buf, size_t bufsz)
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* basename_no_ext: last path segment with any trailing ".ww" stripped. */
|
|
||||||
static void
|
static void
|
||||||
basename_no_ext(const char *path, char *out, size_t outsz)
|
basename_no_ext(const char *path, char *out, size_t outsz)
|
||||||
{
|
{
|
||||||
@@ -1356,13 +1331,6 @@ basename_no_ext(const char *path, char *out, size_t outsz)
|
|||||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* resolve_module: turn a name into a concrete entry path.
|
|
||||||
* foo.ww → use as-is if it exists
|
|
||||||
* <existing dir> → returns the dir path (caller dir-enumerates)
|
|
||||||
* . → cwd as a directory
|
|
||||||
* foo (bare) → walk cwd:incs:WW_LIB; first hit is dir or file.
|
|
||||||
* Sets *is_dir on hit. Dir resolution drives directory-as-module
|
|
||||||
* enumeration in build_one_sep. */
|
|
||||||
static int
|
static int
|
||||||
resolve_module(const char *name, const char *incs, char *out, size_t outsz,
|
resolve_module(const char *name, const char *incs, char *out, size_t outsz,
|
||||||
int *is_dir)
|
int *is_dir)
|
||||||
@@ -1387,12 +1355,11 @@ resolve_module(const char *name, const char *incs, char *out, size_t outsz,
|
|||||||
return locate_import(sp, path_form, out, outsz, is_dir);
|
return locate_import(sp, path_form, out, outsz, is_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parse the standard -I/-L/-l/-o flags into incs/libdirs/libs/outpath. The
|
/* Returns the index past the last arg consumed for positionals (so callers
|
||||||
* first non-flag positional becomes *src_out. Returns the index past the last
|
* can pick up trailing args), or -1 if a flag is missing its argument
|
||||||
* arg consumed for positionals (so callers can pick up trailing args), or -1
|
* (diagnostic already emitted). `cmd` names the subcommand for the
|
||||||
* if a flag is missing its argument (diagnostic already emitted). `cmd` names
|
* diagnostic, byte-identical to the wwstage twin's per-subcommand wording
|
||||||
* the subcommand for the diagnostic, byte-identical to the wwstage twin's
|
* (selfhost/cmd/ww/main.ww dobuild/dorun). */
|
||||||
* per-subcommand wording (selfhost/cmd/ww/main.ww dobuild/dorun). */
|
|
||||||
static int
|
static int
|
||||||
parse_build_flags(const char *cmd, int argc, char **argv,
|
parse_build_flags(const char *cmd, int argc, char **argv,
|
||||||
char *incs, size_t incsz,
|
char *incs, size_t incsz,
|
||||||
@@ -1504,7 +1471,7 @@ do_build(int argc, char **argv)
|
|||||||
outflag, sizeof outflag, workdir, sizeof workdir,
|
outflag, sizeof outflag, workdir, sizeof workdir,
|
||||||
&src, &emit_asm) < 0)
|
&src, &emit_asm) < 0)
|
||||||
return 2;
|
return 2;
|
||||||
if (src == NULL) src = "."; /* default: build cwd */
|
if (src == NULL) src = ".";
|
||||||
char resolved[1024];
|
char resolved[1024];
|
||||||
int is_dir = 0;
|
int is_dir = 0;
|
||||||
if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) {
|
if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) {
|
||||||
@@ -1568,7 +1535,6 @@ do_run(int argc, char **argv)
|
|||||||
fputs("ww: cannot remove temporary directory\n", stderr);
|
fputs("ww: cannot remove temporary directory\n", stderr);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
/* exec the built binary with any trailing argv as its argv. */
|
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
if (pid < 0) {
|
if (pid < 0) {
|
||||||
perror("ww: fork");
|
perror("ww: fork");
|
||||||
@@ -1823,7 +1789,6 @@ do_test(int argc, char **argv)
|
|||||||
"ww test: package options need a directory\n");
|
"ww test: package options need a directory\n");
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
/* single .ww file — build, then run unless -c (compile-only). */
|
|
||||||
char tmpdir[1024] = {0}, tmp[1024];
|
char tmpdir[1024] = {0}, tmp[1024];
|
||||||
const char *outp;
|
const char *outp;
|
||||||
int owntmp = !outstem[0] && !workdir[0];
|
int owntmp = !outstem[0] && !workdir[0];
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* wwdump — deterministic dump tool for ww source.
|
* Diff anchor for self-host: the C-side libwcc and the future ww-side
|
||||||
*
|
* frontend must produce the same dump for the same input, byte-for-byte
|
||||||
* Reads a .ww file and writes either a token stream or an AST
|
* stable across runs.
|
||||||
* s-expression to stdout, byte-for-byte stable across runs. It is the
|
|
||||||
* diff anchor for self-host: the C-side libwcc and the future ww-side
|
|
||||||
* frontend must produce the same dump for the same input.
|
|
||||||
*
|
*
|
||||||
* wwdump -t file.ww tokens, one per line: "<file>:<l>:<c> <kind> [val]"
|
* wwdump -t file.ww tokens, one per line: "<file>:<l>:<c> <kind> [val]"
|
||||||
* wwdump -a file.ww AST as s-expr, one node per line
|
* wwdump -a file.ww AST as s-expr, one node per line
|
||||||
@@ -70,7 +67,7 @@ dump_ast(const char *src, char *buf, u64 len, FILE *out)
|
|||||||
int
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int mode = 't'; /* tokens by default */
|
int mode = 't';
|
||||||
const char *src = NULL;
|
const char *src = NULL;
|
||||||
const char *out = NULL;
|
const char *out = NULL;
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
|
|||||||
@@ -400,7 +400,6 @@ fn pkgdedup(ss: []str) []str = {
|
|||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Decimal value, or -1 on empty, non-digit, or overflow past max.
|
|
||||||
fn pkgparsedec(s: str, max: i64) i64 = {
|
fn pkgparsedec(s: str, max: i64) i64 = {
|
||||||
if (s.len == 0) { return -1i64; };
|
if (s.len == 0) { return -1i64; };
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
// selfhost/cmd/w6a/asm.ww — port of cmd/w6a/asm.c.
|
// Port of cmd/w6a/asm.c. Encoding subset matches what w6c emits — see
|
||||||
//
|
// cmd/w6a/asm.c for the authoritative list.
|
||||||
// Encode the parsed aprog list into amd64 machine bytes, appending to
|
|
||||||
// asm_.text. Relocations for CALL/branch targets that resolve to
|
|
||||||
// externals are queued in asm_.relocs.
|
|
||||||
//
|
|
||||||
// Encoding subset matches what w6c emits — see cmd/w6a/asm.c for the
|
|
||||||
// authoritative list. Helpers (rcode/rhi/modrm/emitrex etc.) are
|
|
||||||
// fully ported; encode itself is still a stub pending the full
|
|
||||||
// switch over A_*.
|
|
||||||
|
|
||||||
package w6a;
|
package w6a;
|
||||||
|
|
||||||
@@ -16,8 +8,6 @@ import rt;
|
|||||||
import opcodes;
|
import opcodes;
|
||||||
import strings;
|
import strings;
|
||||||
|
|
||||||
// ---- text buffer growth ------------------------------------------------
|
|
||||||
|
|
||||||
export fn emitbyte(a: *asm_, b: u8) void = {
|
export fn emitbyte(a: *asm_, b: u8) void = {
|
||||||
if (a.textlen + 1u64 > a.textcap) {
|
if (a.textlen + 1u64 > a.textcap) {
|
||||||
let nc: u64 = a.textcap;
|
let nc: u64 = a.textcap;
|
||||||
@@ -45,16 +35,14 @@ export fn addreloc(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = {
|
|||||||
a.relocs = r;
|
a.relocs = r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Record a relocation that lives in the .data section. Used by
|
// DATAR patches a 64-bit .data slot with a symbol's runtime VA;
|
||||||
// DATAR to patch a 64-bit slot with a symbol's runtime VA. obj.ww
|
// obj.ww separates section=1 relocs into .rela.data when emitting
|
||||||
// separates these into .rela.data when emitting the .o.
|
// the .o.
|
||||||
export fn addrelocdata(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = {
|
export fn addrelocdata(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = {
|
||||||
let r: *areloc = alloc(areloc { off = off, section = 1, kind = kind, asy = s, addend = add, rnext = a.relocs })!;
|
let r: *areloc = alloc(areloc { off = off, section = 1, kind = kind, asy = s, addend = add, rnext = a.relocs })!;
|
||||||
a.relocs = r;
|
a.relocs = r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Append one byte to the writable .data buffer. Mirrors emitbyte
|
|
||||||
// but targets a.data instead of a.text.
|
|
||||||
export fn emitdatabyte(a: *asm_, b: u8) void = {
|
export fn emitdatabyte(a: *asm_, b: u8) void = {
|
||||||
if (a.datalen + 1u64 > a.datacap) {
|
if (a.datalen + 1u64 > a.datacap) {
|
||||||
let nc: u64 = a.datacap;
|
let nc: u64 = a.datacap;
|
||||||
@@ -70,8 +58,6 @@ export fn emitdatabyte(a: *asm_, b: u8) void = {
|
|||||||
a.datalen += 1u64;
|
a.datalen += 1u64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- register codes ----------------------------------------------------
|
|
||||||
|
|
||||||
// Low 3 bits of register encoding.
|
// Low 3 bits of register encoding.
|
||||||
fn rcode(r: i32) i32 = {
|
fn rcode(r: i32) i32 = {
|
||||||
if (r == D_AX) { return 0; }; if (r == D_CX) { return 1; };
|
if (r == D_AX) { return 0; }; if (r == D_CX) { return 1; };
|
||||||
@@ -105,7 +91,6 @@ fn isxmm(r: i32) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ModR/M byte builder.
|
|
||||||
fn modrmbyte(mod: i32, reg: i32, rm: i32) u8 = {
|
fn modrmbyte(mod: i32, reg: i32, rm: i32) u8 = {
|
||||||
return (((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7)): u8;
|
return (((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7)): u8;
|
||||||
};
|
};
|
||||||
@@ -210,8 +195,6 @@ fn sserrw(a: *asm_, prefix: u8, op2: u8, regop: i32, rmop: i32) void = {
|
|||||||
emitbyte(a, modrmbyte(3, rcode(regop), rcode(rmop)));
|
emitbyte(a, modrmbyte(3, rcode(regop), rcode(rmop)));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- label resolution / fixups ----------------------------------------
|
|
||||||
|
|
||||||
fn resolvelabel(a: *asm_, name: str) u64 = {
|
fn resolvelabel(a: *asm_, name: str) u64 = {
|
||||||
let s: *asym = a.syms;
|
let s: *asym = a.syms;
|
||||||
for (s != nil) {
|
for (s != nil) {
|
||||||
@@ -230,8 +213,6 @@ fn labeldefined(a: *asm_, name: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fixup helper -----------------------------------------------------
|
|
||||||
|
|
||||||
fn addfixup(a: *asm_, off: u64, label: str) void = {
|
fn addfixup(a: *asm_, off: u64, label: str) void = {
|
||||||
let f: *afixup = alloc(afixup { off = off, label = label, fnext = a.fixups })!;
|
let f: *afixup = alloc(afixup { off = off, label = label, fnext = a.fixups })!;
|
||||||
a.fixups = f;
|
a.fixups = f;
|
||||||
@@ -242,15 +223,9 @@ fn isgpr(t: i32) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// `intern` lives in parse.ww — flat-scope concat lets us call it
|
|
||||||
// directly without an @symbol declaration here.
|
|
||||||
|
|
||||||
// ---- encode ----------------------------------------------------------
|
|
||||||
|
|
||||||
export fn encode(a: *asm_) i32 = {
|
export fn encode(a: *asm_) i32 = {
|
||||||
let p: *aprog = a.head;
|
let p: *aprog = a.head;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
// Define any pending label at the current PC.
|
|
||||||
if (p.label.len > 0) {
|
if (p.label.len > 0) {
|
||||||
let s: *asym = intern(a, p.label);
|
let s: *asym = intern(a, p.label);
|
||||||
s.defined = 1;
|
s.defined = 1;
|
||||||
@@ -281,10 +256,9 @@ export fn encode(a: *asm_) i32 = {
|
|||||||
p = p.link; continue;
|
p = p.link; continue;
|
||||||
};
|
};
|
||||||
if (op == A_DATAW) {
|
if (op == A_DATAW) {
|
||||||
// Writable variant: bytes go into .data instead of
|
// obj.ww emits the .data section conditionally on
|
||||||
// .text. obj.ww emits the extra section conditionally
|
// datalen > 0 so .o output stays byte-identical for
|
||||||
// on datalen > 0 so .o output stays byte-identical
|
// inputs that don't use DATAW.
|
||||||
// for inputs that don't use DATAW.
|
|
||||||
let s: *asym = intern(a, p.to.asym);
|
let s: *asym = intern(a, p.to.asym);
|
||||||
s.defined = 1;
|
s.defined = 1;
|
||||||
s.isdata = 1;
|
s.isdata = 1;
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
// selfhost/cmd/w6a/lex.ww — port of cmd/w6a/lex.c.
|
// Port of cmd/w6a/lex.c.
|
||||||
//
|
|
||||||
// Character-level helpers for w6a's line-oriented parser. The parser
|
|
||||||
// itself is in parse.ww; here we keep tokenisers for identifiers and
|
|
||||||
// numbers so parse.ww stays focused on syntax.
|
|
||||||
|
|
||||||
package w6a;
|
package w6a;
|
||||||
|
|
||||||
@@ -20,10 +16,6 @@ export fn isidcont(c: i32) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// parsenum — read a leading [+-]?[0x|0X|0]?digits from p[0..n-1].
|
|
||||||
// Returns (value, consumed). Stops at first non-digit.
|
|
||||||
// Plain Plan 9-style: $123 / $0x1f / $-7. Decimal default; 0x prefix
|
|
||||||
// for hex; 0 prefix for octal when followed by a digit (else just 0).
|
|
||||||
export fn parsenum(p: *u8, n: u64) (i64, u64) = {
|
export fn parsenum(p: *u8, n: u64) (i64, u64) = {
|
||||||
// strtoll(s, end, 0) semantics, matching the C twin cmd/w6a/lex.c:30:
|
// strtoll(s, end, 0) semantics, matching the C twin cmd/w6a/lex.c:30:
|
||||||
// skip leading whitespace, optional sign, base-0 prefix detection
|
// skip leading whitespace, optional sign, base-0 prefix detection
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
// selfhost/cmd/w6a/main.ww — port of cmd/w6a/main.c.
|
// Port of cmd/w6a/main.c.
|
||||||
//
|
|
||||||
// w6a = amd64 assembler. Read .s, parse, encode, emit ELF .o.
|
|
||||||
//
|
//
|
||||||
// w6a_ww -o file.o file.s
|
// w6a_ww -o file.o file.s
|
||||||
|
|
||||||
@@ -33,8 +31,7 @@ fn cstrlen(p: *u8) u64 = {
|
|||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
|
||||||
// pathstr — view a NUL-terminated *u8 as a str. Bridges argv-style
|
// Bridges argv-style callers to lib/os entrypoints (str post-task-#23).
|
||||||
// callers to lib/os entrypoints (str post-task-#23).
|
|
||||||
fn pathstr(p: *u8) str = {
|
fn pathstr(p: *u8) str = {
|
||||||
let r: str;
|
let r: str;
|
||||||
r.ptr = p;
|
r.ptr = p;
|
||||||
@@ -42,7 +39,6 @@ fn pathstr(p: *u8) str = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Slurp the whole file into a fresh buffer.
|
|
||||||
fn slurp(path: *u8) (*u8, u64) = {
|
fn slurp(path: *u8) (*u8, u64) = {
|
||||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||||
if (fd < 0) { return nil, 0u64; };
|
if (fd < 0) { return nil, 0u64; };
|
||||||
@@ -122,7 +118,6 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
|||||||
if (parse(&s) != 0) { return 1; };
|
if (parse(&s) != 0) { return 1; };
|
||||||
if (encode(&s) != 0) { return 1; };
|
if (encode(&s) != 0) { return 1; };
|
||||||
|
|
||||||
// Open output for write.
|
|
||||||
let fd: i32 = os.open(pathstr(out), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
let fd: i32 = os.open(pathstr(out), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
os.write(2, "w6a: cannot open output\n".ptr, 23u64);
|
os.write(2, "w6a: cannot open output\n".ptr, 23u64);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// selfhost/cmd/w6a/obj.ww — port of cmd/w6a/obj.c.
|
// Port of cmd/w6a/obj.c.
|
||||||
//
|
//
|
||||||
// Emit a tiny ELF64 relocatable object. Layout (in file order):
|
// Emit a tiny ELF64 relocatable object. Layout (in file order):
|
||||||
// [0] ELF header
|
// [0] ELF header
|
||||||
@@ -36,7 +36,6 @@ fn wrdrop(fd: i32, p: *u8, n: u64) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- ELF constants ----------------------------------------------------
|
|
||||||
def ELFCLASS64: u8 = 2u8;
|
def ELFCLASS64: u8 = 2u8;
|
||||||
def ELFDATA2LSB: u8 = 1u8;
|
def ELFDATA2LSB: u8 = 1u8;
|
||||||
def EV_CURRENT_W: u32 = 1u32;
|
def EV_CURRENT_W: u32 = 1u32;
|
||||||
@@ -59,14 +58,11 @@ def STT_NOTYPE: u8 = 0u8;
|
|||||||
def STT_OBJECT: u8 = 1u8;
|
def STT_OBJECT: u8 = 1u8;
|
||||||
def STT_FUNC: u8 = 2u8;
|
def STT_FUNC: u8 = 2u8;
|
||||||
|
|
||||||
// Sizes of fixed structures.
|
|
||||||
def EHDR_SZ: u64 = 64u64;
|
def EHDR_SZ: u64 = 64u64;
|
||||||
def SHDR_SZ: u64 = 64u64;
|
def SHDR_SZ: u64 = 64u64;
|
||||||
def SYM_SZ: u64 = 24u64;
|
def SYM_SZ: u64 = 24u64;
|
||||||
def RELA_SZ: u64 = 24u64;
|
def RELA_SZ: u64 = 24u64;
|
||||||
|
|
||||||
// ---- LE byte writers (own the bytes — write into a *u8 + offset) ----
|
|
||||||
|
|
||||||
fn wru8(p: *u8, off: u64, v: u8) void = { p[off] = v; };
|
fn wru8(p: *u8, off: u64, v: u8) void = { p[off] = v; };
|
||||||
fn wru16(p: *u8, off: u64, v: u16) void = {
|
fn wru16(p: *u8, off: u64, v: u16) void = {
|
||||||
p[off] = (v & 255u16): u8;
|
p[off] = (v & 255u16): u8;
|
||||||
@@ -83,8 +79,6 @@ fn wru64(p: *u8, off: u64, v: u64) void = {
|
|||||||
wru32(p, off + 4u64, ((v >> 32u64) & 4294967295u64): u32);
|
wru32(p, off + 4u64, ((v >> 32u64) & 4294967295u64): u32);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- growable byte buffer ---------------------------------------------
|
|
||||||
|
|
||||||
type buf = struct {
|
type buf = struct {
|
||||||
p: *u8,
|
p: *u8,
|
||||||
n: u64,
|
n: u64,
|
||||||
@@ -128,8 +122,6 @@ fn bufputcstr(b: *buf, s: str) u32 = {
|
|||||||
return off;
|
return off;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- emitelf ---------------------------------------------------------
|
|
||||||
|
|
||||||
export fn emitelf(a: *asm_, fd: i32) i32 = {
|
export fn emitelf(a: *asm_, fd: i32) i32 = {
|
||||||
let shstr: buf; bufinit(&shstr);
|
let shstr: buf; bufinit(&shstr);
|
||||||
let str_: buf; bufinit(&str_);
|
let str_: buf; bufinit(&str_);
|
||||||
@@ -174,9 +166,9 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
|
|||||||
let SH_STRTAB: u16 = SH_SYMTAB + 1u16;
|
let SH_STRTAB: u16 = SH_SYMTAB + 1u16;
|
||||||
let SH_SHSTR: u16 = SH_STRTAB + 1u16;
|
let SH_SHSTR: u16 = SH_STRTAB + 1u16;
|
||||||
|
|
||||||
// Section name offsets. Append .data / .rela.data only when
|
// Append .data / .rela.data names only when used so the
|
||||||
// used so the .shstrtab buffer stays byte-identical for the
|
// .shstrtab buffer stays byte-identical for the no-DATAW case
|
||||||
// no-DATAW case (test 991 byte-diff invariant).
|
// (test 991 byte-diff invariant).
|
||||||
let shntext: u32 = bufputcstr(&shstr, ".text");
|
let shntext: u32 = bufputcstr(&shstr, ".text");
|
||||||
let shnrela: u32 = bufputcstr(&shstr, ".rela.text");
|
let shnrela: u32 = bufputcstr(&shstr, ".rela.text");
|
||||||
let shndata: u32 = 0u32;
|
let shndata: u32 = 0u32;
|
||||||
@@ -195,7 +187,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
|
|||||||
for (zi < 24) { zsym[zi] = 0u8; zi += 1; };
|
for (zi < 24) { zsym[zi] = 0u8; zi += 1; };
|
||||||
bufputb(&sym, zsym.ptr, 24u64);
|
bufputb(&sym, zsym.ptr, 24u64);
|
||||||
|
|
||||||
// Build symbols.
|
|
||||||
let idx: i32 = 1;
|
let idx: i32 = 1;
|
||||||
let s: *asym = a.syms;
|
let s: *asym = a.syms;
|
||||||
for (s != nil) {
|
for (s != nil) {
|
||||||
@@ -223,7 +214,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
|
|||||||
s = s.snext;
|
s = s.snext;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build relocations — split into text vs data buffers.
|
|
||||||
let r: *areloc = a.relocs;
|
let r: *areloc = a.relocs;
|
||||||
for (r != nil) {
|
for (r != nil) {
|
||||||
let entry: [24]u8;
|
let entry: [24]u8;
|
||||||
@@ -239,7 +229,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
|
|||||||
r = r.rnext;
|
r = r.rnext;
|
||||||
};
|
};
|
||||||
|
|
||||||
// File offsets.
|
|
||||||
let off: u64 = EHDR_SZ;
|
let off: u64 = EHDR_SZ;
|
||||||
let offtext: u64 = off; off = off + a.textlen;
|
let offtext: u64 = off; off = off + a.textlen;
|
||||||
let offrela: u64 = off; off = off + rela.n;
|
let offrela: u64 = off; off = off + rela.n;
|
||||||
@@ -258,7 +247,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
|
|||||||
else { NSECT = 7u16; };
|
else { NSECT = 7u16; };
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Ehdr ----
|
|
||||||
let eh: [64]u8;
|
let eh: [64]u8;
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < 64) { eh[i] = 0u8; i += 1; };
|
for (i < 64) { eh[i] = 0u8; i += 1; };
|
||||||
@@ -321,13 +309,11 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
|
|||||||
written += 1u64;
|
written += 1u64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Section header table — 6 headers of 64 bytes each = 384 bytes.
|
|
||||||
let shbuf: [64]u8;
|
let shbuf: [64]u8;
|
||||||
// SHT_NULL
|
// SHT_NULL
|
||||||
let sn: i32 = 0;
|
let sn: i32 = 0;
|
||||||
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
||||||
wrdrop(fd, shbuf.ptr, 64u64);
|
wrdrop(fd, shbuf.ptr, 64u64);
|
||||||
// .text
|
|
||||||
sn = 0;
|
sn = 0;
|
||||||
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
||||||
wru32(shbuf.ptr, 0u64, shntext);
|
wru32(shbuf.ptr, 0u64, shntext);
|
||||||
@@ -337,7 +323,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
|
|||||||
wru64(shbuf.ptr, 32u64, a.textlen);
|
wru64(shbuf.ptr, 32u64, a.textlen);
|
||||||
wru64(shbuf.ptr, 48u64, 1u64); // sh_addralign
|
wru64(shbuf.ptr, 48u64, 1u64); // sh_addralign
|
||||||
wrdrop(fd, shbuf.ptr, 64u64);
|
wrdrop(fd, shbuf.ptr, 64u64);
|
||||||
// .rela.text
|
|
||||||
sn = 0;
|
sn = 0;
|
||||||
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
||||||
wru32(shbuf.ptr, 0u64, shnrela);
|
wru32(shbuf.ptr, 0u64, shnrela);
|
||||||
@@ -351,7 +336,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
|
|||||||
wru64(shbuf.ptr, 56u64, RELA_SZ);
|
wru64(shbuf.ptr, 56u64, RELA_SZ);
|
||||||
wrdrop(fd, shbuf.ptr, 64u64);
|
wrdrop(fd, shbuf.ptr, 64u64);
|
||||||
if (hasdata) {
|
if (hasdata) {
|
||||||
// .data
|
|
||||||
sn = 0;
|
sn = 0;
|
||||||
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
||||||
wru32(shbuf.ptr, 0u64, shndata);
|
wru32(shbuf.ptr, 0u64, shndata);
|
||||||
@@ -362,7 +346,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
|
|||||||
wru64(shbuf.ptr, 48u64, 8u64); // sh_addralign
|
wru64(shbuf.ptr, 48u64, 8u64); // sh_addralign
|
||||||
wrdrop(fd, shbuf.ptr, 64u64);
|
wrdrop(fd, shbuf.ptr, 64u64);
|
||||||
if (hasdatarelocs) {
|
if (hasdatarelocs) {
|
||||||
// .rela.data
|
|
||||||
sn = 0;
|
sn = 0;
|
||||||
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
||||||
wru32(shbuf.ptr, 0u64, shnrelad);
|
wru32(shbuf.ptr, 0u64, shnrelad);
|
||||||
@@ -377,7 +360,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
|
|||||||
wrdrop(fd, shbuf.ptr, 64u64);
|
wrdrop(fd, shbuf.ptr, 64u64);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// .symtab
|
|
||||||
sn = 0;
|
sn = 0;
|
||||||
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
||||||
wru32(shbuf.ptr, 0u64, shnsymtab);
|
wru32(shbuf.ptr, 0u64, shnsymtab);
|
||||||
@@ -389,7 +371,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
|
|||||||
wru64(shbuf.ptr, 48u64, 8u64);
|
wru64(shbuf.ptr, 48u64, 8u64);
|
||||||
wru64(shbuf.ptr, 56u64, SYM_SZ);
|
wru64(shbuf.ptr, 56u64, SYM_SZ);
|
||||||
wrdrop(fd, shbuf.ptr, 64u64);
|
wrdrop(fd, shbuf.ptr, 64u64);
|
||||||
// .strtab
|
|
||||||
sn = 0;
|
sn = 0;
|
||||||
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
||||||
wru32(shbuf.ptr, 0u64, shnstrtab);
|
wru32(shbuf.ptr, 0u64, shnstrtab);
|
||||||
@@ -398,7 +379,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
|
|||||||
wru64(shbuf.ptr, 32u64, str_.n);
|
wru64(shbuf.ptr, 32u64, str_.n);
|
||||||
wru64(shbuf.ptr, 48u64, 1u64);
|
wru64(shbuf.ptr, 48u64, 1u64);
|
||||||
wrdrop(fd, shbuf.ptr, 64u64);
|
wrdrop(fd, shbuf.ptr, 64u64);
|
||||||
// .shstrtab
|
|
||||||
sn = 0;
|
sn = 0;
|
||||||
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
||||||
wru32(shbuf.ptr, 0u64, shnshstrtab);
|
wru32(shbuf.ptr, 0u64, shnshstrtab);
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
// selfhost/cmd/w6a/opcodes.ww — types + constants shared across the
|
// Mirrors cmd/w6a/a.h and cmd/w6c/6.out.h.
|
||||||
// w6a port. Mirrors cmd/w6a/a.h and cmd/w6c/6.out.h.
|
|
||||||
|
|
||||||
package w6a;
|
package w6a;
|
||||||
|
|
||||||
// ---- registers + operand kinds (from 6.out.h) -------------------------
|
// Registers + operand kinds must stay numerically aligned with the
|
||||||
// These must stay numerically aligned with the C enum so that ww-cgen
|
// 6.out.h C enum so that ww-cgen output (which reads them via
|
||||||
// output (which reads them via `D_AX(SB)` etc.) lands on the same
|
// `D_AX(SB)` etc.) lands on the same integers when read by ww-w6a.
|
||||||
// integers when read by ww-w6a.
|
|
||||||
def D_NONE: i32 = 0;
|
def D_NONE: i32 = 0;
|
||||||
|
|
||||||
def D_AX: i32 = 1;
|
def D_AX: i32 = 1;
|
||||||
@@ -52,7 +50,6 @@ def D_BRANCH: i32 = 37;
|
|||||||
def D_EXTERN: i32 = 38;
|
def D_EXTERN: i32 = 38;
|
||||||
def D_INDIR: i32 = 39;
|
def D_INDIR: i32 = 39;
|
||||||
|
|
||||||
// ---- opcodes ----------------------------------------------------------
|
|
||||||
def A_NOP: i32 = 0;
|
def A_NOP: i32 = 0;
|
||||||
def A_TEXT: i32 = 1;
|
def A_TEXT: i32 = 1;
|
||||||
def A_DATA: i32 = 2;
|
def A_DATA: i32 = 2;
|
||||||
@@ -145,8 +142,6 @@ def A_DATAR: i32 = 61;
|
|||||||
// with DIVQ.
|
// with DIVQ.
|
||||||
def A_CQO: i32 = 66;
|
def A_CQO: i32 = 66;
|
||||||
|
|
||||||
// ---- structs (mirror cmd/w6a/a.h) --------------------------------------
|
|
||||||
|
|
||||||
type aoperand = struct {
|
type aoperand = struct {
|
||||||
atype: i32, // D_NONE / D_AX..D_R15 / D_CONST / D_INDIR / D_EXTERN / D_BRANCH
|
atype: i32, // D_NONE / D_AX..D_R15 / D_CONST / D_INDIR / D_EXTERN / D_BRANCH
|
||||||
reg: i32,
|
reg: i32,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// selfhost/cmd/w6a/parse.ww — port of cmd/w6a/parse.c.
|
// Port of cmd/w6a/parse.c.
|
||||||
//
|
//
|
||||||
// Line-oriented parser for the asm subset emitted by w6c.
|
// Line-oriented parser for the asm subset emitted by w6c.
|
||||||
// Grammar:
|
// Grammar:
|
||||||
@@ -28,8 +28,7 @@ fn streqlit(p: *u8, n: u64, lit: str) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// opcodelookup — name (length-bounded *u8) → A_*. Returns 0 (A_NOP)
|
// Returns 0 (A_NOP) if not found.
|
||||||
// if not found.
|
|
||||||
fn opcodelookup(p: *u8, n: u64) i32 = {
|
fn opcodelookup(p: *u8, n: u64) i32 = {
|
||||||
if (streqlit(p, n, "MOVQ")) { return A_MOVQ; };
|
if (streqlit(p, n, "MOVQ")) { return A_MOVQ; };
|
||||||
if (streqlit(p, n, "MOVL")) { return A_MOVL; };
|
if (streqlit(p, n, "MOVL")) { return A_MOVL; };
|
||||||
@@ -100,7 +99,7 @@ fn opcodelookup(p: *u8, n: u64) i32 = {
|
|||||||
return A_NOP;
|
return A_NOP;
|
||||||
};
|
};
|
||||||
|
|
||||||
// reglookup — name → D_*. Returns D_NONE if not found.
|
// Returns D_NONE if not found.
|
||||||
fn reglookup(p: *u8, n: u64) i32 = {
|
fn reglookup(p: *u8, n: u64) i32 = {
|
||||||
if (streqlit(p, n, "AX")) { return D_AX; };
|
if (streqlit(p, n, "AX")) { return D_AX; };
|
||||||
if (streqlit(p, n, "BX")) { return D_BX; };
|
if (streqlit(p, n, "BX")) { return D_BX; };
|
||||||
@@ -180,7 +179,6 @@ fn perr(a: *asm_, msg: str) void = {
|
|||||||
a.errs += 1;
|
a.errs += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// dupstr — copy n bytes from p into a fresh heap str.
|
|
||||||
fn dupstr(p: *u8, n: u64) str = {
|
fn dupstr(p: *u8, n: u64) str = {
|
||||||
let view: str;
|
let view: str;
|
||||||
view.ptr = p;
|
view.ptr = p;
|
||||||
@@ -188,8 +186,6 @@ fn dupstr(p: *u8, n: u64) str = {
|
|||||||
return strings.dup(view);
|
return strings.dup(view);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- line iteration & whitespace --------------------------------------
|
|
||||||
|
|
||||||
// Read next line into a fresh heap buffer; returns (ptr, len) or (nil,0)
|
// Read next line into a fresh heap buffer; returns (ptr, len) or (nil,0)
|
||||||
// at EOF. Advances a.pos past the newline.
|
// at EOF. Advances a.pos past the newline.
|
||||||
fn nextline(a: *asm_) (*u8, u64) = {
|
fn nextline(a: *asm_) (*u8, u64) = {
|
||||||
@@ -204,7 +200,7 @@ fn nextline(a: *asm_) (*u8, u64) = {
|
|||||||
let i: u64 = 0u64;
|
let i: u64 = 0u64;
|
||||||
for (i < n) { buf[i] = a.src[start + i]; i += 1u64; };
|
for (i < n) { buf[i] = a.src[start + i]; i += 1u64; };
|
||||||
buf[n] = 0u8;
|
buf[n] = 0u8;
|
||||||
a.pos += 1u64; // skip newline
|
a.pos += 1u64;
|
||||||
return buf.ptr, n;
|
return buf.ptr, n;
|
||||||
};
|
};
|
||||||
// EOF without trailing newline
|
// EOF without trailing newline
|
||||||
@@ -226,7 +222,6 @@ fn skipws(p: *u8, off: u64, n: u64) u64 = {
|
|||||||
return i;
|
return i;
|
||||||
};
|
};
|
||||||
|
|
||||||
// parseoperand — parse one operand from p[off..n), populate out.
|
|
||||||
// Returns new offset (clamped to n on error).
|
// Returns new offset (clamped to n on error).
|
||||||
fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = {
|
fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = {
|
||||||
let off: u64 = skipws(p, offin, n);
|
let off: u64 = skipws(p, offin, n);
|
||||||
@@ -239,7 +234,6 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = {
|
|||||||
if (off >= n) { return off; };
|
if (off >= n) { return off; };
|
||||||
let c0: u8 = p[off];
|
let c0: u8 = p[off];
|
||||||
|
|
||||||
// $NUM
|
|
||||||
if (c0 == '$') {
|
if (c0 == '$') {
|
||||||
off += 1u64;
|
off += 1u64;
|
||||||
let v: i64;
|
let v: i64;
|
||||||
@@ -250,7 +244,6 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = {
|
|||||||
return off + used;
|
return off + used;
|
||||||
};
|
};
|
||||||
|
|
||||||
// (REG)
|
|
||||||
if (c0 == '(') {
|
if (c0 == '(') {
|
||||||
off += 1u64;
|
off += 1u64;
|
||||||
let rstart: u64 = off;
|
let rstart: u64 = off;
|
||||||
@@ -377,7 +370,6 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = {
|
|||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Append a fresh aprog to the list with given opcode and label.
|
|
||||||
fn addprog(a: *asm_, opc: i32, lbl: str) *aprog = {
|
fn addprog(a: *asm_, opc: i32, lbl: str) *aprog = {
|
||||||
let pr: *aprog = alloc(aprog { as_ = opc, line = a.line, label = lbl })!;
|
let pr: *aprog = alloc(aprog { as_ = opc, line = a.line, label = lbl })!;
|
||||||
pr.from = alloc(aoperand { })!;
|
pr.from = alloc(aoperand { })!;
|
||||||
@@ -398,9 +390,7 @@ export fn parse(a: *asm_) i32 = {
|
|||||||
line, n = nextline(a);
|
line, n = nextline(a);
|
||||||
if (line == nil) { return a.errs; };
|
if (line == nil) { return a.errs; };
|
||||||
|
|
||||||
// skip leading ws
|
|
||||||
let i: u64 = skipws(line, 0u64, n);
|
let i: u64 = skipws(line, 0u64, n);
|
||||||
// blank or //-comment
|
|
||||||
if (i >= n) { a.line += 1; continue; };
|
if (i >= n) { a.line += 1; continue; };
|
||||||
if (i + 1u64 < n) {
|
if (i + 1u64 < n) {
|
||||||
if (line[i] == '/') { if (line[i + 1u64] == '/') {
|
if (line[i] == '/') { if (line[i + 1u64] == '/') {
|
||||||
@@ -435,7 +425,6 @@ export fn parse(a: *asm_) i32 = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// MNEMONIC at the start of the rest. Scan to first ws/EOL.
|
|
||||||
let mstart: u64 = i;
|
let mstart: u64 = i;
|
||||||
let m: u64 = mstart;
|
let m: u64 = mstart;
|
||||||
let scan: bool = true;
|
let scan: bool = true;
|
||||||
@@ -458,11 +447,9 @@ export fn parse(a: *asm_) i32 = {
|
|||||||
let pr: *aprog = addprog(a, opc, pending);
|
let pr: *aprog = addprog(a, opc, pending);
|
||||||
pending.ptr = nil; pending.len = 0;
|
pending.ptr = nil; pending.len = 0;
|
||||||
|
|
||||||
// Skip ws after mnemonic
|
|
||||||
let r0: u64 = skipws(line, m, n);
|
let r0: u64 = skipws(line, m, n);
|
||||||
|
|
||||||
if (opc == A_TEXT) {
|
if (opc == A_TEXT) {
|
||||||
// TEXT name,$framesize — find first ',' as the end of name.
|
|
||||||
let q: u64 = r0;
|
let q: u64 = r0;
|
||||||
let commapos: u64 = n;
|
let commapos: u64 = n;
|
||||||
let scant: bool = true;
|
let scant: bool = true;
|
||||||
@@ -502,7 +489,6 @@ export fn parse(a: *asm_) i32 = {
|
|||||||
let toop: *aoperand = pr.to;
|
let toop: *aoperand = pr.to;
|
||||||
toop.atype = D_EXTERN;
|
toop.atype = D_EXTERN;
|
||||||
toop.asym = dupstr(line + r0, lparen - r0);
|
toop.asym = dupstr(line + r0, lparen - r0);
|
||||||
// Skip past `(SB)` to land just after ')'.
|
|
||||||
let p2: u64 = lparen;
|
let p2: u64 = lparen;
|
||||||
let scand2: bool = true;
|
let scand2: bool = true;
|
||||||
for (scand2) {
|
for (scand2) {
|
||||||
@@ -510,7 +496,6 @@ export fn parse(a: *asm_) i32 = {
|
|||||||
else { if (line[p2] == ')') { p2 += 1u64; scand2 = false; }
|
else { if (line[p2] == ')') { p2 += 1u64; scand2 = false; }
|
||||||
else { p2 += 1u64; }; };
|
else { p2 += 1u64; }; };
|
||||||
};
|
};
|
||||||
// Skip ws / ',' / tab between `)` and the `"`.
|
|
||||||
let scand3: bool = true;
|
let scand3: bool = true;
|
||||||
for (scand3) {
|
for (scand3) {
|
||||||
if (p2 >= n) { scand3 = false; }
|
if (p2 >= n) { scand3 = false; }
|
||||||
@@ -521,8 +506,7 @@ export fn parse(a: *asm_) i32 = {
|
|||||||
};
|
};
|
||||||
if (p2 >= n) { perr(a, "DATA missing payload"); a.line += 1; continue; };
|
if (p2 >= n) { perr(a, "DATA missing payload"); a.line += 1; continue; };
|
||||||
if (line[p2] != '"') { perr(a, "DATA expects \"...\""); a.line += 1; continue; };
|
if (line[p2] != '"') { perr(a, "DATA expects \"...\""); a.line += 1; continue; };
|
||||||
p2 += 1u64; // past opening "
|
p2 += 1u64;
|
||||||
// Parse escape sequence into a fresh growable buffer.
|
|
||||||
let cap: u64 = 32u64;
|
let cap: u64 = 32u64;
|
||||||
let blen: u64 = 0u64;
|
let blen: u64 = 0u64;
|
||||||
let dbuf: []u8 = alloc([], cap)!;
|
let dbuf: []u8 = alloc([], cap)!;
|
||||||
@@ -575,8 +559,6 @@ export fn parse(a: *asm_) i32 = {
|
|||||||
a.line += 1; continue;
|
a.line += 1; continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generic instruction: 0/1/2 operands separated by ','.
|
|
||||||
// Find top-level comma.
|
|
||||||
let comma: i64 = -1i64;
|
let comma: i64 = -1i64;
|
||||||
let q: u64 = r0;
|
let q: u64 = r0;
|
||||||
for (q < n) {
|
for (q < n) {
|
||||||
|
|||||||
@@ -1,14 +1,4 @@
|
|||||||
// selfhost/cmd/w6c/main.ww — port of cmd/w6c/main.c.
|
// Port of cmd/w6c/main.c.
|
||||||
//
|
|
||||||
// w6c = amd64 compiler. Read .ww, parse, codegen, emit Plan 9 amd64
|
|
||||||
// asm to stdout (or the file given by -o).
|
|
||||||
//
|
|
||||||
// w6c_ww -o file.s file.ww
|
|
||||||
//
|
|
||||||
// The cgen routines in selfhost/cmd/wcc/cgen.ww write directly to
|
|
||||||
// fd 1 via os.write(1, ...). For -o, we open the output file and
|
|
||||||
// dup2 it onto fd 1 before invoking cgfile. This is the same trick
|
|
||||||
// the bootstrap uses with shell redirection, just in-process.
|
|
||||||
|
|
||||||
package main;
|
package main;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
// selfhost/cmd/w6l/dyn.ww — port of cmd/w6l/dyn.c.
|
// Port of cmd/w6l/dyn.c.
|
||||||
//
|
//
|
||||||
// Load a shared object (ET_DYN) so the linker knows which symbols it
|
// Load a shared object (ET_DYN) so the linker knows which symbols it
|
||||||
// exports and which DT_NEEDED entry to record. We do not pull bytes
|
// exports and which DT_NEEDED entry to record. We do not pull bytes
|
||||||
// from the .so; the dynamic loader maps it at runtime.
|
// from the .so; the dynamic loader maps it at runtime.
|
||||||
//
|
|
||||||
// Each call appends one lso to lnk->sos. l_so_provides_v answers
|
|
||||||
// "does this .so export the named symbol, and at which version?" —
|
|
||||||
// l_resolve uses that to promote unresolved references to dynamic.
|
|
||||||
|
|
||||||
package w6l;
|
package w6l;
|
||||||
|
|
||||||
@@ -72,8 +68,6 @@ def VD_NEXT: u64 = 16u64;
|
|||||||
def VA_NAME: u64 = 0u64;
|
def VA_NAME: u64 = 0u64;
|
||||||
def VA_NEXT: u64 = 4u64;
|
def VA_NEXT: u64 = 4u64;
|
||||||
|
|
||||||
// ---- little-endian byte readers ---------------------------------------
|
|
||||||
|
|
||||||
fn du16(p: *u8, off: u64) u16 = {
|
fn du16(p: *u8, off: u64) u16 = {
|
||||||
let b0: u16 = p[off]: u16;
|
let b0: u16 = p[off]: u16;
|
||||||
let b1: u16 = p[off + 1u64]: u16;
|
let b1: u16 = p[off + 1u64]: u16;
|
||||||
@@ -98,8 +92,6 @@ fn di64(p: *u8, off: u64) i64 = {
|
|||||||
return du64(p, off): i64;
|
return du64(p, off): i64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- C-string helpers --------------------------------------------------
|
|
||||||
|
|
||||||
fn dcstrlen(p: *u8) u64 = {
|
fn dcstrlen(p: *u8) u64 = {
|
||||||
let n: u64 = 0u64;
|
let n: u64 = 0u64;
|
||||||
for (p[n] != 0u8) { n += 1u64; };
|
for (p[n] != 0u8) { n += 1u64; };
|
||||||
@@ -114,7 +106,6 @@ fn dcstrtostr(p: *u8) str = {
|
|||||||
return strings.dup(view);
|
return strings.dup(view);
|
||||||
};
|
};
|
||||||
|
|
||||||
// basename: scan for last '/' and return pointer past it.
|
|
||||||
fn dbasename(p: *u8) *u8 = {
|
fn dbasename(p: *u8) *u8 = {
|
||||||
let n: u64 = dcstrlen(p);
|
let n: u64 = dcstrlen(p);
|
||||||
let i: u64 = n;
|
let i: u64 = n;
|
||||||
@@ -127,8 +118,6 @@ fn dbasename(p: *u8) *u8 = {
|
|||||||
return p;
|
return p;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- file slurp --------------------------------------------------------
|
|
||||||
|
|
||||||
fn slurpso(path: *u8) (*u8, u64) = {
|
fn slurpso(path: *u8) (*u8, u64) = {
|
||||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||||
if (fd < 0) { return nil, 0u64; };
|
if (fd < 0) { return nil, 0u64; };
|
||||||
@@ -151,8 +140,6 @@ fn slurpso(path: *u8) (*u8, u64) = {
|
|||||||
return buf.ptr, n: u64;
|
return buf.ptr, n: u64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- verdef helpers ----------------------------------------------------
|
|
||||||
|
|
||||||
// vdnameat — walk verdef records and return the name (as *u8 into
|
// vdnameat — walk verdef records and return the name (as *u8 into
|
||||||
// the .so's verstr buffer) for the entry whose vd_ndx == ndx. The name
|
// the .so's verstr buffer) for the entry whose vd_ndx == ndx. The name
|
||||||
// is the first Verdaux's vda_name (subsequent auxes are predecessor
|
// is the first Verdaux's vda_name (subsequent auxes are predecessor
|
||||||
@@ -176,8 +163,6 @@ fn vdnameat(buf: *u8, verdefoff: u64, verdefsize: u64,
|
|||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- entry points ------------------------------------------------------
|
|
||||||
|
|
||||||
export fn loadso(l: *lnk, path: *u8) i32 = {
|
export fn loadso(l: *lnk, path: *u8) i32 = {
|
||||||
let buf: *u8;
|
let buf: *u8;
|
||||||
let blen: u64;
|
let blen: u64;
|
||||||
@@ -207,7 +192,6 @@ export fn loadso(l: *lnk, path: *u8) i32 = {
|
|||||||
if (shoff == 0u64) { return soerr("stripped .so unsupported"); };
|
if (shoff == 0u64) { return soerr("stripped .so unsupported"); };
|
||||||
if (shnum == 0u32) { return soerr("stripped .so unsupported"); };
|
if (shnum == 0u32) { return soerr("stripped .so unsupported"); };
|
||||||
|
|
||||||
// Locate the four sections we care about.
|
|
||||||
let idxdynsym: i32 = -1;
|
let idxdynsym: i32 = -1;
|
||||||
let idxdynamic: i32 = -1;
|
let idxdynamic: i32 = -1;
|
||||||
let idxversym: i32 = -1;
|
let idxversym: i32 = -1;
|
||||||
@@ -292,8 +276,8 @@ export fn loadso(l: *lnk, path: *u8) i32 = {
|
|||||||
verstr = buf + vstroff;
|
verstr = buf + vstroff;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build the lso. Exports are appended in dynsym order so
|
// Exports are appended in dynsym order so soprovides_v's
|
||||||
// soprovides_v's first-match semantics match the C version.
|
// first-match semantics match the C version.
|
||||||
let so: *lso = alloc(lso { path = dcstrtostr(path), soname = dcstrtostr(sonamecs) })!;
|
let so: *lso = alloc(lso { path = dcstrtostr(path), soname = dcstrtostr(sonamecs) })!;
|
||||||
let tail: *lexport = nil;
|
let tail: *lexport = nil;
|
||||||
|
|
||||||
@@ -371,7 +355,6 @@ fn soerr(msg: str) i32 = {
|
|||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// soprovides — 1 if so exports name, 0 otherwise.
|
|
||||||
export fn soprovides(so: *lso, name: str) i32 = {
|
export fn soprovides(so: *lso, name: str) i32 = {
|
||||||
if (so == nil) { return 0; };
|
if (so == nil) { return 0; };
|
||||||
let e: *lexport = so.exports;
|
let e: *lexport = so.exports;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// selfhost/cmd/w6l/dynout.ww — port of cmd/w6l/dynout.c.
|
// Port of cmd/w6l/dynout.c.
|
||||||
//
|
//
|
||||||
// Emit a dynamic-linked ELF executable. The shape is the simplest
|
// Emit a dynamic-linked ELF executable. The shape is the simplest
|
||||||
// valid one: PT_INTERP + PT_DYNAMIC + DT_BIND_NOW so the loader
|
// valid one: PT_INTERP + PT_DYNAMIC + DT_BIND_NOW so the loader
|
||||||
@@ -29,7 +29,6 @@ import rt;
|
|||||||
import strings;
|
import strings;
|
||||||
import sym;
|
import sym;
|
||||||
|
|
||||||
// ELF constants
|
|
||||||
def ET_EXEC_D: u16 = 2u16;
|
def ET_EXEC_D: u16 = 2u16;
|
||||||
def EM_X86_64_D: u16 = 62u16;
|
def EM_X86_64_D: u16 = 62u16;
|
||||||
def EV_CURRENT_D: u32 = 1u32;
|
def EV_CURRENT_D: u32 = 1u32;
|
||||||
@@ -75,8 +74,6 @@ def PAGE: u64 = 4096u64;
|
|||||||
|
|
||||||
def INTERP: str = "/lib64/ld-linux-x86-64.so.2";
|
def INTERP: str = "/lib64/ld-linux-x86-64.so.2";
|
||||||
|
|
||||||
// ---- byte writers ------------------------------------------------------
|
|
||||||
|
|
||||||
fn dwr8(buf: *u8, off: u64, v: u8) void = {
|
fn dwr8(buf: *u8, off: u64, v: u8) void = {
|
||||||
buf[off] = v;
|
buf[off] = v;
|
||||||
};
|
};
|
||||||
@@ -106,8 +103,6 @@ fn dwri32(buf: *u8, off: u64, v: i32) void = {
|
|||||||
dwr32(buf, off, v: u32);
|
dwr32(buf, off, v: u32);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- byte readers ------------------------------------------------------
|
|
||||||
|
|
||||||
fn drdu16(p: *u8, off: u64) u16 = {
|
fn drdu16(p: *u8, off: u64) u16 = {
|
||||||
let b0: u16 = p[off]: u16;
|
let b0: u16 = p[off]: u16;
|
||||||
let b1: u16 = p[off + 1u64]: u16;
|
let b1: u16 = p[off + 1u64]: u16;
|
||||||
@@ -153,8 +148,6 @@ fn alignup(off: u64, a: u64) u64 = {
|
|||||||
return (off + a - 1u64) & ~(a - 1u64);
|
return (off + a - 1u64) & ~(a - 1u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- main entry --------------------------------------------------------
|
|
||||||
|
|
||||||
export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
||||||
// .data shares the R+W PT_LOAD with .got.plt and .dynamic.
|
// .data shares the R+W PT_LOAD with .got.plt and .dynamic.
|
||||||
// Placed after .dynamic so the segment is one contiguous run;
|
// Placed after .dynamic so the segment is one contiguous run;
|
||||||
@@ -163,7 +156,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
let n: i32 = l.dynn;
|
let n: i32 = l.dynn;
|
||||||
let nu: u64 = n: u64;
|
let nu: u64 = n: u64;
|
||||||
|
|
||||||
// ---- collect dyn syms into a plt_idx-indexed array ----
|
|
||||||
let dynsyms: []*lsym = alloc([], nu)!;
|
let dynsyms: []*lsym = alloc([], nu)!;
|
||||||
let s: *lsym = l.syms;
|
let s: *lsym = l.syms;
|
||||||
for (s != nil) {
|
for (s != nil) {
|
||||||
@@ -185,7 +177,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- collect used .so's (in l.sos order) ----
|
// Used .so's are collected in l.sos order.
|
||||||
let maxsos: i32 = 0;
|
let maxsos: i32 = 0;
|
||||||
let so: *lso = l.sos;
|
let so: *lso = l.sos;
|
||||||
for (so != nil) { maxsos += 1; so = so.sonext; };
|
for (so != nil) { maxsos += 1; so = so.sonext; };
|
||||||
@@ -208,7 +200,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
so = so.sonext;
|
so = so.sonext;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- build flat version table grouped by vlib ----
|
// Flat version table grouped by vlib:
|
||||||
// vlib_sos_idx[k] = sos_used index for vlib k.
|
// vlib_sos_idx[k] = sos_used index for vlib k.
|
||||||
// vlib_first[k] = ver index of first version under vlib k.
|
// vlib_first[k] = ver index of first version under vlib k.
|
||||||
// vlib_count[k] = number of versions under vlib k.
|
// vlib_count[k] = number of versions under vlib k.
|
||||||
@@ -301,7 +293,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
vi += 1;
|
vi += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- compute dynstr size ----
|
|
||||||
let dynstrsz: u64 = 1u64; // leading NUL
|
let dynstrsz: u64 = 1u64; // leading NUL
|
||||||
let pi: i32 = 0;
|
let pi: i32 = 0;
|
||||||
for (pi < nsos) {
|
for (pi < nsos) {
|
||||||
@@ -325,7 +316,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
pi += 1;
|
pi += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fill dynstr ----
|
|
||||||
let dynstr: []u8 = alloc([], dynstrsz)!;
|
let dynstr: []u8 = alloc([], dynstrsz)!;
|
||||||
let dynstrpos: u64 = 1u64; // past leading NUL
|
let dynstrpos: u64 = 1u64; // past leading NUL
|
||||||
|
|
||||||
@@ -365,7 +355,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
pi += 1;
|
pi += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- per-dyn-sym versym index ----
|
|
||||||
let versymfor: []u8 = alloc([], nu * 2u64)!;
|
let versymfor: []u8 = alloc([], nu * 2u64)!;
|
||||||
pi = 0;
|
pi = 0;
|
||||||
for (pi < n) {
|
for (pi < n) {
|
||||||
@@ -400,7 +389,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
pi += 1;
|
pi += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- compute byte sizes ----
|
|
||||||
let ehdrsz: u64 = 64u64;
|
let ehdrsz: u64 = 64u64;
|
||||||
let nphdrs: u64 = 4u64;
|
let nphdrs: u64 = 4u64;
|
||||||
let phdrsz: u64 = nphdrs * 56u64;
|
let phdrsz: u64 = nphdrs * 56u64;
|
||||||
@@ -433,7 +421,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
let ndyn: u64 = (nsos: u64) + 11u64 + extra;
|
let ndyn: u64 = (nsos: u64) + 11u64 + extra;
|
||||||
let dynamicsz: u64 = ndyn * 16u64;
|
let dynamicsz: u64 = ndyn * 16u64;
|
||||||
|
|
||||||
// ---- compute file offsets ----
|
|
||||||
let off: u64 = ehdrsz + phdrsz;
|
let off: u64 = ehdrsz + phdrsz;
|
||||||
let interpoff: u64 = off; off += interpsz;
|
let interpoff: u64 = off; off += interpsz;
|
||||||
off = alignup(off, 8u64);
|
off = alignup(off, 8u64);
|
||||||
@@ -486,7 +473,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
let datafilelen: u64 = l.datalen - bsslen;
|
let datafilelen: u64 = l.datalen - bsslen;
|
||||||
let filedataend: u64 = dataoff + datafilelen;
|
let filedataend: u64 = dataoff + datafilelen;
|
||||||
|
|
||||||
// ---- build .dynsym ----
|
|
||||||
let dynsymbuf: []u8 = alloc([], dynsymsz)!;
|
let dynsymbuf: []u8 = alloc([], dynsymsz)!;
|
||||||
pi = 0;
|
pi = 0;
|
||||||
for (pi < n) {
|
for (pi < n) {
|
||||||
@@ -500,7 +486,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
pi += 1;
|
pi += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- build .hash (SysV, 1 bucket) ----
|
// .hash is SysV with a single bucket.
|
||||||
let hashbuf: []u8 = alloc([], hashsz)!;
|
let hashbuf: []u8 = alloc([], hashsz)!;
|
||||||
dwr32(hashbuf.ptr, 0u64, nbuckets);
|
dwr32(hashbuf.ptr, 0u64, nbuckets);
|
||||||
dwr32(hashbuf.ptr, 4u64, nchain);
|
dwr32(hashbuf.ptr, 4u64, nchain);
|
||||||
@@ -515,7 +501,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
ci += 1u64;
|
ci += 1u64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- build .rela.plt ----
|
|
||||||
let relapltbuf: []u8 = alloc([], relapltsz)!;
|
let relapltbuf: []u8 = alloc([], relapltsz)!;
|
||||||
pi = 0;
|
pi = 0;
|
||||||
for (pi < n) {
|
for (pi < n) {
|
||||||
@@ -527,7 +512,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
pi += 1;
|
pi += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- build .gnu.version (u16 per dynsym entry) ----
|
// .gnu.version is one u16 per dynsym entry.
|
||||||
let versymbuf: []u8 = alloc([], versymsz)!;
|
let versymbuf: []u8 = alloc([], versymsz)!;
|
||||||
dwr16(versymbuf.ptr, 0u64, VER_NDX_LOCAL_D);
|
dwr16(versymbuf.ptr, 0u64, VER_NDX_LOCAL_D);
|
||||||
pi = 0;
|
pi = 0;
|
||||||
@@ -536,7 +521,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
pi += 1;
|
pi += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- build .gnu.version_r ----
|
|
||||||
let verneedbuf: []u8 = alloc([], verneedsz)!;
|
let verneedbuf: []u8 = alloc([], verneedsz)!;
|
||||||
if (verneedsz > 0u64) {
|
if (verneedsz > 0u64) {
|
||||||
let vnoff: u64 = 0u64;
|
let vnoff: u64 = 0u64;
|
||||||
@@ -575,7 +559,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- build .plt ----
|
|
||||||
let pltbuf: []u8 = alloc([], pltsz)!;
|
let pltbuf: []u8 = alloc([], pltsz)!;
|
||||||
pi = 0;
|
pi = 0;
|
||||||
for (pi < n) {
|
for (pi < n) {
|
||||||
@@ -590,11 +573,9 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
pi += 1;
|
pi += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- build .got.plt ----
|
|
||||||
let gotpltbuf: []u8 = alloc([], gotpltsz)!;
|
let gotpltbuf: []u8 = alloc([], gotpltsz)!;
|
||||||
dwr64(gotpltbuf.ptr, 0u64, dynamicva);
|
dwr64(gotpltbuf.ptr, 0u64, dynamicva);
|
||||||
|
|
||||||
// ---- build .dynamic ----
|
|
||||||
let dynamicbuf: []u8 = alloc([], dynamicsz)!;
|
let dynamicbuf: []u8 = alloc([], dynamicsz)!;
|
||||||
let dk: u64 = 0u64;
|
let dk: u64 = 0u64;
|
||||||
pi = 0;
|
pi = 0;
|
||||||
@@ -625,7 +606,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- patch .text relocs targeting dynamic syms ----
|
|
||||||
let r: *lrel = l.rels;
|
let r: *lrel = l.rels;
|
||||||
for (r != nil) {
|
for (r != nil) {
|
||||||
if (r.sym != nil) {
|
if (r.sym != nil) {
|
||||||
@@ -646,7 +626,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
r = r.rnext;
|
r = r.rnext;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- assemble file buffer ----
|
|
||||||
let filebuf: []u8 = alloc([], fileend)!;
|
let filebuf: []u8 = alloc([], fileend)!;
|
||||||
filebuf.len = fileend: i32;
|
filebuf.len = fileend: i32;
|
||||||
|
|
||||||
@@ -721,7 +700,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
dwr64(filebuf.ptr, p3 + 40u64, dynamicsz);
|
dwr64(filebuf.ptr, p3 + 40u64, dynamicsz);
|
||||||
dwr64(filebuf.ptr, p3 + 48u64, 8u64);
|
dwr64(filebuf.ptr, p3 + 48u64, 8u64);
|
||||||
|
|
||||||
// Sections.
|
|
||||||
dbcopy(filebuf.ptr, interpoff, INTERP.ptr, INTERP.len: u64);
|
dbcopy(filebuf.ptr, interpoff, INTERP.ptr, INTERP.len: u64);
|
||||||
dwr8(filebuf.ptr, interpoff + (INTERP.len: u64), 0u8);
|
dwr8(filebuf.ptr, interpoff + (INTERP.len: u64), 0u8);
|
||||||
dbcopy(filebuf.ptr, dynstroff, dynstr.ptr, dynstrsz);
|
dbcopy(filebuf.ptr, dynstroff, dynstr.ptr, dynstrsz);
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
// selfhost/cmd/w6l/main.ww — port of cmd/w6l/main.c.
|
// Port of cmd/w6l/main.c.
|
||||||
//
|
|
||||||
// w6l = amd64 linker. Reads relocatable ELF .o files, SysV `ar`
|
|
||||||
// archives, and shared objects (ET_DYN). Resolves symbols, applies
|
|
||||||
// relocations, writes a static or dynamic-linked ELF executable.
|
|
||||||
//
|
//
|
||||||
// w6l_ww -o out [-L<dir>...] [-l<name>...] file1.o file2.o ...
|
// w6l_ww -o out [-L<dir>...] [-l<name>...] file1.o file2.o ...
|
||||||
|
|
||||||
@@ -25,10 +21,6 @@ fn mklnk() *lnk = {
|
|||||||
return l;
|
return l;
|
||||||
};
|
};
|
||||||
|
|
||||||
// `cstreq` lives in obj.ww — same bundle, single definition.
|
|
||||||
|
|
||||||
// `cstrlen` lives in obj.ww — same bundle, single definition.
|
|
||||||
|
|
||||||
// Build "<dir>/lib<name>.<ext>" into dst (NUL-terminated). Returns total
|
// Build "<dir>/lib<name>.<ext>" into dst (NUL-terminated). Returns total
|
||||||
// length excluding NUL. dst must be large enough.
|
// length excluding NUL. dst must be large enough.
|
||||||
fn buildpath(dst: *u8, dir: *u8, name: *u8, ext: str) u64 = {
|
fn buildpath(dst: *u8, dir: *u8, name: *u8, ext: str) u64 = {
|
||||||
@@ -274,8 +266,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
|||||||
k += 1;
|
k += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Then resolve -l flags and load each. Archives append; shared
|
// Archives append; shared objects register their exports.
|
||||||
// objects register their exports.
|
|
||||||
let lf: i32 = 0;
|
let lf: i32 = 0;
|
||||||
for (lf < nlflags) {
|
for (lf < nlflags) {
|
||||||
let p: *u8 = resolvelib(lflags[lf], libdirs.ptr, nlibdirs);
|
let p: *u8 = resolvelib(lflags[lf], libdirs.ptr, nlibdirs);
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
// selfhost/cmd/w6l/obj.ww — port of cmd/w6l/obj.c.
|
// Port of cmd/w6l/obj.c.
|
||||||
//
|
|
||||||
// Loads relocatable ELF64 .o files emitted by w6a, appends .text to
|
|
||||||
// the combined image, and pulls in symbols + relocations with
|
|
||||||
// offsets adjusted to the combined section.
|
|
||||||
//
|
|
||||||
// Also handles SysV `ar` archives (libwwrt.a). The two-pass loader
|
|
||||||
// indexes members on the first pass and iteratively pulls members
|
|
||||||
// that define currently-undefined symbols on subsequent passes.
|
|
||||||
|
|
||||||
package w6l;
|
package w6l;
|
||||||
|
|
||||||
@@ -22,7 +14,6 @@ def SHT_SYMTAB: i32 = 2;
|
|||||||
def SHT_STRTAB: i32 = 3;
|
def SHT_STRTAB: i32 = 3;
|
||||||
def SHT_RELA: i32 = 4;
|
def SHT_RELA: i32 = 4;
|
||||||
|
|
||||||
// ---- little-endian byte readers ----------------------------------------
|
|
||||||
// w6a/w6l use straight LE on amd64. Reading via byte offsets keeps us off
|
// w6a/w6l use straight LE on amd64. Reading via byte offsets keeps us off
|
||||||
// the cgen's u16 field-load story for now (MOVZBQ exists; MOVZWQ doesn't).
|
// the cgen's u16 field-load story for now (MOVZBQ exists; MOVZWQ doesn't).
|
||||||
|
|
||||||
@@ -46,7 +37,6 @@ fn rdu64(p: *u8, off: u64) u64 = {
|
|||||||
return lo | (hi << 32u64);
|
return lo | (hi << 32u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- ELF64 section header offsets (40 bytes total) --------------------
|
|
||||||
def SHDR_SIZE: u64 = 64u64; // sizeof(Shdr) per ELF64 spec
|
def SHDR_SIZE: u64 = 64u64; // sizeof(Shdr) per ELF64 spec
|
||||||
def SHDR_NAME: u64 = 0u64;
|
def SHDR_NAME: u64 = 0u64;
|
||||||
def SHDR_TYPE: u64 = 4u64;
|
def SHDR_TYPE: u64 = 4u64;
|
||||||
@@ -76,8 +66,6 @@ def RELA_OFFSET: u64 = 0u64;
|
|||||||
def RELA_INFO: u64 = 8u64;
|
def RELA_INFO: u64 = 8u64;
|
||||||
def RELA_ADDEND: u64 = 16u64;
|
def RELA_ADDEND: u64 = 16u64;
|
||||||
|
|
||||||
// ---- file slurp --------------------------------------------------------
|
|
||||||
|
|
||||||
fn slurp(path: *u8) (*u8, u64) = {
|
fn slurp(path: *u8) (*u8, u64) = {
|
||||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||||
if (fd < 0) { return nil, 0u64; };
|
if (fd < 0) { return nil, 0u64; };
|
||||||
@@ -100,8 +88,6 @@ fn slurp(path: *u8) (*u8, u64) = {
|
|||||||
return buf.ptr, n: u64;
|
return buf.ptr, n: u64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- text buffer growth ------------------------------------------------
|
|
||||||
|
|
||||||
fn emittext(l: *lnk, src: *u8, n: u64) void = {
|
fn emittext(l: *lnk, src: *u8, n: u64) void = {
|
||||||
if (l.textlen + n > l.textcap) {
|
if (l.textlen + n > l.textcap) {
|
||||||
let nc: u64 = l.textcap;
|
let nc: u64 = l.textcap;
|
||||||
@@ -149,17 +135,13 @@ fn emitdata(l: *lnk, src: *u8, n: u64) void = {
|
|||||||
l.datalen += n;
|
l.datalen += n;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- C-string helpers --------------------------------------------------
|
|
||||||
|
|
||||||
fn cstrlen(p: *u8) u64 = {
|
fn cstrlen(p: *u8) u64 = {
|
||||||
let n: u64 = 0u64;
|
let n: u64 = 0u64;
|
||||||
for (p[n] != 0u8) { n += 1u64; };
|
for (p[n] != 0u8) { n += 1u64; };
|
||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
|
||||||
// pathstr — view a NUL-terminated *u8 as a str. Bridges argv-style
|
// Bridges argv-style callers to lib/os entrypoints (str post-task-#23).
|
||||||
// callers to lib/os entrypoints (str post-task-#23). Shared with
|
|
||||||
// main.ww and dyn.ww via the w6l bundle.
|
|
||||||
fn pathstr(p: *u8) str = {
|
fn pathstr(p: *u8) str = {
|
||||||
let r: str;
|
let r: str;
|
||||||
r.ptr = p;
|
r.ptr = p;
|
||||||
@@ -179,7 +161,6 @@ fn cstreq(p: *u8, lit: str) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build a ww str from a NUL-terminated *u8 (for passing to intern).
|
|
||||||
fn cstrtostr(p: *u8) str = {
|
fn cstrtostr(p: *u8) str = {
|
||||||
let n: u64 = cstrlen(p);
|
let n: u64 = cstrlen(p);
|
||||||
let view: str;
|
let view: str;
|
||||||
@@ -188,8 +169,6 @@ fn cstrtostr(p: *u8) str = {
|
|||||||
return strings.dup(view);
|
return strings.dup(view);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- archive (SysV ar) types and helpers -------------------------------
|
|
||||||
//
|
|
||||||
// Each archive member starts with a 60-byte ar_hdr. The fields we care
|
// Each archive member starts with a 60-byte ar_hdr. The fields we care
|
||||||
// about are the first byte (member type) and the size at offset 48 (a
|
// about are the first byte (member type) and the size at offset 48 (a
|
||||||
// 10-byte, space-padded decimal). Member bodies are 2-byte aligned.
|
// 10-byte, space-padded decimal). Member bodies are 2-byte aligned.
|
||||||
@@ -231,9 +210,8 @@ fn arfield(p: *u8, n: u64) u64 = {
|
|||||||
return v;
|
return v;
|
||||||
};
|
};
|
||||||
|
|
||||||
// elfglobals — return a linked list of names of globally-defined
|
// Names are owned heap copies, so the source ELF buffer can be freed
|
||||||
// (STB_GLOBAL) symbols whose section is `.text`. Names are owned
|
// afterward.
|
||||||
// heap copies, so the source ELF buffer can be freed afterward.
|
|
||||||
fn elfglobals(buf: *u8, len: u64) *defent = {
|
fn elfglobals(buf: *u8, len: u64) *defent = {
|
||||||
if (len < EHDR_SIZE) { return nil; };
|
if (len < EHDR_SIZE) { return nil; };
|
||||||
if (buf[0u64] != 127u8) { return nil; };
|
if (buf[0u64] != 127u8) { return nil; };
|
||||||
@@ -310,9 +288,8 @@ fn elfglobals(buf: *u8, len: u64) *defent = {
|
|||||||
return head;
|
return head;
|
||||||
};
|
};
|
||||||
|
|
||||||
// memberdefinesundef — true if any of m's defined globals matches a
|
// Names not already interned are uninteresting (the link doesn't need
|
||||||
// currently-undefined symbol in the linker's symbol table. Names not
|
// them yet).
|
||||||
// already interned are uninteresting (the link doesn't need them yet).
|
|
||||||
fn memberdefinesundef(l: *lnk, m: *armember) bool = {
|
fn memberdefinesundef(l: *lnk, m: *armember) bool = {
|
||||||
let de: *defent = m.defs;
|
let de: *defent = m.defs;
|
||||||
for (de != nil) {
|
for (de != nil) {
|
||||||
@@ -406,8 +383,6 @@ fn loadarchive(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- main loader -------------------------------------------------------
|
|
||||||
|
|
||||||
export fn load(l: *lnk, path: *u8) i32 = {
|
export fn load(l: *lnk, path: *u8) i32 = {
|
||||||
let bufp: *u8;
|
let bufp: *u8;
|
||||||
let buflen: u64;
|
let buflen: u64;
|
||||||
@@ -441,7 +416,6 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
|
|||||||
let shstrshoff: u64 = rdu64(buf, shoff + (shstrndx: u64) * SHDR_SIZE + SHDR_OFFSET);
|
let shstrshoff: u64 = rdu64(buf, shoff + (shstrndx: u64) * SHDR_SIZE + SHDR_OFFSET);
|
||||||
let shstr: *u8 = buf + shstrshoff;
|
let shstr: *u8 = buf + shstrshoff;
|
||||||
|
|
||||||
// find .text, .data, .symtab, .rela.text, .rela.data
|
|
||||||
let idxtext: i32 = -1;
|
let idxtext: i32 = -1;
|
||||||
let idxdata: i32 = -1;
|
let idxdata: i32 = -1;
|
||||||
let idxsymtab: i32 = -1;
|
let idxsymtab: i32 = -1;
|
||||||
@@ -497,7 +471,6 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
|
|||||||
datasize = rdu64(buf, datash + SHDR_SIZE_F);
|
datasize = rdu64(buf, datash + SHDR_SIZE_F);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Track this object.
|
|
||||||
let ob: *lobj = alloc(lobj {
|
let ob: *lobj = alloc(lobj {
|
||||||
path = cstrtostr(path),
|
path = cstrtostr(path),
|
||||||
buf = buf,
|
buf = buf,
|
||||||
@@ -510,18 +483,16 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
|
|||||||
})!;
|
})!;
|
||||||
l.objs = ob;
|
l.objs = ob;
|
||||||
|
|
||||||
// Append .text bytes to the combined image.
|
|
||||||
emittext(l, buf + textoff, textsize);
|
emittext(l, buf + textoff, textsize);
|
||||||
// Append .data bytes (if present) to the combined .data buffer.
|
|
||||||
if (idxdata >= 0) {
|
if (idxdata >= 0) {
|
||||||
if (datasize > 0u64) {
|
if (datasize > 0u64) {
|
||||||
emitdata(l, buf + dataoff, datasize);
|
emitdata(l, buf + dataoff, datasize);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Walk symbols. We don't keep a per-object map[] of *lsym. Instead
|
// We don't keep a per-object map[] of *lsym. Instead the reloc
|
||||||
// the reloc loop re-walks symtab and re-interns by name. Simpler
|
// loop re-walks symtab and re-interns by name. Simpler than
|
||||||
// than dancing around the cgen's u64-shift gaps.
|
// dancing around the cgen's u64-shift gaps.
|
||||||
let si: u64 = 1u64; // skip index 0 (always undef sentinel)
|
let si: u64 = 1u64; // skip index 0 (always undef sentinel)
|
||||||
for (si < nsyms) {
|
for (si < nsyms) {
|
||||||
let symp: u64 = symoff + si * SYM_SIZE;
|
let symp: u64 = symoff + si * SYM_SIZE;
|
||||||
@@ -569,7 +540,6 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
|
|||||||
si += 1u64;
|
si += 1u64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Per-object relocation collection.
|
|
||||||
if (idxrela >= 0) {
|
if (idxrela >= 0) {
|
||||||
let relash: u64 = shoff + (idxrela: u64) * SHDR_SIZE;
|
let relash: u64 = shoff + (idxrela: u64) * SHDR_SIZE;
|
||||||
let relaoff: u64 = rdu64(buf, relash + SHDR_OFFSET);
|
let relaoff: u64 = rdu64(buf, relash + SHDR_OFFSET);
|
||||||
@@ -590,7 +560,6 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
|
|||||||
addend = raddend: i64,
|
addend = raddend: i64,
|
||||||
rnext = l.rels,
|
rnext = l.rels,
|
||||||
})!;
|
})!;
|
||||||
// Look up the referenced sym by name (re-walk symtab).
|
|
||||||
if ((rsymidx: u64) < nsyms) {
|
if ((rsymidx: u64) < nsyms) {
|
||||||
let sp: u64 = symoff + (rsymidx: u64) * SYM_SIZE;
|
let sp: u64 = symoff + (rsymidx: u64) * SYM_SIZE;
|
||||||
let sname: u32 = rdu32(buf, sp + SYM_NAME);
|
let sname: u32 = rdu32(buf, sp + SYM_NAME);
|
||||||
@@ -605,8 +574,8 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Data-reloc collection. Offsets land in .data, shifted by
|
// Offsets land in .data, shifted by this object's dataoff so
|
||||||
// this object's data_off so they index the combined buffer.
|
// they index the combined buffer.
|
||||||
if (idxrelad >= 0) {
|
if (idxrelad >= 0) {
|
||||||
let relash: u64 = shoff + (idxrelad: u64) * SHDR_SIZE;
|
let relash: u64 = shoff + (idxrelad: u64) * SHDR_SIZE;
|
||||||
let relaoff: u64 = rdu64(buf, relash + SHDR_OFFSET);
|
let relaoff: u64 = rdu64(buf, relash + SHDR_OFFSET);
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
// selfhost/cmd/w6l/out.ww — port of cmd/w6l/out.c.
|
// Port of cmd/w6l/out.c.
|
||||||
//
|
|
||||||
// Emit a static ELF64 executable. File layout (per the C original):
|
|
||||||
// [0..64) Ehdr
|
|
||||||
// [64..120) Phdr (one PT_LOAD)
|
|
||||||
// [120..0x1000) zero pad
|
|
||||||
// [0x1000..) .text bytes
|
|
||||||
// Single PT_LOAD covers the whole file, R+X. No interpreter, no .bss.
|
|
||||||
|
|
||||||
package w6l;
|
package w6l;
|
||||||
|
|
||||||
@@ -27,8 +20,6 @@ def PF_R: u32 = 4u32;
|
|||||||
def TEXT_OFF: u64 = 4096u64; // 0x1000
|
def TEXT_OFF: u64 = 4096u64; // 0x1000
|
||||||
def PAGE_SZ: u64 = 4096u64;
|
def PAGE_SZ: u64 = 4096u64;
|
||||||
|
|
||||||
// ---- little-endian byte writers ----------------------------------------
|
|
||||||
|
|
||||||
fn wru16(buf: *u8, off: u64, v: u16) void = {
|
fn wru16(buf: *u8, off: u64, v: u16) void = {
|
||||||
buf[off] = (v & 255u16): u8;
|
buf[off] = (v & 255u16): u8;
|
||||||
buf[off + 1u64] = ((v >> 8u16) & 255u16): u8;
|
buf[off + 1u64] = ((v >> 8u16) & 255u16): u8;
|
||||||
@@ -46,8 +37,6 @@ fn wru64(buf: *u8, off: u64, v: u64) void = {
|
|||||||
wru32(buf, off + 4u64, ((v >> 32u64) & 4294967295u64): u32);
|
wru32(buf, off + 4u64, ((v >> 32u64) & 4294967295u64): u32);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- emit ---------------------------------------------------------------
|
|
||||||
|
|
||||||
export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
||||||
// Dispatch: any loaded shared object plus any dynamic ref means
|
// Dispatch: any loaded shared object plus any dynamic ref means
|
||||||
// we owe the loader a real PT_INTERP/PT_DYNAMIC binary.
|
// we owe the loader a real PT_INTERP/PT_DYNAMIC binary.
|
||||||
@@ -92,7 +81,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
let hdr: []u8 = alloc([], TEXT_OFF)!;
|
let hdr: []u8 = alloc([], TEXT_OFF)!;
|
||||||
hdr.len = TEXT_OFF: i32;
|
hdr.len = TEXT_OFF: i32;
|
||||||
|
|
||||||
// --- Ehdr (64 bytes) ---
|
// Ehdr (64 bytes).
|
||||||
hdr[0u64] = 127u8; // 0x7f
|
hdr[0u64] = 127u8; // 0x7f
|
||||||
hdr[1u64] = 'E';
|
hdr[1u64] = 'E';
|
||||||
hdr[2u64] = 'L';
|
hdr[2u64] = 'L';
|
||||||
@@ -115,7 +104,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
wru16(hdr.ptr, 60u64, 0u16); // e_shnum
|
wru16(hdr.ptr, 60u64, 0u16); // e_shnum
|
||||||
wru16(hdr.ptr, 62u64, 0u16); // e_shstrndx
|
wru16(hdr.ptr, 62u64, 0u16); // e_shstrndx
|
||||||
|
|
||||||
// --- Phdr #1 (R+X) at offset 64 ---
|
// Phdr #1 (R+X) at offset 64.
|
||||||
wru32(hdr.ptr, 64u64, PT_LOAD);
|
wru32(hdr.ptr, 64u64, PT_LOAD);
|
||||||
wru32(hdr.ptr, 68u64, PF_R | PF_X);
|
wru32(hdr.ptr, 68u64, PF_R | PF_X);
|
||||||
wru64(hdr.ptr, 72u64, 0u64); // p_offset
|
wru64(hdr.ptr, 72u64, 0u64); // p_offset
|
||||||
@@ -126,7 +115,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
wru64(hdr.ptr, 112u64, TEXT_OFF); // p_align
|
wru64(hdr.ptr, 112u64, TEXT_OFF); // p_align
|
||||||
|
|
||||||
if (hasdata) {
|
if (hasdata) {
|
||||||
// --- Phdr #2 (R+W) at offset 64+56=120 ---
|
// Phdr #2 (R+W) at offset 64+56=120.
|
||||||
wru32(hdr.ptr, 120u64, PT_LOAD);
|
wru32(hdr.ptr, 120u64, PT_LOAD);
|
||||||
wru32(hdr.ptr, 124u64, PF_R | PF_W);
|
wru32(hdr.ptr, 124u64, PF_R | PF_W);
|
||||||
wru64(hdr.ptr, 128u64, dataoff); // p_offset
|
wru64(hdr.ptr, 128u64, dataoff); // p_offset
|
||||||
@@ -137,7 +126,6 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|||||||
wru64(hdr.ptr, 168u64, PAGE_SZ); // p_align
|
wru64(hdr.ptr, 168u64, PAGE_SZ); // p_align
|
||||||
};
|
};
|
||||||
|
|
||||||
// Write [0..0x1000) then .text.
|
|
||||||
let r1: (i64 | os.oserror) = os.writeall(fd, hdr.ptr, TEXT_OFF);
|
let r1: (i64 | os.oserror) = os.writeall(fd, hdr.ptr, TEXT_OFF);
|
||||||
let n1: i64 = 0i64;
|
let n1: i64 = 0i64;
|
||||||
match (r1) {
|
match (r1) {
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
// selfhost/cmd/w6l/pass.ww — port of cmd/w6l/pass.c.
|
// Port of cmd/w6l/pass.c.
|
||||||
//
|
|
||||||
// Resolution + relocation. l_resolve flags every undefined symbol
|
|
||||||
// referenced by a relocation, and promotes those provided by some
|
|
||||||
// loaded .so to "dynamic" with a freshly-assigned PLT slot.
|
|
||||||
// l_relocate walks the rel list and patches the .text bytes in place
|
|
||||||
// once the final virtual base is known. Dynamic refs are deferred:
|
|
||||||
// their site is patched later in dynout, once the PLT vaddr is known.
|
|
||||||
//
|
//
|
||||||
// Supported relocation kinds: PC32 (=2), PLT32 (=4); both are 32-bit
|
// Supported relocation kinds: PC32 (=2), PLT32 (=4); both are 32-bit
|
||||||
// PC-relative displacements (PLT32 == PC32 for static).
|
// PC-relative displacements (PLT32 == PC32 for static).
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// selfhost/cmd/w6l/sym.ww — port of cmd/w6l/sym.c.
|
// Port of cmd/w6l/sym.c.
|
||||||
//
|
//
|
||||||
// Linker symbol table. Singly-linked list, usually a few hundred
|
// Singly-linked list, usually a few hundred entries; hashing isn't
|
||||||
// entries; hashing isn't worth it yet.
|
// worth it yet.
|
||||||
|
|
||||||
package w6l;
|
package w6l;
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,4 @@
|
|||||||
// selfhost/cmd/wcc/cgen.ww — port of cmd/w6c/cgen.c.
|
// Port of cmd/w6c/cgen.c.
|
||||||
//
|
|
||||||
// Status: GROWING. Each subsystem we add is verified by `wwdump_ww -c`
|
|
||||||
// producing byte-identical output to C-side `w6c` for the same source,
|
|
||||||
// then by assembling + linking + running the result.
|
|
||||||
//
|
|
||||||
// Current coverage:
|
|
||||||
// - decls: nkind.N_FILE, nkind.N_FNDECL (params, frame for locals, prologue
|
|
||||||
// + dual-epilogue suppression; FFI body-less fn skipped)
|
|
||||||
// - stmts: nkind.N_BLOCK, nkind.N_RETURN, nkind.N_EXPRSTMT, nkind.N_LET (no init),
|
|
||||||
// nkind.N_LET (int-literal / ident / call / nkind.N_BIN init),
|
|
||||||
// nkind.N_IF (with optional else), nkind.N_FOR (cond-only and full
|
|
||||||
// init/cond/post), nkind.N_BREAK, nkind.N_CONTINUE
|
|
||||||
// - exprs: nkind.N_INTLIT, nkind.N_IDENT (local/param), nkind.N_BIN with full op
|
|
||||||
// coverage (+/-/*/// %, &/|/^, <</>>, comparisons with
|
|
||||||
// signed-vs-unsigned dispatch, &&/||), nkind.N_UN (- ! ~ & *),
|
|
||||||
// nkind.N_CALL (recursive R-to-L push, pop into argregs L-to-R),
|
|
||||||
// nkind.N_ASSIGN to local idents (plain and compound +=/-=)
|
|
||||||
//
|
|
||||||
// Type info is shallow — frame slots are 8 bytes per local, all loads
|
|
||||||
// /stores are MOVQ. Programs that mix i8/i32/i64 locals work but spill
|
|
||||||
// 8 bytes per local. Float, str, slice, struct, match, defer, alloc,
|
|
||||||
// tagged-union return — none of those are wired yet.
|
|
||||||
|
|
||||||
package wcc;
|
package wcc;
|
||||||
|
|
||||||
@@ -38,12 +16,8 @@ import cgenexpr;
|
|||||||
import cgenstmt;
|
import cgenstmt;
|
||||||
import cgendecl;
|
import cgendecl;
|
||||||
|
|
||||||
// ---- typedef alias registry -----------------------------------------
|
// Only direct nkind.N_TNAME aliases are mapped; `type p = struct {...}`
|
||||||
//
|
// is handled by collectstructs.
|
||||||
// `type error = str;` makes `error` a struct-shape alias. We track
|
|
||||||
// alias→target so isstrtype / isslicetype / structlookup can
|
|
||||||
// resolve through the chain. Only direct nkind.N_TNAME aliases are mapped;
|
|
||||||
// `type p = struct {...}` is handled by collectstructs.
|
|
||||||
|
|
||||||
type aliasent = struct {
|
type aliasent = struct {
|
||||||
aname: str,
|
aname: str,
|
||||||
@@ -156,12 +130,9 @@ fn aliassamemod(c: *cgen, name: str) *syntax.node = {
|
|||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- enum registry --------------------------------------------------
|
// Enum member values are pre-computed at collect time (auto-increment
|
||||||
//
|
// + sibling refs) so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX.
|
||||||
// Mirrors cmd/wcc/check.c's enum resolution at collect time: walk
|
// Mirrors cmd/wcc/check.c's enum resolution.
|
||||||
// every `type Foo = enum [storage] { ... }`, pre-compute each
|
|
||||||
// member's u64 value (supporting auto-increment and sibling refs),
|
|
||||||
// and stash them so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX.
|
|
||||||
|
|
||||||
// foldintliteral — fold the literal subset usable for top-level
|
// foldintliteral — fold the literal subset usable for top-level
|
||||||
// constant slots: int/rune literal, true/false/nil, and a unary
|
// constant slots: int/rune literal, true/false/nil, and a unary
|
||||||
@@ -376,13 +347,6 @@ fn resolvetype(c: *cgen, t: *syntax.node) *syntax.node = {
|
|||||||
return cur;
|
return cur;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- struct registry ------------------------------------------------
|
|
||||||
//
|
|
||||||
// Per-file map from struct name → list of fields with computed offsets
|
|
||||||
// and sizes. Built when cgfile walks nkind.N_TYPEDECL with nkind.N_TSTRUCT lhs.
|
|
||||||
// nkind.N_DOT and nkind.N_ASSIGN consult this to resolve `s.field` for struct or
|
|
||||||
// *struct bases.
|
|
||||||
|
|
||||||
type fieldinfo = struct {
|
type fieldinfo = struct {
|
||||||
fname: str,
|
fname: str,
|
||||||
foff: i32,
|
foff: i32,
|
||||||
@@ -399,8 +363,6 @@ type structinfo = struct {
|
|||||||
sinext: *structinfo,
|
sinext: *structinfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- locals / frame --------------------------------------------------
|
|
||||||
|
|
||||||
type local = struct {
|
type local = struct {
|
||||||
name: str,
|
name: str,
|
||||||
off: i32,
|
off: i32,
|
||||||
@@ -764,8 +726,6 @@ fn localfind(c: *cgen, name: str) i32 = {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- emit helpers ---------------------------------------------------
|
|
||||||
|
|
||||||
// Cgfn defers its prologue (TEXT / SUBQ) until after the body so the
|
// Cgfn defers its prologue (TEXT / SUBQ) until after the body so the
|
||||||
// frame size reflects every emit-time localadd — the scanlocals pre-
|
// frame size reflects every emit-time localadd — the scanlocals pre-
|
||||||
// pass that previously pre-computed it was dropped per #15/#26c. The
|
// pass that previously pre-computed it was dropped per #15/#26c. The
|
||||||
@@ -942,10 +902,6 @@ fn mkscratchname(c: *cgen, prefix: str) str = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- string interning ------------------------------------------------
|
|
||||||
//
|
|
||||||
// streq is provided by sym.ww and reused here.
|
|
||||||
|
|
||||||
// internstrlit — return a stable label for `bytes`. Dedups by content
|
// internstrlit — return a stable label for `bytes`. Dedups by content
|
||||||
// so identical literals share storage.
|
// so identical literals share storage.
|
||||||
fn internstrlit(c: *cgen, bytes: str) str = {
|
fn internstrlit(c: *cgen, bytes: str) str = {
|
||||||
@@ -1008,8 +964,6 @@ fn letscalarprim(nm: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// letfloatprim — float type-name keywords. f32 → 4B slot, f64 → 8B.
|
|
||||||
// Returns the slot size or 0 if not a float type.
|
|
||||||
fn letfloatprim(nm: str) i32 = {
|
fn letfloatprim(nm: str) i32 = {
|
||||||
if (syntax.streq(nm, "f32")) { return 4; };
|
if (syntax.streq(nm, "f32")) { return 4; };
|
||||||
if (syntax.streq(nm, "f64")) { return 8; };
|
if (syntax.streq(nm, "f64")) { return 8; };
|
||||||
@@ -1019,11 +973,6 @@ fn letfloatprim(nm: str) i32 = {
|
|||||||
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
|
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
|
||||||
// the type isn't yet supported as a writable global. Walks type
|
// the type isn't yet supported as a writable global. Walks type
|
||||||
// aliases so byte output matches C cgen, which resolves Type kinds.
|
// aliases so byte output matches C cgen, which resolves Type kinds.
|
||||||
// 4 → f32 (literal init supported)
|
|
||||||
// 8 → scalar or f64 (literal init supported)
|
|
||||||
// 16 → str (only zero-init / nil / "" supported)
|
|
||||||
// 24 → slice (only zero-init supported)
|
|
||||||
// varies → struct (zero-init only; field reads/scalar-field writes)
|
|
||||||
fn letemitsize(c: *cgen, d: *syntax.node) i32 = {
|
fn letemitsize(c: *cgen, d: *syntax.node) i32 = {
|
||||||
if (d == nil) { return 0; };
|
if (d == nil) { return 0; };
|
||||||
let t: *syntax.node = d.lhs;
|
let t: *syntax.node = d.lhs;
|
||||||
@@ -2024,7 +1973,6 @@ fn emitarraylitbytes(c: *cgen, arrt: *syntax.tinfo, rhs: *syntax.node,
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (eu != nil && eu.kind == syntax.tykind.TY_STRUCT) {
|
if (eu != nil && eu.kind == syntax.tykind.TY_STRUCT) {
|
||||||
// Validate: every element must be N_STRUCTLIT (after N_CAST).
|
|
||||||
let idx: i32 = 0;
|
let idx: i32 = 0;
|
||||||
let last_ev: *syntax.node = nil;
|
let last_ev: *syntax.node = nil;
|
||||||
let e: *syntax.node = rhs.list;
|
let e: *syntax.node = rhs.list;
|
||||||
@@ -2113,7 +2061,6 @@ fn emitarraylitbytes(c: *cgen, arrt: *syntax.tinfo, rhs: *syntax.node,
|
|||||||
|
|
||||||
if (syntax.typeisfloat(au.sub)) {
|
if (syntax.typeisfloat(au.sub)) {
|
||||||
let isf32: bool = syntax.typeisf32(au.sub);
|
let isf32: bool = syntax.typeisf32(au.sub);
|
||||||
// Validate.
|
|
||||||
let idx: i32 = 0;
|
let idx: i32 = 0;
|
||||||
let e: *syntax.node = rhs.list;
|
let e: *syntax.node = rhs.list;
|
||||||
for (e != nil && idx < alen) {
|
for (e != nil && idx < alen) {
|
||||||
@@ -2211,7 +2158,6 @@ fn emitarraylitbytes(c: *cgen, arrt: *syntax.tinfo, rhs: *syntax.node,
|
|||||||
let e: *syntax.node = rhs.list;
|
let e: *syntax.node = rhs.list;
|
||||||
let last: u64 = 0u64;
|
let last: u64 = 0u64;
|
||||||
let repeat: bool = false;
|
let repeat: bool = false;
|
||||||
// Validate first.
|
|
||||||
for (e != nil && idx < alen) {
|
for (e != nil && idx < alen) {
|
||||||
if (e.kind == syntax.nkind.N_FIELD) {
|
if (e.kind == syntax.nkind.N_FIELD) {
|
||||||
if (syntax.streq(e.str, "...")) { repeat = true; break; };
|
if (syntax.streq(e.str, "...")) { repeat = true; break; };
|
||||||
@@ -2551,7 +2497,6 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *syntax.tinfo,
|
|||||||
os.write(2, m.ptr, m.len: u64);
|
os.write(2, m.ptr, m.len: u64);
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
};
|
};
|
||||||
// Writable backing data.
|
|
||||||
emitline("DATAW ");
|
emitline("DATAW ");
|
||||||
emitfnname(c, name, module);
|
emitfnname(c, name, module);
|
||||||
emitline(".d(SB),\"");
|
emitline(".d(SB),\"");
|
||||||
@@ -3494,12 +3439,9 @@ fn emitdatasection(c: *cgen) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fn return-type map ---------------------------------------------
|
// fnret decides whether to shuffle (AX, DX) → (AX, BX) after a CALL —
|
||||||
//
|
// needed for str-returning fns so the value flows through cgen as the
|
||||||
// Per-file: ident → ret-type-node. Used to decide whether to shuffle
|
// canonical (AX, BX) str pair.
|
||||||
// (AX, DX) → (AX, BX) after a CALL — needed for str-returning fns so
|
|
||||||
// the value flows through cgen as the canonical (AX, BX) str pair.
|
|
||||||
|
|
||||||
type fnret = struct {
|
type fnret = struct {
|
||||||
fname: str,
|
fname: str,
|
||||||
fmod: str,
|
fmod: str,
|
||||||
@@ -3670,12 +3612,8 @@ fn fnparamslookupmod(c: *cgen, name: str, mod: str) *syntax.node = {
|
|||||||
return fnparamslookup(c, name);
|
return fnparamslookup(c, name);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- def-constant registry ------------------------------------------
|
|
||||||
//
|
|
||||||
// `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an
|
// `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an
|
||||||
// ident reference loads it via `MOVQ NAME(SB), AX`. We collect them at
|
// ident reference loads it via `MOVQ NAME(SB), AX`.
|
||||||
// file load and consult on nkind.N_IDENT lookup.
|
|
||||||
|
|
||||||
type defent = struct {
|
type defent = struct {
|
||||||
dname: str,
|
dname: str,
|
||||||
dmod: str, // originating module (`// MODULE: foo`), or empty
|
dmod: str, // originating module (`// MODULE: foo`), or empty
|
||||||
@@ -3819,8 +3757,6 @@ fn defisaddressable(c: *cgen, opnd: *syntax.node) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- module-private symbol map --------------------------------------
|
|
||||||
//
|
|
||||||
// Every non-FFI top-level fn decl lives in its module's namespace —
|
// Every non-FFI top-level fn decl lives in its module's namespace —
|
||||||
// cgen mangles the leaf to `<module>.<name>` at the def site (TEXT)
|
// cgen mangles the leaf to `<module>.<name>` at the def site (TEXT)
|
||||||
// and at every call/load site, so cross-module same-leaf fns (lib/os
|
// and at every call/load site, so cross-module same-leaf fns (lib/os
|
||||||
@@ -4046,8 +3982,6 @@ fn emitfnname(c: *cgen, ident: str, hint: str) void = {
|
|||||||
emitbytes( ident.ptr, ident.len: u64);
|
emitbytes( ident.ptr, ident.len: u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- FFI map ---------------------------------------------------------
|
|
||||||
|
|
||||||
fn fficollect(c: *cgen, file: *syntax.node) void = {
|
fn fficollect(c: *cgen, file: *syntax.node) void = {
|
||||||
c.ffis = nil;
|
c.ffis = nil;
|
||||||
if (file == nil) { return; };
|
if (file == nil) { return; };
|
||||||
@@ -4085,8 +4019,6 @@ fn ffiresolve(c: *cgen, ident: str) str = {
|
|||||||
return ident;
|
return ident;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- ABI argreg helpers ---------------------------------------------
|
|
||||||
|
|
||||||
fn argregname(i: i32) str = {
|
fn argregname(i: i32) str = {
|
||||||
if (i == 0) { return "DI"; };
|
if (i == 0) { return "DI"; };
|
||||||
if (i == 1) { return "SI"; };
|
if (i == 1) { return "SI"; };
|
||||||
|
|||||||
@@ -1,24 +1,9 @@
|
|||||||
// selfhost/cmd/wcc/cgendecl.ww — split out of cgen.ww.
|
|
||||||
//
|
|
||||||
// Houses the top-level emission glue:
|
|
||||||
// - cgfnparams: parameter spilling per SysV
|
|
||||||
// - cgfn: fn body emit (TEXT/SUBQ patched after body), prologue
|
|
||||||
// deferred via cgen.ww's cgoutstate so the frame size
|
|
||||||
// reflects every emit-time localadd (#15/#26c)
|
|
||||||
// - cgfile: file-level entry (the exported driver)
|
|
||||||
//
|
|
||||||
// Bundler pulls this in transitively via cgen.ww; consumers don't
|
|
||||||
// need to `use cgendecl;` directly.
|
|
||||||
|
|
||||||
package wcc;
|
package wcc;
|
||||||
|
|
||||||
import os;
|
import os;
|
||||||
import syntax;
|
import syntax;
|
||||||
import strconv;
|
import strconv;
|
||||||
|
|
||||||
|
|
||||||
// ---- function-level cgen ---------------------------------------------
|
|
||||||
|
|
||||||
fn cgfnparams(c: *cgen, params: *syntax.node) void = {
|
fn cgfnparams(c: *cgen, params: *syntax.node) void = {
|
||||||
let p: *syntax.node = params;
|
let p: *syntax.node = params;
|
||||||
// sret (#23): RDI is consumed by the hidden dest pointer
|
// sret (#23): RDI is consumed by the hidden dest pointer
|
||||||
@@ -580,8 +565,6 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (c.lastwasreturn == 0) {
|
if (c.lastwasreturn == 0) {
|
||||||
// Run any registered defers in LIFO order before the
|
|
||||||
// implicit return.
|
|
||||||
rundefers(c);
|
rundefers(c);
|
||||||
// Zero AX before the fall-through return — matches cstage,
|
// Zero AX before the fall-through return — matches cstage,
|
||||||
// which always emits this so void-returning fns don't leak
|
// which always emits this so void-returning fns don't leak
|
||||||
@@ -629,8 +612,6 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = {
|
|||||||
cgout_flush();
|
cgout_flush();
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- file-level entry ------------------------------------------------
|
|
||||||
|
|
||||||
export fn cgfile(c: *cgen, file: *syntax.node) void = {
|
export fn cgfile(c: *cgen, file: *syntax.node) void = {
|
||||||
if (file == nil) { return; };
|
if (file == nil) { return; };
|
||||||
c.strlits = nil;
|
c.strlits = nil;
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww.
|
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww.
|
||||||
//
|
//
|
||||||
// cgexpr is a thin dispatcher over n.kind; each non-trivial branch
|
|
||||||
// lives in a per-kind helper (cgstrlit, cgident, cgindex, cgmatch,
|
|
||||||
// cgdot, cgun, cgbin, cgcall, cgassign). Trivial literal loads
|
|
||||||
// (nkind.N_INTLIT, nkind.N_RUNELIT, nkind.N_TRUE/FALSE/NIL, nkind.N_CAST) stay inline.
|
|
||||||
//
|
|
||||||
// The remainder of cgen lives in cgen.ww (foundation: types, emit
|
// The remainder of cgen lives in cgen.ww (foundation: types, emit
|
||||||
// primitives, the collect* tables, FFI/module maps) and cgenstmt.ww
|
// primitives, the collect* tables, FFI/module maps) and cgenstmt.ww
|
||||||
// (cgstmt).
|
// (cgstmt).
|
||||||
@@ -683,9 +678,6 @@ fn cgtryunw(c: *cgen, n: *syntax.node) void = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn cgtypetest(c: *cgen, n: *syntax.node) void = {
|
fn cgtypetest(c: *cgen, n: *syntax.node) void = {
|
||||||
// `e is T` — load the lhs's tag, compare against T's variant
|
|
||||||
// index, set AX = (tag == idx). Result type is bool.
|
|
||||||
//
|
|
||||||
// Slot resolution is inlined (rather than factored into a helper
|
// Slot resolution is inlined (rather than factored into a helper
|
||||||
// with output parameters): wwstage cgen has a trap with i32
|
// with output parameters): wwstage cgen has a trap with i32
|
||||||
// stored via *i32 in this context — direct assignment of the
|
// stored via *i32 in this context — direct assignment of the
|
||||||
@@ -1077,9 +1069,6 @@ fn cgcast(c: *cgen, n: *syntax.node) void = {
|
|||||||
else { if (lk == syntax.nkind.N_TENUM) { leaf_tn = leaf_tn.lhs; }
|
else { if (lk == syntax.nkind.N_TENUM) { leaf_tn = leaf_tn.lhs; }
|
||||||
else { if (lk == syntax.nkind.N_TNAME) {
|
else { if (lk == syntax.nkind.N_TNAME) {
|
||||||
let lnm: str = leaf_tn.str;
|
let lnm: str = leaf_tn.str;
|
||||||
// This is an alias chase loop
|
|
||||||
// — primsize is the leaf-primitive break test the loop
|
|
||||||
// wraps (aliaslookup advances the cursor on a miss).
|
|
||||||
if (primsize(lnm) > 0) { break; };
|
if (primsize(lnm) > 0) { break; };
|
||||||
let lal: *syntax.node = aliaslookup(c, lnm);
|
let lal: *syntax.node = aliaslookup(c, lnm);
|
||||||
if (lal == nil) { leaf_tn = nil; }
|
if (lal == nil) { leaf_tn = nil; }
|
||||||
@@ -2062,9 +2051,6 @@ fn cgplaceaddr(c: *cgen, n: *syntax.node, dstreg: str) bool = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn cgindex(c: *cgen, n: *syntax.node) void = {
|
fn cgindex(c: *cgen, n: *syntax.node) void = {
|
||||||
// Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL,
|
|
||||||
// str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast
|
|
||||||
// path when the base is a bare ident (mem.ww shape).
|
|
||||||
let base: *syntax.node = n.lhs;
|
let base: *syntax.node = n.lhs;
|
||||||
let idx: *syntax.node = n.rhs;
|
let idx: *syntax.node = n.rhs;
|
||||||
// Direct non-ident index bases that match none of the typed arms
|
// Direct non-ident index bases that match none of the typed arms
|
||||||
@@ -2512,7 +2498,6 @@ fn cgindex(c: *cgen, n: *syntax.node) void = {
|
|||||||
emitline("\t(BX), AX\n");
|
emitline("\t(BX), AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// Generic fallback when base isn't a plain ident.
|
|
||||||
// #135: N_DOT base on `[N]T` field needs the field's ADDRESS,
|
// #135: N_DOT base on `[N]T` field needs the field's ADDRESS,
|
||||||
// not its value. cgexpr would auto-deref + load the 8-byte value
|
// not its value. cgexpr would auto-deref + load the 8-byte value
|
||||||
// as if it were a pointer. dotbaseaddr emits the address inline.
|
// as if it were a pointer. dotbaseaddr emits the address inline.
|
||||||
@@ -2866,7 +2851,6 @@ fn cgslice(c: *cgen, n: *syntax.node) void = {
|
|||||||
let es60: *syntax.tinfo = tichase(bu60.sub);
|
let es60: *syntax.tinfo = tichase(bu60.sub);
|
||||||
if (es60 != nil) { esz = es60.size: i32; };
|
if (es60 != nil) { esz = es60.size: i32; };
|
||||||
};
|
};
|
||||||
// base address
|
|
||||||
if (baselocal != nil) {
|
if (baselocal != nil) {
|
||||||
let tn: *syntax.node = baselocal.tnode;
|
let tn: *syntax.node = baselocal.tnode;
|
||||||
let isarray: bool = false;
|
let isarray: bool = false;
|
||||||
@@ -2936,11 +2920,9 @@ fn cgslice(c: *cgen, n: *syntax.node) void = {
|
|||||||
};
|
};
|
||||||
};};};};
|
};};};};
|
||||||
emitline("\tPUSHQ\tAX\n");
|
emitline("\tPUSHQ\tAX\n");
|
||||||
// lo (default 0)
|
|
||||||
if (lo != nil) { cgexpr(c, lo); }
|
if (lo != nil) { cgexpr(c, lo); }
|
||||||
else { emitline("\tMOVQ\t$0, AX\n"); };
|
else { emitline("\tMOVQ\t$0, AX\n"); };
|
||||||
emitline("\tPUSHQ\tAX\n");
|
emitline("\tPUSHQ\tAX\n");
|
||||||
// hi (default base length)
|
|
||||||
if (hi != nil) {
|
if (hi != nil) {
|
||||||
cgexpr(c, hi);
|
cgexpr(c, hi);
|
||||||
} else { if (baselocal != nil) {
|
} else { if (baselocal != nil) {
|
||||||
@@ -3140,8 +3122,6 @@ fn matcharmwant(c: *cgen, scrutt: *syntax.node, pat: *syntax.node) i32 = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn cgmatch(c: *cgen, n: *syntax.node) void = {
|
fn cgmatch(c: *cgen, n: *syntax.node) void = {
|
||||||
// match (e) { case let v: T => stmt; ... }
|
|
||||||
//
|
|
||||||
// Read the tagged-union slot and dispatch by tag. Slot
|
// Read the tagged-union slot and dispatch by tag. Slot
|
||||||
// layout: [+0]=tag, [+8]=value0, [+16]=value1. Bindings
|
// layout: [+0]=tag, [+8]=value0, [+16]=value1. Bindings
|
||||||
// (`case let v: T =>`) get a fresh local slot loaded from
|
// (`case let v: T =>`) get a fresh local slot loaded from
|
||||||
@@ -3415,7 +3395,6 @@ fn cgmatch(c: *cgen, n: *syntax.node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// Bind `let v: T` from the slot, if requested.
|
|
||||||
let bn: str = cs.str;
|
let bn: str = cs.str;
|
||||||
if (bn.len > 0) {
|
if (bn.len > 0) {
|
||||||
if (pat != nil) {
|
if (pat != nil) {
|
||||||
@@ -3468,7 +3447,6 @@ fn cgmatch(c: *cgen, n: *syntax.node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// Body. Match arms are statements; we cgstmt them.
|
|
||||||
if (cs.body != nil) { cgstmt(c, cs.body); };
|
if (cs.body != nil) { cgstmt(c, cs.body); };
|
||||||
// Restore the locals head — pop everything the arm pushed
|
// Restore the locals head — pop everything the arm pushed
|
||||||
// so post-match code resolves names to their original (outer)
|
// so post-match code resolves names to their original (outer)
|
||||||
@@ -5299,10 +5277,6 @@ fn cgdot(c: *cgen, n: *syntax.node) void = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn cgun(c: *cgen, n: *syntax.node) void = {
|
fn cgun(c: *cgen, n: *syntax.node) void = {
|
||||||
// Match C cgen ordering: evaluate operand first (load into AX),
|
|
||||||
// then apply the unary op. AMP / STAR override AX with the
|
|
||||||
// address / deref. The wasted load before AMP keeps our asm
|
|
||||||
// byte-identical to the C version.
|
|
||||||
let fk: i32 = 0;
|
let fk: i32 = 0;
|
||||||
if (n.lhs != nil) {
|
if (n.lhs != nil) {
|
||||||
let lt: *syntax.tinfo = n.lhs.type_: *syntax.tinfo;
|
let lt: *syntax.tinfo = n.lhs.type_: *syntax.tinfo;
|
||||||
|
|||||||
@@ -1,21 +1,9 @@
|
|||||||
// selfhost/cmd/wcc/cgenstmt.ww — split out of cgen.ww.
|
|
||||||
//
|
|
||||||
// cgstmt is a thin dispatcher over n.kind; each branch defers to a
|
|
||||||
// per-kind helper: cgblock, cgreturn, cgexprstmt, cglet, cgif, cgfor,
|
|
||||||
// cgmassign, cgbreak, cgcontinue.
|
|
||||||
//
|
|
||||||
// The expression generator (cgexpr) lives in cgenexpr.ww; the
|
|
||||||
// foundation (types, emit primitives, collect* tables, FFI/module
|
|
||||||
// maps) lives in cgen.ww.
|
|
||||||
|
|
||||||
package wcc;
|
package wcc;
|
||||||
|
|
||||||
import os;
|
import os;
|
||||||
import syntax;
|
import syntax;
|
||||||
import strconv;
|
import strconv;
|
||||||
|
|
||||||
// ---- statement cgen --------------------------------------------------
|
|
||||||
|
|
||||||
fn cgstmt(c: *cgen, n: *syntax.node) void = {
|
fn cgstmt(c: *cgen, n: *syntax.node) void = {
|
||||||
if (n == nil) { return; };
|
if (n == nil) { return; };
|
||||||
let k: syntax.nkind = n.kind;
|
let k: syntax.nkind = n.kind;
|
||||||
@@ -64,9 +52,8 @@ fn cgstmt(c: *cgen, n: *syntax.node) void = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn cgyield(c: *cgen, n: *syntax.node) void = {
|
fn cgyield(c: *cgen, n: *syntax.node) void = {
|
||||||
// Evaluate the value into AX (and BX for str), then JMP to the
|
// Falls through silently if there is no active match — should be
|
||||||
// enclosing match's end label. Falls through silently if there
|
// a checker error eventually.
|
||||||
// is no active match — should be a checker error eventually.
|
|
||||||
if (n.lhs != nil) { cgexpr(c, n.lhs); };
|
if (n.lhs != nil) { cgexpr(c, n.lhs); };
|
||||||
if (c.yieldtop > 0) {
|
if (c.yieldtop > 0) {
|
||||||
let tgt: str = c.yieldbuf[c.yieldtop - 1];
|
let tgt: str = c.yieldbuf[c.yieldtop - 1];
|
||||||
@@ -100,8 +87,6 @@ fn cgblock(c: *cgen, n: *syntax.node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// rundefers — emit cgexpr for every queued defer in LIFO order.
|
|
||||||
// Called from cgreturn and the cgfn implicit-return path.
|
|
||||||
fn rundefers(c: *cgen) void = {
|
fn rundefers(c: *cgen) void = {
|
||||||
let i: i32 = c.defertop - 1;
|
let i: i32 = c.defertop - 1;
|
||||||
for (i >= 0) {
|
for (i >= 0) {
|
||||||
@@ -3178,11 +3163,6 @@ fn cgfor(c: *cgen, n: *syntax.node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tuple-destructure assign: `a, b = call();`. The call's tuple
|
|
||||||
// return lands in (AX, DX); push DX to free it, store AX into
|
|
||||||
// the first lvalue, then pop DX into the second. Mirrors
|
|
||||||
// cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same
|
|
||||||
// as C — no fixture uses >2 today).
|
|
||||||
fn cgmassign(c: *cgen, n: *syntax.node) void = {
|
fn cgmassign(c: *cgen, n: *syntax.node) void = {
|
||||||
// #83: positional per-element destructure REASSIGN. Same cursor as
|
// #83: positional per-element destructure REASSIGN. Same cursor as
|
||||||
// cgmlet (and cgreturn; harec create_unpack_bindings,
|
// cgmlet (and cgreturn; harec create_unpack_bindings,
|
||||||
@@ -3394,17 +3374,6 @@ fn cgmassign(c: *cgen, n: *syntax.node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Multi-let from a tuple-returning call: `let n, s = call();` or
|
|
||||||
// `let (n, s) = call();`. wwstage has no checker, so each binding's
|
|
||||||
// type is taken from its explicit annotation (l.lhs) when present
|
|
||||||
// or inferred from the called fn's return-type tuple element.
|
|
||||||
//
|
|
||||||
// Per the AX:DX:CX:R8 return convention (mirrors C cgen nkind.N_MLET):
|
|
||||||
// (scalar, scalar) — AX → l0, DX → l1.
|
|
||||||
// (scalar, str) — AX → scalar slot, (DX, CX, R8) → str slot
|
|
||||||
// as (.ptr, .len, .cap). Position-agnostic — the
|
|
||||||
// regs are routed by element type, not by AX/DX.
|
|
||||||
// str IS []u8 (24B): cap rides R8 (#1/Phase 3, task #5).
|
|
||||||
fn cgmlet(c: *cgen, n: *syntax.node) void = {
|
fn cgmlet(c: *cgen, n: *syntax.node) void = {
|
||||||
let rhs: *syntax.node = n.rhs;
|
let rhs: *syntax.node = n.rhs;
|
||||||
if (rhs == nil) { return; };
|
if (rhs == nil) { return; };
|
||||||
@@ -3958,7 +3927,6 @@ fn cgforrange(c: *cgen, n: *syntax.node) void = {
|
|||||||
nbinds = 1;
|
nbinds = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// init: ioff(BP) = 0
|
|
||||||
emitline("\tMOVQ\t$0, ");
|
emitline("\tMOVQ\t$0, ");
|
||||||
emitoff(ioff: i64);
|
emitoff(ioff: i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
@@ -4290,5 +4258,3 @@ fn cgcontinue(c: *cgen, n: *syntax.node) void = {
|
|||||||
c.lastwasreturn = 0;
|
c.lastwasreturn = 0;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,3 @@
|
|||||||
// selfhost/cmd/wcc/cgenutil.ww — split out of cgen.ww.
|
|
||||||
//
|
|
||||||
// General helpers used across cgenexpr / cgenstmt / cgendecl:
|
|
||||||
// - pushargsrev: per-call arg pushing
|
|
||||||
// - type predicates: isstr*/isslice*/istagged*/nodeis* families
|
|
||||||
// - field ops: fieldloadop, fieldstoreop
|
|
||||||
// - index helpers: elemsizeof, elemsizeofc
|
|
||||||
// - slot sizing: structlookup, primsize, slotsize, fieldsize,
|
|
||||||
// registerstruct, collectstructs
|
|
||||||
// - rhs helpers: taggedvariantindex
|
|
||||||
//
|
|
||||||
// Bundler pulls this in transitively via cgen.ww; consumers don't
|
// Bundler pulls this in transitively via cgen.ww; consumers don't
|
||||||
// need to `use cgenutil;` directly.
|
// need to `use cgenutil;` directly.
|
||||||
|
|
||||||
@@ -18,8 +7,6 @@ import os;
|
|||||||
import syntax;
|
import syntax;
|
||||||
import strconv;
|
import strconv;
|
||||||
|
|
||||||
// ---- variadic-call helpers (Hare-style `T...` param) -----------------
|
|
||||||
|
|
||||||
// slicewrap — synthesise an N_TSLICE node wrapping the given element
|
// slicewrap — synthesise an N_TSLICE node wrapping the given element
|
||||||
// type AST. Used by the Hare-style variadic path so the local entry
|
// type AST. Used by the Hare-style variadic path so the local entry
|
||||||
// for the param (callee side) and the call-site slice descriptor
|
// for the param (callee side) and the call-site slice descriptor
|
||||||
@@ -263,8 +250,6 @@ fn calleecvariadic(c: *cgen, callee: *syntax.node, nfixed_out: *i32) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- expression cgen -------------------------------------------------
|
|
||||||
|
|
||||||
// pushargsrev — recursively walks the arg list, evaluates rightmost
|
// pushargsrev — recursively walks the arg list, evaluates rightmost
|
||||||
// first, and pushes. str args take two slots (ptr in AX, len in BX);
|
// first, and pushes. str args take two slots (ptr in AX, len in BX);
|
||||||
// the order on the stack so a left-to-right pop into argregs lands
|
// the order on the stack so a left-to-right pop into argregs lands
|
||||||
@@ -787,7 +772,6 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool,
|
|||||||
let es60: *syntax.tinfo = tichase(bu60.sub);
|
let es60: *syntax.tinfo = tichase(bu60.sub);
|
||||||
if (es60 != nil) { esz = es60.size: i32; };
|
if (es60 != nil) { esz = es60.size: i32; };
|
||||||
};
|
};
|
||||||
// base address → push
|
|
||||||
if (baselocal != nil) {
|
if (baselocal != nil) {
|
||||||
let tn: *syntax.node = baselocal.tnode;
|
let tn: *syntax.node = baselocal.tnode;
|
||||||
let isarr60: bool = false;
|
let isarr60: bool = false;
|
||||||
@@ -828,7 +812,6 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool,
|
|||||||
cgexpr(c, base);
|
cgexpr(c, base);
|
||||||
};};};
|
};};};
|
||||||
emitline("\tPUSHQ\tAX\n");
|
emitline("\tPUSHQ\tAX\n");
|
||||||
// hi (default base length) → push
|
|
||||||
if (hi != nil) {
|
if (hi != nil) {
|
||||||
cgexpr(c, hi);
|
cgexpr(c, hi);
|
||||||
} else { if (baselocal != nil) {
|
} else { if (baselocal != nil) {
|
||||||
@@ -945,7 +928,6 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool,
|
|||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
};};};};};
|
};};};};};
|
||||||
emitline("\tPUSHQ\tAX\n");
|
emitline("\tPUSHQ\tAX\n");
|
||||||
// lo (default 0) → AX
|
|
||||||
if (lo != nil) { cgexpr(c, lo); }
|
if (lo != nil) { cgexpr(c, lo); }
|
||||||
else { emitline("\tMOVQ\t$0, AX\n"); };
|
else { emitline("\tMOVQ\t$0, AX\n"); };
|
||||||
emitline("\tPOPQ\tBX\n"); // hi
|
emitline("\tPOPQ\tBX\n"); // hi
|
||||||
@@ -2098,14 +2080,9 @@ fn elemsizeofc(c: *cgen, t: *syntax.node) i32 = {
|
|||||||
return slotsize(c, elem);
|
return slotsize(c, elem);
|
||||||
};
|
};
|
||||||
|
|
||||||
// nodeisunsigned — best-effort cgen-time inference from the AST. We
|
// nodeisunsigned — best-effort cgen-time inference from the AST
|
||||||
// walk surface nodes (N_DOT now reads n.type_ — #55 A.6.3g):
|
// (N_DOT reads the checker-stamped n.type_ — #55 A.6.3g).
|
||||||
// nkind.N_INTLIT — never marked unsigned (no tsuffix plumbing yet)
|
// nkind.N_INTLIT is never marked unsigned (no tsuffix plumbing yet).
|
||||||
// nkind.N_IDENT — look up the local's declared type
|
|
||||||
// nkind.N_DOT — read the checker-stamped n.type_ (#55 A.6.3g)
|
|
||||||
// nkind.N_BIN / nkind.N_UN — recurse: unsigned if either operand is unsigned
|
|
||||||
// nkind.N_CAST — use the cast target type
|
|
||||||
//
|
|
||||||
// Conservative: if we can't tell, return false (signed). The cost of
|
// Conservative: if we can't tell, return false (signed). The cost of
|
||||||
// being wrong here is byte-different asm vs C, not bad runtime.
|
// being wrong here is byte-different asm vs C, not bad runtime.
|
||||||
fn nodeisunsigned(c: *cgen, n: *syntax.node) bool = {
|
fn nodeisunsigned(c: *cgen, n: *syntax.node) bool = {
|
||||||
@@ -2191,8 +2168,6 @@ fn nodeprimwidth(c: *cgen, n: *syntax.node) i32 = {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- type-driven slot sizing ----------------------------------------
|
|
||||||
|
|
||||||
// structnaturalsize — type-natural size of `si`, i.e. max(foff +
|
// structnaturalsize — type-natural size of `si`, i.e. max(foff +
|
||||||
// fsz) across declared fields, UNROUNDED. This is the memory-copy
|
// fsz) across declared fields, UNROUNDED. This is the memory-copy
|
||||||
// extent: cstage copies exactly these bytes for the >24B sret
|
// extent: cstage copies exactly these bytes for the >24B sret
|
||||||
@@ -2594,11 +2569,8 @@ fn structsamemod(c: *cgen, name: str) *structinfo = {
|
|||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
// primsize — size in bytes of a primitive type name (or 0 if not
|
// fldnumidx — used by cgdot to dispatch `t.0` / `t.1` against an
|
||||||
// recognised as a primitive — the caller falls back to other paths).
|
// nkind.N_TTUPLE local without pulling in strconv.
|
||||||
// fldnumidx — parse a tuple field name like "0" / "1" / "12" into an
|
|
||||||
// index, or -1 if not all-digits. Used by cgdot to dispatch
|
|
||||||
// `t.0` / `t.1` against an nkind.N_TTUPLE local without pulling in strconv.
|
|
||||||
fn fldnumidx(s: str) i32 = {
|
fn fldnumidx(s: str) i32 = {
|
||||||
if (s.len == 0) { return -1; };
|
if (s.len == 0) { return -1; };
|
||||||
let r: i32 = 0;
|
let r: i32 = 0;
|
||||||
@@ -5141,7 +5113,6 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *syntax.tinfo, src: *syntax.node, slot_of
|
|||||||
}; };
|
}; };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// Struct payload (literal or ident).
|
|
||||||
let sname: str = rhsstructpayload(c, src);
|
let sname: str = rhsstructpayload(c, src);
|
||||||
if (sname.len > 0) {
|
if (sname.len > 0) {
|
||||||
let si: *structinfo = structlookup(c, sname);
|
let si: *structinfo = structlookup(c, sname);
|
||||||
|
|||||||
@@ -1,21 +1,4 @@
|
|||||||
// selfhost/cmd/wcc/check.ww — minimal port of cmd/wcc/check.c.
|
// Port of cmd/wcc/check.c.
|
||||||
//
|
|
||||||
// Status: name-resolution + primitive-type seeding only. Full type
|
|
||||||
// inference, conversion rules, tagged-union dispatch typing, return-
|
|
||||||
// type checking, etc. all live in cmd/wcc/check.c (937 lines) and
|
|
||||||
// will land here in subsequent commits.
|
|
||||||
//
|
|
||||||
// What this version does:
|
|
||||||
// 1. Creates a top scope and seeds it with primitive type names so
|
|
||||||
// `i32`, `str`, `*u8` etc. resolve.
|
|
||||||
// 2. Walks the file's top-level decls (use/def/type/fn/let) and
|
|
||||||
// installs Sym entries for each.
|
|
||||||
// 3. Recursively walks fn bodies; for every nkind.N_IDENT used as an
|
|
||||||
// expression or as a type name, looks it up and counts the
|
|
||||||
// resolved vs. unresolved.
|
|
||||||
// 4. Returns a summary the caller (wwdump -r) prints; the test
|
|
||||||
// asserts unresolved == 0 on every selfhost fixture, which is
|
|
||||||
// the floor signal that the frontend can name-resolve real ww.
|
|
||||||
|
|
||||||
package wcc;
|
package wcc;
|
||||||
|
|
||||||
@@ -91,8 +74,6 @@ fn circularnamed(c: *checker, t: *syntax.tinfo, n: *syntax.node) bool = {
|
|||||||
os.exit(1);
|
os.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// seedprimitives — install the built-in type names so `i32`, `str`,
|
|
||||||
// etc. can be looked up like ordinary symbols.
|
|
||||||
fn seedprimitives(c: *checker) void = {
|
fn seedprimitives(c: *checker) void = {
|
||||||
syntax.scopedefine(c.top, "void", syntax.skind.SK_TYPE, c.tc.tyvoid, nil);
|
syntax.scopedefine(c.top, "void", syntax.skind.SK_TYPE, c.tc.tyvoid, nil);
|
||||||
syntax.scopedefine(c.top, "bool", syntax.skind.SK_TYPE, c.tc.tybool, nil);
|
syntax.scopedefine(c.top, "bool", syntax.skind.SK_TYPE, c.tc.tybool, nil);
|
||||||
@@ -822,8 +803,7 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
|||||||
// free identifier.
|
// free identifier.
|
||||||
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
||||||
// A.6.0: branch returns early; stamp here so the post-walk
|
// A.6.0: branch returns early; stamp here so the post-walk
|
||||||
// dispatch below sees N_DOT covered. exprtype N_DOT arm is
|
// dispatch below sees N_DOT covered.
|
||||||
// added in A.6.1; for now this is a no-op nil return.
|
|
||||||
let _t: *syntax.node = exprtype(c, n, nil);
|
let _t: *syntax.node = exprtype(c, n, nil);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -957,15 +937,12 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- type-level helpers (AST-level, no resolved tinfo) --------------
|
|
||||||
//
|
|
||||||
// The selfhost check operates on AST type expressions rather than
|
// The selfhost check operates on AST type expressions rather than
|
||||||
// resolved Type structs. These helpers mirror what cmd/wcc/check.c
|
// resolved Type structs. These helpers mirror what cmd/wcc/check.c
|
||||||
// does with tinfo, but only on the subset of cases this checker
|
// does with tinfo, but only on the subset of cases this checker
|
||||||
// needs to enforce: tagged-union exhaustiveness, ? subset
|
// needs to enforce: tagged-union exhaustiveness, ? subset
|
||||||
// propagation, and !-flag semantics.
|
// propagation, and !-flag semantics.
|
||||||
|
|
||||||
// unwrapbang — strip an nkind.N_TBANG wrapper; leaves other nodes alone.
|
|
||||||
fn unwrapbang(n: *syntax.node) *syntax.node = {
|
fn unwrapbang(n: *syntax.node) *syntax.node = {
|
||||||
if (n == nil) { return nil; };
|
if (n == nil) { return nil; };
|
||||||
if (n.kind == syntax.nkind.N_TBANG) { return n.lhs; };
|
if (n.kind == syntax.nkind.N_TBANG) { return n.lhs; };
|
||||||
@@ -1050,10 +1027,6 @@ fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
|
|||||||
return s;
|
return s;
|
||||||
};
|
};
|
||||||
|
|
||||||
// resolvealias — if n is an nkind.N_TNAME pointing at a typedecl, return
|
|
||||||
// the typedecl's body (possibly recursively). Pass-through for any
|
|
||||||
// other node. The chain stops once we hit a non-nkind.N_TNAME node or a
|
|
||||||
// name we can't resolve.
|
|
||||||
fn resolvealias(c: *checker, n: *syntax.node) *syntax.node = {
|
fn resolvealias(c: *checker, n: *syntax.node) *syntax.node = {
|
||||||
let cur: *syntax.node = n;
|
let cur: *syntax.node = n;
|
||||||
for (cur != nil) {
|
for (cur != nil) {
|
||||||
@@ -1274,9 +1247,6 @@ fn scruttype(c: *checker, e: *syntax.node) *syntax.node = {
|
|||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
// mktname — fabricate an nkind.N_TNAME node with str = `nm`. Used by
|
|
||||||
// exprtype to return primitive type nodes for literal
|
|
||||||
// expressions. The arena keeps them around as long as the checker.
|
|
||||||
fn mktname(c: *checker, nm: str) *syntax.node = {
|
fn mktname(c: *checker, nm: str) *syntax.node = {
|
||||||
let n: *syntax.node = syntax.newnode(syntax.nkind.N_TNAME, "", 0, 0);
|
let n: *syntax.node = syntax.newnode(syntax.nkind.N_TNAME, "", 0, 0);
|
||||||
n.str = nm;
|
n.str = nm;
|
||||||
@@ -4692,9 +4662,6 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
// isuntypedint / is_str_like / is_bool_like — helpers used
|
|
||||||
// by the assignability check below to allow common AST shapes
|
|
||||||
// through without needing real type inference.
|
|
||||||
fn isuntypedint(t: *syntax.node) bool = {
|
fn isuntypedint(t: *syntax.node) bool = {
|
||||||
if (t == nil) { return false; };
|
if (t == nil) { return false; };
|
||||||
if (t.kind != syntax.nkind.N_TNAME) { return false; };
|
if (t.kind != syntax.nkind.N_TNAME) { return false; };
|
||||||
@@ -5386,14 +5353,10 @@ fn isassignable(c: *checker, dst: *syntax.node, src: *syntax.node, confident: *b
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- match exhaustiveness --------------------------------------------
|
|
||||||
//
|
|
||||||
// For every match arm, verify that every variant of the scrutinee's
|
// For every match arm, verify that every variant of the scrutinee's
|
||||||
// tagged-union type is handled by some case (or a default arm
|
// tagged-union type is handled by some case (or a default arm
|
||||||
// exists). Multi-pattern `case A | B =>` covers all alts.
|
// exists). Multi-pattern `case A | B =>` covers all alts.
|
||||||
|
|
||||||
// qualleaf — rightmost dotted segment of a (possibly module-qualified)
|
|
||||||
// type name; the whole name when unqualified.
|
|
||||||
fn qualleaf(nm: str) str = {
|
fn qualleaf(nm: str) str = {
|
||||||
let dotidx: i32 = -1;
|
let dotidx: i32 = -1;
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
@@ -5408,8 +5371,6 @@ fn qualleaf(nm: str) str = {
|
|||||||
return leaf;
|
return leaf;
|
||||||
};
|
};
|
||||||
|
|
||||||
// qualmod — module qualifier of a type name (segment before the
|
|
||||||
// rightmost '.'), or `defmod` when unqualified.
|
|
||||||
fn qualmod(nm: str, defmod: str) str = {
|
fn qualmod(nm: str, defmod: str) str = {
|
||||||
let dotidx: i32 = -1;
|
let dotidx: i32 = -1;
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
@@ -5646,8 +5607,6 @@ fn checkvariantcovered(c: *checker, n: *syntax.node, v: *syntax.node, unionmod:
|
|||||||
if (!covered) { errmatchvariant(c, n, v); };
|
if (!covered) { errmatchvariant(c, n, v); };
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- let init / return assignability --------------------------------
|
|
||||||
//
|
|
||||||
// AST-level approximation: when we can infer src's type and dst is
|
// AST-level approximation: when we can infer src's type and dst is
|
||||||
// explicitly declared, verify isassignable. We only emit an error
|
// explicitly declared, verify isassignable. We only emit an error
|
||||||
// when isassignable says "false with confidence." If we can't tell
|
// when isassignable says "false with confidence." If we can't tell
|
||||||
@@ -6581,8 +6540,6 @@ fn checkretassign(c: *checker, n: *syntax.node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- is / as validity ------------------------------------------------
|
|
||||||
//
|
|
||||||
// `e is T` and `e as T` require that e's declared type be a tagged
|
// `e is T` and `e as T` require that e's declared type be a tagged
|
||||||
// union and that T name one of its variants. Operates on AST type
|
// union and that T name one of its variants. Operates on AST type
|
||||||
// expressions; falls back silently when we can't determine e's
|
// expressions; falls back silently when we can't determine e's
|
||||||
@@ -6670,8 +6627,6 @@ fn checkisas(c: *checker, n: *syntax.node) void = {
|
|||||||
c.errs += 1;
|
c.errs += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- ? subset propagation --------------------------------------------
|
|
||||||
//
|
|
||||||
// For `expr?`, the operand's error subset must be a subset of the
|
// For `expr?`, the operand's error subset must be a subset of the
|
||||||
// enclosing fn's return-type variants. Mirrors C check.c. Operand
|
// enclosing fn's return-type variants. Mirrors C check.c. Operand
|
||||||
// is nkind.N_TRYPROP or nkind.N_TRYUNW (the F8 cardinality gate covers
|
// is nkind.N_TRYPROP or nkind.N_TRYUNW (the F8 cardinality gate covers
|
||||||
@@ -6836,9 +6791,6 @@ fn hascvariadic(params: *syntax.node) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// install_param — when entering a fn body, define its params in a
|
|
||||||
// fresh local scope.
|
|
||||||
//
|
|
||||||
// TODO(#11): cstage check.c (post-#32) errors `param '%s' redeclared`
|
// TODO(#11): cstage check.c (post-#32) errors `param '%s' redeclared`
|
||||||
// when two params share a name. The fn body's scope IS fresh here
|
// when two params share a name. The fn body's scope IS fresh here
|
||||||
// (resolvefnbody opens it before calling us), so guarding scopedefine's
|
// (resolvefnbody opens it before calling us), so guarding scopedefine's
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// selfhost/cmd/wcc/err.ww — port of cmd/wcc/err.c.
|
// Port of cmd/wcc/err.c. Plan 9 style: short, no levels beyond
|
||||||
//
|
// fatal/error/warn. Output goes through os.write so we don't pull in
|
||||||
// Diagnostics. Plan 9 style: short, no levels beyond fatal/error/warn.
|
// libc stdio.
|
||||||
// Output goes through os.write so we don't pull in libc stdio.
|
|
||||||
|
|
||||||
package wcc;
|
package wcc;
|
||||||
|
|
||||||
|
|||||||
@@ -16,17 +16,12 @@
|
|||||||
// - A `.wwi` is ONE package's interface; the emit filters to PRIMARY
|
// - A `.wwi` is ONE package's interface; the emit filters to PRIMARY
|
||||||
// decls (imported==0).
|
// decls (imported==0).
|
||||||
|
|
||||||
// Imports mirror check.ww (os/tok/strconv only): node/nkind/sym/skind/
|
|
||||||
// scopelookuptype/streq/checker/tkind resolve bare in the selfhost's flat
|
|
||||||
// bundled scope, exactly as check.ww references them.
|
|
||||||
package wcc;
|
package wcc;
|
||||||
|
|
||||||
import os;
|
import os;
|
||||||
import syntax;
|
import syntax;
|
||||||
import strconv;
|
import strconv;
|
||||||
|
|
||||||
// --- byte writers ------------------------------------------------------
|
|
||||||
|
|
||||||
fn wputs(fd: i32, s: str) void = {
|
fn wputs(fd: i32, s: str) void = {
|
||||||
os.write(fd, s.ptr, s.len: u64);
|
os.write(fd, s.ptr, s.len: u64);
|
||||||
};
|
};
|
||||||
@@ -71,13 +66,11 @@ fn wquote(fd: i32, s: str) void = {
|
|||||||
wputb(fd, '"');
|
wputb(fd, '"');
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- check_exported_type (drew) ---------------------------------------
|
// check_exported_type (drew): resolve an N_TNAME to its type sym via the
|
||||||
//
|
// public sym helpers (mirror of cstage wwi_typesym / scope_lookup_type)
|
||||||
// Resolve an N_TNAME to its type sym via the public sym helpers (mirror
|
// WITHOUT the side effects of the checker's aliassym (no on-demand
|
||||||
// of cstage wwi_typesym / scope_lookup_type) WITHOUT the side effects of
|
// resolve, no double error). A primitive/keyword resolves to no SK_TYPE
|
||||||
// the checker's aliassym (no on-demand resolve, no double error). A
|
// → leaf. By producer time checkfile has finished and c.cur == c.top.
|
||||||
// primitive/keyword resolves to no SK_TYPE → leaf. By producer time
|
|
||||||
// checkfile has finished and c.cur == c.top.
|
|
||||||
|
|
||||||
fn wwitypesym(c: *checker, nm: str) *syntax.sym = {
|
fn wwitypesym(c: *checker, nm: str) *syntax.sym = {
|
||||||
let empty: str;
|
let empty: str;
|
||||||
@@ -188,7 +181,7 @@ fn wwicheckdecl(c: *checker, d: *syntax.node) i32 = {
|
|||||||
bad = bad | wwichecktype(c, d, p.lhs);
|
bad = bad | wwichecktype(c, d, p.lhs);
|
||||||
p = p.next;
|
p = p.next;
|
||||||
};
|
};
|
||||||
bad = bad | wwichecktype(c, d, d.lhs); // ret
|
bad = bad | wwichecktype(c, d, d.lhs);
|
||||||
} else { if (d.kind == syntax.nkind.N_TYPEDECL) {
|
} else { if (d.kind == syntax.nkind.N_TYPEDECL) {
|
||||||
bad = bad | wwichecktype(c, d, d.lhs);
|
bad = bad | wwichecktype(c, d, d.lhs);
|
||||||
} else { if (d.kind == syntax.nkind.N_DEF) {
|
} else { if (d.kind == syntax.nkind.N_DEF) {
|
||||||
@@ -200,10 +193,8 @@ fn wwicheckdecl(c: *checker, d: *syntax.node) i32 = {
|
|||||||
return bad;
|
return bad;
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- type-expr + const-expr unparser (rob §2.2/§2.4) ------------------
|
// Type-expr + const-expr unparser (rob §2.2/§2.4). wwihexdigits is
|
||||||
|
// byte-identical to cstage's fprintf("%0Nx").
|
||||||
// wwihexdigits — emit the low `n` hex digits of `v`, most-significant
|
|
||||||
// first, lowercase. Byte-identical to cstage's fprintf("%0Nx").
|
|
||||||
fn wwihexdigits(fd: i32, v: u64, n: i32) void = {
|
fn wwihexdigits(fd: i32, v: u64, n: i32) void = {
|
||||||
let i: i32 = n - 1;
|
let i: i32 = n - 1;
|
||||||
for (i >= 0) {
|
for (i >= 0) {
|
||||||
@@ -515,8 +506,6 @@ fn wwidecl(fd: i32, d: *syntax.node) void = {
|
|||||||
};};};};
|
};};};};
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- deterministic ordering (rob §3) ----------------------------------
|
|
||||||
|
|
||||||
fn wwiprimary(n: *syntax.node) bool = {
|
fn wwiprimary(n: *syntax.node) bool = {
|
||||||
// imported==1 marks a decl reached through a `//ww:module <path>`
|
// imported==1 marks a decl reached through a `//ww:module <path>`
|
||||||
// boundary (an imported module's concatenated section).
|
// boundary (an imported module's concatenated section).
|
||||||
@@ -529,8 +518,9 @@ fn wwiisdecl(d: *syntax.node) bool = {
|
|||||||
d.kind == syntax.nkind.N_DEF || d.kind == syntax.nkind.N_LET;
|
d.kind == syntax.nkind.N_DEF || d.kind == syntax.nkind.N_LET;
|
||||||
};
|
};
|
||||||
|
|
||||||
// strcmp — byte lexicographic, mirror C strcmp sign (<0/0/>0). Both
|
// Deterministic ordering (rob §3): byte-lexicographic, mirror C strcmp
|
||||||
// stages key the sort identically, so the `.wwi` order is deterministic.
|
// sign (<0/0/>0). Both stages key the sort identically, so the `.wwi`
|
||||||
|
// order is deterministic.
|
||||||
fn wwistrcmp(a: str, b: str) i32 = {
|
fn wwistrcmp(a: str, b: str) i32 = {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < a.len && i < b.len) {
|
for (i < a.len && i < b.len) {
|
||||||
@@ -542,8 +532,7 @@ fn wwistrcmp(a: str, b: str) i32 = {
|
|||||||
return a.len - b.len;
|
return a.len - b.len;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Selection sort over parallel (key, node) arrays. Total order keyed on
|
// Total order keyed on the symbol name; ties broken by original index —
|
||||||
// the symbol name; ties broken by original index — so the result is
|
|
||||||
// stable regardless of any same-name collision, matching cstage's qsort
|
// stable regardless of any same-name collision, matching cstage's qsort
|
||||||
// + idx tiebreak.
|
// + idx tiebreak.
|
||||||
fn wwisortdecls(keys: []str, nodes: []*syntax.node, n: i32) void = {
|
fn wwisortdecls(keys: []str, nodes: []*syntax.node, n: i32) void = {
|
||||||
|
|||||||
@@ -24,9 +24,8 @@ import strings;
|
|||||||
// the cgen #127 mod-mangle attribution bug consumer per rule-7.
|
// the cgen #127 mod-mangle attribution bug consumer per rule-7.
|
||||||
def CMD_MAX: u64 = 8192u64;
|
def CMD_MAX: u64 = 8192u64;
|
||||||
|
|
||||||
// cerr — bare stderr fragment writer for the driver's piecewise
|
// Tool-local (NOT a lib wrapper): messages are built from many
|
||||||
// diagnostics. Tool-local (NOT a lib wrapper): messages are built from
|
// fragments and we route through os.write to avoid libc stdio.
|
||||||
// many fragments and we route through os.write to avoid libc stdio.
|
|
||||||
// .len replaces the error-prone hand-counted byte literals these sites
|
// .len replaces the error-prone hand-counted byte literals these sites
|
||||||
// carried. Lives here (the selfhost ww-driver build is a single main.ww;
|
// carried. Lives here (the selfhost ww-driver build is a single main.ww;
|
||||||
// err.c's bare-message path is not ported into this tree).
|
// err.c's bare-message path is not ported into this tree).
|
||||||
@@ -34,17 +33,14 @@ fn cerr(m: str) void = {
|
|||||||
os.write(2, m.ptr, m.len: u64);
|
os.write(2, m.ptr, m.len: u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- C-string helpers --------------------------------------------------
|
|
||||||
|
|
||||||
fn cstrlen(p: *u8) u64 = {
|
fn cstrlen(p: *u8) u64 = {
|
||||||
let n: u64 = 0u64;
|
let n: u64 = 0u64;
|
||||||
for (p[n] != 0u8) { n += 1u64; };
|
for (p[n] != 0u8) { n += 1u64; };
|
||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
|
||||||
// pathstr — view a NUL-terminated *u8 as a str. Bridges the
|
// Bridges the driver's argv-style *u8 paths to lib/os entrypoints
|
||||||
// driver's argv-style *u8 paths to lib/os entrypoints (str
|
// (str post-task-#23).
|
||||||
// post-task-#23).
|
|
||||||
fn pathstr(p: *u8) str = {
|
fn pathstr(p: *u8) str = {
|
||||||
let r: str;
|
let r: str;
|
||||||
r.ptr = p;
|
r.ptr = p;
|
||||||
@@ -56,12 +52,10 @@ fn cstreq(a: *u8, b: *u8) bool = {
|
|||||||
return strings.compare(pathstr(a), pathstr(b)) == 0;
|
return strings.compare(pathstr(a), pathstr(b)) == 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// cstreqlit — compare a NUL-terminated *u8 to a ww string literal.
|
|
||||||
fn cstreqlit(a: *u8, lit: str) bool = {
|
fn cstreqlit(a: *u8, lit: str) bool = {
|
||||||
return strings.compare(pathstr(a), lit) == 0;
|
return strings.compare(pathstr(a), lit) == 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// memcpy
|
|
||||||
fn bytecpy(dst: *u8, src: *u8, n: u64) void = {
|
fn bytecpy(dst: *u8, src: *u8, n: u64) void = {
|
||||||
let i: u64 = 0u64;
|
let i: u64 = 0u64;
|
||||||
for (i < n) {
|
for (i < n) {
|
||||||
@@ -70,8 +64,6 @@ fn bytecpy(dst: *u8, src: *u8, n: u64) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Copy a NUL-terminated *u8 into dst starting at off; return the new
|
|
||||||
// offset (without writing a NUL).
|
|
||||||
fn cstrinto(dst: *u8, off: u64, src: *u8) u64 = {
|
fn cstrinto(dst: *u8, off: u64, src: *u8) u64 = {
|
||||||
let i: u64 = 0u64;
|
let i: u64 = 0u64;
|
||||||
for (src[i] != 0u8) {
|
for (src[i] != 0u8) {
|
||||||
@@ -81,7 +73,6 @@ fn cstrinto(dst: *u8, off: u64, src: *u8) u64 = {
|
|||||||
return off + i;
|
return off + i;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Same, but for a ww `str` (no NUL on the source side; we copy len bytes).
|
|
||||||
fn strinto(dst: *u8, off: u64, src: str) u64 = {
|
fn strinto(dst: *u8, off: u64, src: str) u64 = {
|
||||||
let n: i32 = src.len;
|
let n: i32 = src.len;
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
@@ -94,22 +85,15 @@ fn strinto(dst: *u8, off: u64, src: str) u64 = {
|
|||||||
return off + nu;
|
return off + nu;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Write a single byte, return new offset.
|
|
||||||
fn byteinto(dst: *u8, off: u64, c: u8) u64 = {
|
fn byteinto(dst: *u8, off: u64, c: u8) u64 = {
|
||||||
dst[off] = c;
|
dst[off] = c;
|
||||||
return off + 1u64;
|
return off + 1u64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// NUL-terminate at off and return the same off (handy when passing the
|
|
||||||
// buffer to a syscall that expects a C-string).
|
|
||||||
fn cstrseal(dst: *u8, off: u64) void = {
|
fn cstrseal(dst: *u8, off: u64) void = {
|
||||||
dst[off] = 0u8;
|
dst[off] = 0u8;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Tool-path resolution ---------------------------------------------
|
|
||||||
|
|
||||||
// dirname-equivalent: copy argv[0] up to (but not including) the last
|
|
||||||
// '/' into dst, NUL-terminated. If no slash, write ".".
|
|
||||||
fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = {
|
fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = {
|
||||||
let n: u64 = cstrlen(argv0);
|
let n: u64 = cstrlen(argv0);
|
||||||
let cut: u64 = 0u64;
|
let cut: u64 = 0u64;
|
||||||
@@ -128,7 +112,6 @@ fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = {
|
|||||||
dst[cut] = 0u8;
|
dst[cut] = 0u8;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build "$dir/$name" (NUL-terminated) into a fresh page-sized buffer.
|
|
||||||
fn joinpath(dir: *u8, name: *u8) *u8 = {
|
fn joinpath(dir: *u8, name: *u8) *u8 = {
|
||||||
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
buf.len = os.PATH_MAX;
|
buf.len = os.PATH_MAX;
|
||||||
@@ -139,7 +122,6 @@ fn joinpath(dir: *u8, name: *u8) *u8 = {
|
|||||||
return buf.ptr;
|
return buf.ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Same, but the second component is a ww `str` literal.
|
|
||||||
fn joinpathlit(dir: *u8, name: str) *u8 = {
|
fn joinpathlit(dir: *u8, name: str) *u8 = {
|
||||||
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
buf.len = os.PATH_MAX;
|
buf.len = os.PATH_MAX;
|
||||||
@@ -208,8 +190,6 @@ fn execpackagetests(selfdir: *u8, argv: **u8, argc: i32, start: i32,
|
|||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- import resolution + visited-set -----------------------------------
|
|
||||||
//
|
|
||||||
// The separate-compilation producer scans each unit's top-of-file
|
// The separate-compilation producer scans each unit's top-of-file
|
||||||
// `import IDENT;` lines and resolves them via the colon-separated `dirs`
|
// `import IDENT;` lines and resolves them via the colon-separated `dirs`
|
||||||
// search path. A per-scan visited set (linear; typical builds visit a
|
// search path. A per-scan visited set (linear; typical builds visit a
|
||||||
@@ -409,10 +389,9 @@ fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64) i32 = {
|
|||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Byte-wise memcmp returning < 0, 0, > 0. Rule-10 byte-id requires
|
// Rule-10 byte-id requires cstage and wwstage sort the same way;
|
||||||
// cstage and wwstage sort the same way; memcmp is the
|
// memcmp is the locale-independent total order (mirrors
|
||||||
// locale-independent total order (mirrors ref/hare/sort/cmp/cmp.ha
|
// ref/hare/sort/cmp/cmp.ha strs).
|
||||||
// strs).
|
|
||||||
fn bytecmp(a: *u8, alen: u64, b: *u8, blen: u64) i32 = {
|
fn bytecmp(a: *u8, alen: u64, b: *u8, blen: u64) i32 = {
|
||||||
let n: u64 = alen;
|
let n: u64 = alen;
|
||||||
if (blen < n) { n = blen; };
|
if (blen < n) { n = blen; };
|
||||||
@@ -526,8 +505,6 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
|||||||
return exact.ptr, n;
|
return exact.ptr, n;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- file slurp -------------------------------------------------------
|
|
||||||
|
|
||||||
fn slurp(pathcs: *u8) (*u8, u64) = {
|
fn slurp(pathcs: *u8) (*u8, u64) = {
|
||||||
let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32);
|
let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32);
|
||||||
if (fd < 0) { return nil, 0u64; };
|
if (fd < 0) { return nil, 0u64; };
|
||||||
@@ -566,7 +543,6 @@ fn isidentbyte(c: u8) bool = {
|
|||||||
// caller passes a slice of the source: src points at the line start.
|
// caller passes a slice of the source: src points at the line start.
|
||||||
fn scanuse(src: *u8, len: u64) (*u8, u64) = {
|
fn scanuse(src: *u8, len: u64) (*u8, u64) = {
|
||||||
let i: u64 = 0u64;
|
let i: u64 = 0u64;
|
||||||
// skip leading whitespace
|
|
||||||
for (i < len) {
|
for (i < len) {
|
||||||
if (src[i] != 32u8) { if (src[i] != 9u8) { break; }; };
|
if (src[i] != 32u8) { if (src[i] != 9u8) { break; }; };
|
||||||
i += 1u64;
|
i += 1u64;
|
||||||
@@ -595,11 +571,6 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = {
|
|||||||
return src + idstart, idlen;
|
return src + idstart, idlen;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Build pipeline ---------------------------------------------------
|
|
||||||
|
|
||||||
// Strip the trailing ".ww" off `src` (a NUL-terminated path) into
|
|
||||||
// `stem`, NUL-terminated. If there's no .ww, the stem is the whole
|
|
||||||
// path.
|
|
||||||
fn makestem(stem: *u8, src: *u8) void = {
|
fn makestem(stem: *u8, src: *u8) void = {
|
||||||
let n: u64 = cstrlen(src);
|
let n: u64 = cstrlen(src);
|
||||||
let stop: u64 = n;
|
let stop: u64 = n;
|
||||||
@@ -617,7 +588,6 @@ fn makestem(stem: *u8, src: *u8) void = {
|
|||||||
stem[stop] = 0u8;
|
stem[stop] = 0u8;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Append a literal suffix to `stem` (which already lives in a buffer).
|
|
||||||
fn appendlit(stem: *u8, suffix: str) *u8 = {
|
fn appendlit(stem: *u8, suffix: str) *u8 = {
|
||||||
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
buf.len = os.PATH_MAX;
|
buf.len = os.PATH_MAX;
|
||||||
@@ -638,8 +608,6 @@ type lflags = struct {
|
|||||||
nlibs: i32,
|
nlibs: i32,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- separate-compilation driver -------------------------------------
|
|
||||||
//
|
|
||||||
// Port of cmd/ww/main.c build_one_sep (task #46/c3). The build path
|
// Port of cmd/ww/main.c build_one_sep (task #46/c3). The build path
|
||||||
// materializes each imported package's `.wwi` interface and compiles
|
// materializes each imported package's `.wwi` interface and compiles
|
||||||
// every package on its own (`w6c -c`), then flat-links the `.o` set.
|
// every package on its own (`w6c -c`), then flat-links the `.o` set.
|
||||||
@@ -677,7 +645,6 @@ type sepgraph = struct {
|
|||||||
n: i32,
|
n: i32,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Find a package by dotted path, or add it. Returns index, -1 if full.
|
|
||||||
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < g.n) {
|
for (i < g.n) {
|
||||||
@@ -1060,8 +1027,6 @@ fn seploadpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Print one cycle-chain node: a package path, or "(root)" for the
|
|
||||||
// empty root path.
|
|
||||||
fn sepcyclenode(p: *u8) void = {
|
fn sepcyclenode(p: *u8) void = {
|
||||||
if (p[0] == 0u8) { cerr("(root)"); } else { cerr(pathstr(p)); };
|
if (p[0] == 0u8) { cerr("(root)"); } else { cerr(pathstr(p)); };
|
||||||
};
|
};
|
||||||
@@ -1103,7 +1068,6 @@ fn septopovisit(g: *sepgraph, pi: i32, order: []i32, no: *i32,
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mark pi's transitive deps (excluding pi) in inset[].
|
|
||||||
fn sepmarkdeps(g: *sepgraph, pi: i32, inset: []u8) void = {
|
fn sepmarkdeps(g: *sepgraph, pi: i32, inset: []u8) void = {
|
||||||
let k: i32 = 0;
|
let k: i32 = 0;
|
||||||
for (k < g.pkg[pi].ndeps) {
|
for (k < g.pkg[pi].ndeps) {
|
||||||
@@ -1306,7 +1270,7 @@ fn archiveo(objpath: *u8, apath: *u8) i32 = {
|
|||||||
// reverse-topo `w6l` of the root `.o` + dependency `.a` set + libwwrt.a.
|
// reverse-topo `w6l` of the root `.o` + dependency `.a` set + libwwrt.a.
|
||||||
// Side files land in a cold `<stem>.sepwork` scratch dir. Twin of cstage
|
// Side files land in a cold `<stem>.sepwork` scratch dir. Twin of cstage
|
||||||
// build_one_sep.
|
// build_one_sep.
|
||||||
// ---- -w workdir freshness ----------------------------------------------
|
|
||||||
// A `-w DIR` workdir is a caller-owned persistent package-artifact tree
|
// A `-w DIR` workdir is a caller-owned persistent package-artifact tree
|
||||||
// that replaces the fresh `.sepwork` scratch. Staleness is pure content
|
// that replaces the fresh `.sepwork` scratch. Staleness is pure content
|
||||||
// identity, never mtime: a package is reused only when its freshly
|
// identity, never mtime: a package is reused only when its freshly
|
||||||
@@ -1478,7 +1442,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
|
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
|
||||||
let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
|
let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
|
||||||
|
|
||||||
// Default lib search path: <selfdir>/../../lib
|
|
||||||
let dotdotlib: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
let dotdotlib: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
dotdotlib.len = os.PATH_MAX;
|
dotdotlib.len = os.PATH_MAX;
|
||||||
{
|
{
|
||||||
@@ -1487,7 +1450,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
cstrseal(dotdotlib.ptr, off);
|
cstrseal(dotdotlib.ptr, off);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Source directory.
|
|
||||||
let srcd: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
let srcd: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
srcd.len = os.PATH_MAX;
|
srcd.len = os.PATH_MAX;
|
||||||
if (entryisdir != 0) {
|
if (entryisdir != 0) {
|
||||||
@@ -1532,7 +1494,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
cstrseal(searchpath.ptr, off);
|
cstrseal(searchpath.ptr, off);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Stem for the scratch dir.
|
|
||||||
let stem: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
let stem: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
stem.len = os.PATH_MAX;
|
stem.len = os.PATH_MAX;
|
||||||
if (entryisdir != 0) {
|
if (entryisdir != 0) {
|
||||||
@@ -1606,7 +1567,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// libwwrt.a path: <selfdir>/../lib/libwwrt.a
|
|
||||||
let libwwrt: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
let libwwrt: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
libwwrt.len = os.PATH_MAX;
|
libwwrt.len = os.PATH_MAX;
|
||||||
{
|
{
|
||||||
@@ -1615,7 +1575,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
cstrseal(libwwrt.ptr, off);
|
cstrseal(libwwrt.ptr, off);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Discover.
|
|
||||||
let pkgslot: []seppkg = alloc([], SEP_MAXPKG: u64)!;
|
let pkgslot: []seppkg = alloc([], SEP_MAXPKG: u64)!;
|
||||||
pkgslot.len = SEP_MAXPKG;
|
pkgslot.len = SEP_MAXPKG;
|
||||||
let g: *sepgraph = alloc(sepgraph{pkg = pkgslot, n = 0})!;
|
let g: *sepgraph = alloc(sepgraph{pkg = pkgslot, n = 0})!;
|
||||||
@@ -1649,7 +1608,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
};
|
};
|
||||||
if (seploadpkg(g, root, searchpath.ptr) < 0) { return 1; };
|
if (seploadpkg(g, root, searchpath.ptr) < 0) { return 1; };
|
||||||
|
|
||||||
// Reset colors, reverse-topo.
|
|
||||||
let ci: i32 = 0;
|
let ci: i32 = 0;
|
||||||
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
|
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
|
||||||
let order: []i32 = alloc([], g.n: u64)!;
|
let order: []i32 = alloc([], g.n: u64)!;
|
||||||
@@ -1659,7 +1617,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
let norder: i32 = 0;
|
let norder: i32 = 0;
|
||||||
if (septopovisit(g, root, order, &norder, stack, 0) < 0) { return 1; };
|
if (septopovisit(g, root, order, &norder, stack, 0) < 0) { return 1; };
|
||||||
|
|
||||||
// Producer loop — dep-first, one `w6c -c -I` per package.
|
|
||||||
let oi: i32 = 0;
|
let oi: i32 = 0;
|
||||||
for (oi < norder) {
|
for (oi < norder) {
|
||||||
let pi: i32 = order[oi];
|
let pi: i32 = order[oi];
|
||||||
@@ -1961,22 +1918,10 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Module-by-name resolution ----------------------------------------
|
|
||||||
//
|
|
||||||
// Mirrors cmd/ww/main.c:resolvemodule. Maps a name like "foo", "lib/foo",
|
|
||||||
// "foo.ww", or "." to a concrete .ww file path:
|
|
||||||
// 1. literal <name>.ww that exists → use as-is
|
|
||||||
// 2. "." → <cwd>/<basename(cwd)>.ww → that, if it exists
|
|
||||||
// 3. <name>/<basename(name)>.ww → that, if it exists
|
|
||||||
// 4. walk search path (cwd:incs:<selfdir>/../../lib):
|
|
||||||
// <dir>/<name>.ww or <dir>/<name>/<name>.ww
|
|
||||||
|
|
||||||
fn cstrendswithlit(p: *u8, lit: str) bool = {
|
fn cstrendswithlit(p: *u8, lit: str) bool = {
|
||||||
return strings.hassuffix(pathstr(p), lit);
|
return strings.hassuffix(pathstr(p), lit);
|
||||||
};
|
};
|
||||||
|
|
||||||
// basenameoff — return the offset of the last path segment within `p`
|
|
||||||
// (i.e. one past the final '/'). Returns 0 if there's no slash.
|
|
||||||
fn basenameoff(p: *u8, plen: u64) u64 = {
|
fn basenameoff(p: *u8, plen: u64) u64 = {
|
||||||
let start: u64 = 0u64;
|
let start: u64 = 0u64;
|
||||||
let i: u64 = 0u64;
|
let i: u64 = 0u64;
|
||||||
@@ -1987,8 +1932,6 @@ fn basenameoff(p: *u8, plen: u64) u64 = {
|
|||||||
return start;
|
return start;
|
||||||
};
|
};
|
||||||
|
|
||||||
// arenadupcstr — copy `plen` bytes from `src` into a fresh NUL-sealed
|
|
||||||
// heap buffer.
|
|
||||||
fn arenadupcstr(src: *u8, plen: u64) *u8 = {
|
fn arenadupcstr(src: *u8, plen: u64) *u8 = {
|
||||||
let buf: []u8 = alloc([], plen + 1u64)!;
|
let buf: []u8 = alloc([], plen + 1u64)!;
|
||||||
let i: u64 = 0u64;
|
let i: u64 = 0u64;
|
||||||
@@ -1997,8 +1940,7 @@ fn arenadupcstr(src: *u8, plen: u64) *u8 = {
|
|||||||
return buf.ptr;
|
return buf.ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
// buildsearchpath — compose the colon-separated lookup path used by
|
// resolvemodule search-path order: "." : <incs> : <selfdir>/../../lib
|
||||||
// resolvemodule's case (4). Order: "." : <incs> : <selfdir>/../../lib
|
|
||||||
fn buildsearchpath(selfdir: *u8, incs: *u8) *u8 = {
|
fn buildsearchpath(selfdir: *u8, incs: *u8) *u8 = {
|
||||||
let buf: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
|
let buf: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
|
||||||
let off: u64 = 0u64;
|
let off: u64 = 0u64;
|
||||||
@@ -2016,13 +1958,11 @@ fn buildsearchpath(selfdir: *u8, incs: *u8) *u8 = {
|
|||||||
return buf.ptr;
|
return buf.ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
// resolvemodule — map a name like "foo", "lib/foo", "foo.ww", or
|
// Mirrors cmd/ww/main.c:resolvemodule. Sets *isdir when the entry is a
|
||||||
// "." to a concrete entry path. Sets *isdir when the entry is a
|
|
||||||
// module directory (caller will dir-enumerate).
|
// module directory (caller will dir-enumerate).
|
||||||
fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
|
fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
|
||||||
let nlen: u64 = cstrlen(name);
|
let nlen: u64 = cstrlen(name);
|
||||||
|
|
||||||
// (1) Literal file that exists → use as-is.
|
|
||||||
if (cstrendswithlit(name, ".ww")) {
|
if (cstrendswithlit(name, ".ww")) {
|
||||||
if (os.access(pathstr(name), 0i32) == 0) {
|
if (os.access(pathstr(name), 0i32) == 0) {
|
||||||
*isdir = 0;
|
*isdir = 0;
|
||||||
@@ -2030,7 +1970,6 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// (2) Existing path → use as-is, dir vs file via stat.
|
|
||||||
let fi: os.filestat;
|
let fi: os.filestat;
|
||||||
let sr: (void | os.oserror) = os.stat(&fi, pathstr(name));
|
let sr: (void | os.oserror) = os.stat(&fi, pathstr(name));
|
||||||
let found: bool = false;
|
let found: bool = false;
|
||||||
@@ -2048,13 +1987,10 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
|
|||||||
return arenadupcstr(name, nlen);
|
return arenadupcstr(name, nlen);
|
||||||
};
|
};
|
||||||
|
|
||||||
// (3) Search-path lookup with dot-to-slash path translation.
|
|
||||||
let search: *u8 = buildsearchpath(selfdir, incs);
|
let search: *u8 = buildsearchpath(selfdir, incs);
|
||||||
return locateimport(search, name, nlen, isdir);
|
return locateimport(search, name, nlen, isdir);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Subcommand handlers ----------------------------------------------
|
|
||||||
|
|
||||||
fn writeusage(fd: i32) void = {
|
fn writeusage(fd: i32) void = {
|
||||||
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\n build [-S] [-w DIR] [-o FILE] [path] compile module; -S stops after package asm\n run [path] ... build then exec, passing extra args to the program\n test [-S -o STEM] [-w DIR] [options] [path] build/run tests; -S emits package asm\n version print version and exit\n\n path forms:\n foo.ww literal file\n foo search cwd, -I dirs, then $WW_LIB-equiv for foo.ww or foo/foo.ww\n lib/foo directory: build lib/foo/foo.ww\n lib/... every package under lib, recursively (test only)\n . build the cwd's <basename>.ww\n";
|
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\n build [-S] [-w DIR] [-o FILE] [path] compile module; -S stops after package asm\n run [path] ... build then exec, passing extra args to the program\n test [-S -o STEM] [-w DIR] [options] [path] build/run tests; -S emits package asm\n version print version and exit\n\n path forms:\n foo.ww literal file\n foo search cwd, -I dirs, then $WW_LIB-equiv for foo.ww or foo/foo.ww\n lib/foo directory: build lib/foo/foo.ww\n lib/... every package under lib, recursively (test only)\n . build the cwd's <basename>.ww\n";
|
||||||
os.write(fd, s.ptr, s.len: u64);
|
os.write(fd, s.ptr, s.len: u64);
|
||||||
@@ -2065,8 +2001,6 @@ fn doversion() i32 = {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compute the basename of src (without trailing ".ww") into a fresh
|
|
||||||
// buffer. Used as the default output path for `ww build`.
|
|
||||||
fn defaultoutpath(src: *u8) *u8 = {
|
fn defaultoutpath(src: *u8) *u8 = {
|
||||||
let n: u64 = cstrlen(src);
|
let n: u64 = cstrlen(src);
|
||||||
let start: u64 = 0u64;
|
let start: u64 = 0u64;
|
||||||
@@ -2084,7 +2018,6 @@ fn defaultoutpath(src: *u8) *u8 = {
|
|||||||
off += 1u64;
|
off += 1u64;
|
||||||
j += 1u64;
|
j += 1u64;
|
||||||
};
|
};
|
||||||
// Strip ".ww" if present.
|
|
||||||
if (off >= 3u64) {
|
if (off >= 3u64) {
|
||||||
if (out[off - 3u64] == 46u8) {
|
if (out[off - 3u64] == 46u8) {
|
||||||
if (out[off - 2u64] == 119u8) {
|
if (out[off - 2u64] == 119u8) {
|
||||||
@@ -2209,7 +2142,6 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (src == nil) {
|
if (src == nil) {
|
||||||
// default to cwd module
|
|
||||||
let dot: [2]u8 = ['.': u8, 0u8];
|
let dot: [2]u8 = ['.': u8, 0u8];
|
||||||
src = &dot[0];
|
src = &dot[0];
|
||||||
};
|
};
|
||||||
@@ -2264,7 +2196,6 @@ fn makedrivertmp(buf: *u8, prefix: str) void = {
|
|||||||
pk += 1;
|
pk += 1;
|
||||||
};
|
};
|
||||||
let pid: i32 = os.getpid();
|
let pid: i32 = os.getpid();
|
||||||
// itoa for non-negative pid
|
|
||||||
let dig: [16]u8;
|
let dig: [16]u8;
|
||||||
let n: i32 = 0;
|
let n: i32 = 0;
|
||||||
if (pid <= 0) {
|
if (pid <= 0) {
|
||||||
@@ -2383,7 +2314,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
src = p;
|
src = p;
|
||||||
i += 1;
|
i += 1;
|
||||||
} else {
|
} else {
|
||||||
passstart = i; // remaining args go to the program
|
passstart = i;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -2426,8 +2357,8 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Execute with [tmp, argv[passstart..argc)). The standard process
|
// The standard process facility inherits stdio and waits only for
|
||||||
// facility inherits stdio and waits only for this user program.
|
// this user program.
|
||||||
let nextra: i32 = 0;
|
let nextra: i32 = 0;
|
||||||
if (passstart >= 0) { nextra = argc - passstart; };
|
if (passstart >= 0) { nextra = argc - passstart; };
|
||||||
let execargv: []str = alloc([], (nextra + 1): u64)!;
|
let execargv: []str = alloc([], (nextra + 1): u64)!;
|
||||||
@@ -2464,8 +2395,6 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
return rc;
|
return rc;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- ww test ----------------------------------------------------------
|
|
||||||
//
|
|
||||||
// Mirrors cmd/ww/main.c:dotest. Explicit regular files retain the bootstrap
|
// Mirrors cmd/ww/main.c:dotest. Explicit regular files retain the bootstrap
|
||||||
// compatibility route; directory/default requests delegate to wwtest.
|
// compatibility route; directory/default requests delegate to wwtest.
|
||||||
|
|
||||||
@@ -2762,15 +2691,12 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
replacement, targetindex < 0);
|
replacement, targetindex < 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Entry -------------------------------------------------------------
|
|
||||||
|
|
||||||
export fn main(argc: i32, argv: **u8) i32 = {
|
export fn main(argc: i32, argv: **u8) i32 = {
|
||||||
if (argc < 1) {
|
if (argc < 1) {
|
||||||
writeusage(2);
|
writeusage(2);
|
||||||
return 2;
|
return 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
// selfdir = dirname(argv[0])
|
|
||||||
let selfdir: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
let selfdir: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
selfdir.len = os.PATH_MAX;
|
selfdir.len = os.PATH_MAX;
|
||||||
selfdirinto(selfdir.ptr, (os.PATH_MAX: u64), argv[0]);
|
selfdirinto(selfdir.ptr, (os.PATH_MAX: u64), argv[0]);
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
// selfhost/cmd/wwdump/main.ww — ww-side port of cmd/wwdump/main.c.
|
// Port of cmd/wwdump/main.c. The 990_selfhost test diffs token output
|
||||||
//
|
// byte-for-byte against the C-side wwdump on the same file; any
|
||||||
// Reads a .ww file, runs the ww-side lexer, prints tokens through
|
|
||||||
// the ww-side tokprint. The 990_selfhost test diffs this output
|
|
||||||
// byte-for-byte against the C-side wwdump on the same file. Any
|
|
||||||
// divergence is a port bug in lex.ww or tok.ww.
|
// divergence is a port bug in lex.ww or tok.ww.
|
||||||
//
|
|
||||||
// Modes:
|
|
||||||
// wwdump -t file.ww tokens (default)
|
|
||||||
// wwdump -a file.ww AST (not yet implemented; reserved)
|
|
||||||
|
|
||||||
package main;
|
package main;
|
||||||
|
|
||||||
@@ -17,10 +10,8 @@ import check;
|
|||||||
import cgen;
|
import cgen;
|
||||||
import strconv;
|
import strconv;
|
||||||
|
|
||||||
// ---- argv helpers -----------------------------------------------------
|
// argv strings are always NUL-terminated (kernel-supplied), so the
|
||||||
|
// unbounded scan is safe.
|
||||||
// argstrlen — strlen on a NUL-terminated *u8. argv strings are always
|
|
||||||
// NUL-terminated (kernel-supplied) so this is safe.
|
|
||||||
fn argstrlen(s: *u8) i32 = {
|
fn argstrlen(s: *u8) i32 = {
|
||||||
let n: i32 = 0;
|
let n: i32 = 0;
|
||||||
for (s[n] != 0u8) { n += 1; };
|
for (s[n] != 0u8) { n += 1; };
|
||||||
@@ -34,7 +25,6 @@ fn argstr(p: *u8) str = {
|
|||||||
return s;
|
return s;
|
||||||
};
|
};
|
||||||
|
|
||||||
// streqlit — compare a NUL-terminated argv entry to a string literal.
|
|
||||||
fn streqlit(p: *u8, lit: str) bool = {
|
fn streqlit(p: *u8, lit: str) bool = {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < lit.len) {
|
for (i < lit.len) {
|
||||||
@@ -44,8 +34,6 @@ fn streqlit(p: *u8, lit: str) bool = {
|
|||||||
return p[i] == 0u8;
|
return p[i] == 0u8;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- main -------------------------------------------------------------
|
|
||||||
|
|
||||||
export fn main(argc: i32, argv: **u8) i32 = {
|
export fn main(argc: i32, argv: **u8) i32 = {
|
||||||
let mode: i32 = 116; // 't'
|
let mode: i32 = 116; // 't'
|
||||||
let path: *u8 = nil;
|
let path: *u8 = nil;
|
||||||
@@ -142,7 +130,6 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
|||||||
// Quiet by default; flip to 1 when debugging missing names.
|
// Quiet by default; flip to 1 when debugging missing names.
|
||||||
ck.verbose = 0;
|
ck.verbose = 0;
|
||||||
checkfile(&ck, f);
|
checkfile(&ck, f);
|
||||||
// (close out the if-else chain — we'll close all braces below)
|
|
||||||
// "<file>: <resolved>/<resolved+unresolved> resolved"
|
// "<file>: <resolved>/<resolved+unresolved> resolved"
|
||||||
os.write(1, argstr(path).ptr, argstrlen(path): u64);
|
os.write(1, argstr(path).ptr, argstrlen(path): u64);
|
||||||
os.write(1, ": ".ptr, 2u64);
|
os.write(1, ": ".ptr, 2u64);
|
||||||
|
|||||||
Reference in New Issue
Block a user