many things

This commit is contained in:
nora 2023-07-31 22:39:17 +02:00
parent f582a5b4c3
commit 924236532c
13 changed files with 264 additions and 85 deletions

View file

@ -1,6 +1,6 @@
import { Span } from "./error"; import { DUMMY_SPAN, Span } from "./error";
import { LitIntType } from "./lexer"; import { LitIntType } from "./lexer";
import { ComplexMap } from "./utils"; import { ComplexMap, unwrap } from "./utils";
export type Phase = { export type Phase = {
res: unknown; res: unknown;
@ -430,6 +430,9 @@ export const BUILTINS = [
"__i64_load", "__i64_load",
"__string_ptr", "__string_ptr",
"__string_len", "__string_len",
"__memory_size",
"__memory_grow",
"__i32_extend_to_i64_u"
] as const; ] as const;
export type BuiltinName = (typeof BUILTINS)[number]; export type BuiltinName = (typeof BUILTINS)[number];
@ -521,6 +524,29 @@ export type TypeckResults = {
main: Resolution | undefined; main: Resolution | undefined;
}; };
export function findCrateItem<P extends Phase>(
crate: Crate<P>,
id: ItemId
): Item<P> {
if (id.crateId !== crate.id) {
throw new Error("trying to get item from the wrong crate");
}
if (id.itemIdx === 0) {
// Return a synthetic module representing the crate root.
return {
kind: "mod",
node: {
contents: crate.rootItems,
name: crate.packageName,
},
span: DUMMY_SPAN,
id,
};
}
return unwrap(crate.itemsById.get(id));
}
// folders // folders
export type FoldFn<From, To> = (value: From) => To; export type FoldFn<From, To> = (value: From) => To;

View file

@ -11,6 +11,7 @@ export function spanMerge(a: Span, b: Span): Span {
} }
export const DUMMY_SPAN = { start: 0, end: 0 }; export const DUMMY_SPAN = { start: 0, end: 0 };
export const EOF_SPAN = {start: Number.MAX_SAFE_INTEGER, end: Number.MAX_SAFE_INTEGER};
export class CompilerError extends Error { export class CompilerError extends Error {
msg: string; msg: string;
@ -59,7 +60,10 @@ function renderError(input: string, filename: string, e: CompilerError) {
const startRelLine = const startRelLine =
e.span.start === Number.MAX_SAFE_INTEGER ? 0 : e.span.start - line.start; e.span.start === Number.MAX_SAFE_INTEGER ? 0 : e.span.start - line.start;
const spanLength = min(e.span.end, line.end) - e.span.start; const spanLength =
e.span.start === Number.MAX_SAFE_INTEGER
? 1
: min(e.span.end, line.end) - e.span.start;
console.error( console.error(
`${" ".repeat(String(lineNo).length)} ${" ".repeat( `${" ".repeat(String(lineNo).length)} ${" ".repeat(

View file

@ -13,10 +13,12 @@ import { Crate, Built, Typecked } from "./ast";
import { Ids } from "./utils"; import { Ids } from "./utils";
const INPUT = ` const INPUT = `
global HELLO: I32 = 0_I32; extern mod std;
type A = { a: String };
function main() = ( function main() = (
HELLO = 1_I32; std.rt.allocateItem(0_I32, 0_I32);
); );
`; `;
@ -135,6 +137,8 @@ function loadCrate(
const tokens = tokenize(input); const tokens = tokenize(input);
const ast = parse(name, tokens, crateId.next()); const ast = parse(name, tokens, crateId.next());
const [resolved, crates] = resolve(ast, loadCrate); const [resolved, crates] = resolve(ast, loadCrate);
console.log(resolved);
const typecked = typeck(resolved, [...existingCrates, ...crates]); const typecked = typeck(resolved, [...existingCrates, ...crates]);
return [typecked, crates]; return [typecked, crates];

View file

@ -37,7 +37,8 @@ export type DatalessToken =
| "==" | "=="
| "<=" | "<="
| ">=" | ">="
| "!="; | "!="
| "end of file";
export type TokenIdent = { kind: "identifier"; ident: string }; export type TokenIdent = { kind: "identifier"; ident: string };
@ -109,6 +110,9 @@ export function tokenize(input: string): Token[] {
throw new CompilerError("unterminated block comment", span); throw new CompilerError("unterminated block comment", span);
} }
} }
i++;
i++;
continue;
} }
if (SINGLE_PUNCT.includes(next)) { if (SINGLE_PUNCT.includes(next)) {

View file

@ -13,6 +13,7 @@ import {
TyFn, TyFn,
TyTuple, TyTuple,
Typecked, Typecked,
findCrateItem,
varUnreachable, varUnreachable,
} from "./ast"; } from "./ast";
import { printTy } from "./printer"; import { printTy } from "./printer";
@ -30,6 +31,8 @@ const WASM_PAGE = 65536;
const DUMMY_IDX = 9999999; const DUMMY_IDX = 9999999;
const ALLOCATE_SYMBOL = "nil__std__rt__allocateItem";
type RelocationKind = type RelocationKind =
| { | {
kind: "funccall"; kind: "funccall";
@ -86,7 +89,7 @@ function appendData(cx: Context, newData: Uint8Array): number {
mode: { mode: {
kind: "active", kind: "active",
memory: 0, memory: 0,
offset: [{ kind: "i32.const", imm: 0 }], offset: [{ kind: "i32.const", imm: 0n }],
}, },
_name: "staticdata", _name: "staticdata",
}); });
@ -103,8 +106,9 @@ function appendData(cx: Context, newData: Uint8Array): number {
} }
function findItem(cx: Context, id: ItemId): Item<Typecked> { function findItem(cx: Context, id: ItemId): Item<Typecked> {
return unwrap( return findCrateItem(
unwrap(cx.crates.find((crate) => crate.id === id.crateId)).itemsById.get(id) unwrap(cx.crates.find((crate) => crate.id === id.crateId)),
id
); );
} }
@ -271,7 +275,7 @@ function lowerGlobal(
const init: wasm.Instr = { const init: wasm.Instr = {
kind: `${valtype}.const`, kind: `${valtype}.const`,
imm: def.init.value.value, imm: BigInt(def.init.value.value),
}; };
cx.mod.globals.push({ cx.mod.globals.push({
@ -300,6 +304,11 @@ type ArgRetAbi = wasm.ValType[];
type VarLocation = { localIdx: number; types: wasm.ValType[] }; type VarLocation = { localIdx: number; types: wasm.ValType[] };
type StructLayout = {
size: number,
align: number,
}
function lowerFunc( function lowerFunc(
cx: Context, cx: Context,
item: Item<Typecked>, item: Item<Typecked>,
@ -438,18 +447,18 @@ function lowerExpr(
const utf8 = encodeUtf8(expr.value.value); const utf8 = encodeUtf8(expr.value.value);
const idx = appendData(fcx.cx, utf8); const idx = appendData(fcx.cx, utf8);
instrs.push({ kind: "i32.const", imm: idx }); instrs.push({ kind: "i32.const", imm: BigInt(idx) });
instrs.push({ kind: "i32.const", imm: utf8.length }); instrs.push({ kind: "i32.const", imm: BigInt(utf8.length) });
break; break;
} }
case "int": case "int":
switch (expr.value.type) { switch (expr.value.type) {
case "Int": case "Int":
instrs.push({ kind: "i64.const", imm: expr.value.value }); instrs.push({ kind: "i64.const", imm: BigInt(expr.value.value) });
break; break;
case "I32": case "I32":
instrs.push({ kind: "i32.const", imm: expr.value.value }); instrs.push({ kind: "i32.const", imm: BigInt(expr.value.value) });
break; break;
} }
break; break;
@ -487,10 +496,10 @@ function lowerExpr(
case "builtin": case "builtin":
switch (res.name) { switch (res.name) {
case "false": case "false":
instrs.push({ kind: "i32.const", imm: 0 }); instrs.push({ kind: "i32.const", imm: 0n });
break; break;
case "true": case "true":
instrs.push({ kind: "i32.const", imm: 1 }); instrs.push({ kind: "i32.const", imm: 1n });
break; break;
case "print": case "print":
todo("print function"); todo("print function");
@ -603,11 +612,18 @@ function lowerExpr(
case "!": case "!":
if (ty.kind === "bool") { if (ty.kind === "bool") {
// `xor RHS, 1` flips the lowermost bit. // `xor RHS, 1` flips the lowermost bit.
instrs.push({ kind: "i64.const", imm: 1 }); instrs.push({ kind: "i64.const", imm: 1n });
instrs.push({ kind: "i64.xor" }); instrs.push({ kind: "i64.xor" });
} else if (ty.kind === "int") { } else if (ty.kind === "int") {
// `xor RHS, -1` flips all bits. // `xor RHS, -1` flips all bits.
todo("Thanks to JS, we cannot represent -1 i64 yet"); instrs.push({ kind: "i64.const", imm: -1n });
instrs.push({ kind: "i64.xor" });
} else if (ty.kind === "i32") {
// `xor RHS, -1` flips all bits.
instrs.push({ kind: "i32.const", imm: -1n });
instrs.push({ kind: "i32.xor" });
} else {
throw new Error("invalid type for !");
} }
break; break;
case "-": case "-":
@ -621,36 +637,44 @@ function lowerExpr(
} }
const res = expr.lhs.kind === "ident" ? expr.lhs.value.res : expr.lhs.res; const res = expr.lhs.kind === "ident" ? expr.lhs.value.res : expr.lhs.res;
if (res.kind === "builtin") { if (res.kind === "builtin") {
const assertArgs = (n: number) => {
if (expr.args.length !== n) throw new Error("nope");
};
switch (res.name) { switch (res.name) {
case "trap": { case "trap": {
assertArgs(0);
instrs.push({ kind: "unreachable" }); instrs.push({ kind: "unreachable" });
break exprKind; break exprKind;
} }
case "__i32_load": { case "__i32_load": {
assertArgs(1);
lowerExpr(fcx, instrs, expr.args[0]); lowerExpr(fcx, instrs, expr.args[0]);
instrs.push({ kind: "i64.load", imm: {} }); instrs.push({ kind: "i64.load", imm: {} });
break exprKind; break exprKind;
} }
case "__i64_load": { case "__i64_load": {
assertArgs(1);
lowerExpr(fcx, instrs, expr.args[0]); lowerExpr(fcx, instrs, expr.args[0]);
instrs.push({ kind: "i64.load", imm: {} }); instrs.push({ kind: "i64.load", imm: {} });
break exprKind; break exprKind;
} }
case "__i32_store": { case "__i32_store": {
assertArgs(2);
lowerExpr(fcx, instrs, expr.args[0]); lowerExpr(fcx, instrs, expr.args[0]);
lowerExpr(fcx, instrs, expr.args[1]); lowerExpr(fcx, instrs, expr.args[1]);
instrs.push({ kind: "i32.store", imm: {} }); instrs.push({ kind: "i32.store", imm: {} });
break exprKind; break exprKind;
} }
case "__i64_store": { case "__i64_store": {
assertArgs(3);
lowerExpr(fcx, instrs, expr.args[0]); lowerExpr(fcx, instrs, expr.args[0]);
lowerExpr(fcx, instrs, expr.args[1]); lowerExpr(fcx, instrs, expr.args[1]);
instrs.push({ kind: "i64.store", imm: {} }); instrs.push({ kind: "i64.store", imm: {} });
break exprKind; break exprKind;
} }
case "__string_ptr": { case "__string_ptr": {
assertArgs(1);
lowerExpr(fcx, instrs, expr.args[0]); lowerExpr(fcx, instrs, expr.args[0]);
// ptr, len // ptr, len
instrs.push({ kind: "drop" }); instrs.push({ kind: "drop" });
@ -658,14 +682,32 @@ function lowerExpr(
break exprKind; break exprKind;
} }
case "__string_len": { case "__string_len": {
assertArgs(1);
lowerExpr(fcx, instrs, expr.args[0]); lowerExpr(fcx, instrs, expr.args[0]);
// ptr, len // ptr, len
instrs.push({ kind: "i32.const", imm: 0 }); instrs.push({ kind: "i32.const", imm: 0n });
// ptr, len, 0 // ptr, len, 0
instrs.push({ kind: "select" }); instrs.push({ kind: "select" });
// len // len
break exprKind; break exprKind;
} }
case "__memory_size": {
assertArgs(0);
instrs.push({ kind: "memory.size" });
break exprKind;
}
case "__memory_grow": {
assertArgs(1);
lowerExpr(fcx, instrs, expr.args[0]);
instrs.push({ kind: "memory.grow" });
break exprKind;
}
case "__i32_extend_to_i64_u": {
assertArgs(1);
lowerExpr(fcx, instrs, expr.args[0]);
instrs.push({ kind: "i64.extend_i32_u" });
break exprKind;
}
} }
} }
@ -1016,18 +1058,18 @@ function addRt(cx: Context, crates: Crate<Typecked>[]) {
type: internFuncType(cx, { params: [POINTER, USIZE], returns: [] }), type: internFuncType(cx, { params: [POINTER, USIZE], returns: [] }),
body: [ body: [
// get the pointer and store it in the iovec // get the pointer and store it in the iovec
{ kind: "i32.const", imm: iovecArray }, { kind: "i32.const", imm: BigInt(iovecArray) },
{ kind: "local.get", imm: 0 }, { kind: "local.get", imm: 0 },
{ kind: "i32.store", imm: { offset: 0, align: 4 } }, { kind: "i32.store", imm: { offset: 0, align: 4 } },
// get the length and store it in the iovec // get the length and store it in the iovec
{ kind: "i32.const", imm: iovecArray + 4 }, { kind: "i32.const", imm: BigInt(iovecArray + 4) },
{ kind: "local.get", imm: 1 }, { kind: "local.get", imm: 1 },
{ kind: "i32.store", imm: { offset: 0, align: 4 } }, { kind: "i32.store", imm: { offset: 0, align: 4 } },
// now call stuff // now call stuff
{ kind: "i32.const", imm: /*stdout*/ 1 }, { kind: "i32.const", imm: /*stdout*/ 1n },
{ kind: "i32.const", imm: iovecArray }, { kind: "i32.const", imm: BigInt(iovecArray) },
{ kind: "i32.const", imm: /*iovec len*/ 1 }, { kind: "i32.const", imm: /*iovec len*/ 1n },
{ kind: "i32.const", imm: /*out ptr*/ printReturnValue }, { kind: "i32.const", imm: /*out ptr*/ BigInt(printReturnValue) },
{ kind: "call", func: 0 }, { kind: "call", func: 0 },
{ kind: "drop" }, { kind: "drop" },
], ],

View file

@ -31,7 +31,7 @@ import {
ItemId, ItemId,
GlobalItem, GlobalItem,
} from "./ast"; } from "./ast";
import { CompilerError, Span, spanMerge } from "./error"; import { CompilerError, DUMMY_SPAN, EOF_SPAN, Span, spanMerge } from "./error";
import { BaseToken, Token, TokenIdent, TokenLitString } from "./lexer"; import { BaseToken, Token, TokenIdent, TokenLitString } from "./lexer";
import { ComplexMap, ComplexSet, Ids } from "./utils"; import { ComplexMap, ComplexSet, Ids } from "./utils";
@ -165,7 +165,7 @@ function parseItem(t: Token[]): [Token[], Item<Parsed>] {
const contents: Item<Parsed>[] = []; const contents: Item<Parsed>[] = [];
while (next(t)[1].kind !== ")") { while (peekKind(t) !== ")") {
let item; let item;
[t, item] = parseItem(t); [t, item] = parseItem(t);
@ -287,13 +287,15 @@ function mkParserExprBinary(
let lhs; let lhs;
[t, lhs] = lower(t); [t, lhs] = lower(t);
const [, peek] = next(t); const peek = peekKind(t);
if (kinds.includes(peek.kind)) { if (peek && kinds.includes(peek)) {
[t] = next(t); let tok;
[t, tok] = next(t);
let rhs; let rhs;
[t, rhs] = parser(t); [t, rhs] = parser(t);
const span = spanMerge(lhs.span, rhs.span); const span = spanMerge(lhs.span, rhs.span);
return [t, mkExpr(lhs, rhs, span, peek.kind)];
return [t, mkExpr(lhs, rhs, span, tok.kind)];
} }
return [t, lhs]; return [t, lhs];
@ -326,17 +328,19 @@ const parseExprAssignment = mkParserExprBinary(
); );
function parseExprUnary(t: Token[]): [Token[], Expr<Parsed>] { function parseExprUnary(t: Token[]): [Token[], Expr<Parsed>] {
const [, peak] = next(t); const peek = peekKind(t);
if (peak.kind in UNARY_KINDS) { if (peek && UNARY_KINDS.includes(peek as UnaryKind)) {
let tok: Token;
[t, tok] = expectNext(t, peek);
let rhs; let rhs;
[t, rhs] = parseExprUnary(t); [t, rhs] = parseExprUnary(t);
return [ return [
t, t,
{ {
kind: "unary", kind: "unary",
unaryKind: peak.kind as UnaryKind, unaryKind: tok.kind as UnaryKind,
rhs, rhs,
span: peak.span, span: tok.span,
}, },
]; ];
} }
@ -347,7 +351,7 @@ function parseExprCall(t: Token[]): [Token[], Expr<Parsed>] {
let lhs: Expr<Parsed>; let lhs: Expr<Parsed>;
[t, lhs] = parseExprAtom(t); [t, lhs] = parseExprAtom(t);
while (next(t)[1].kind === "(" || next(t)[1].kind === ".") { while (peekKind(t) === "(" || peekKind(t) === ".") {
let tok; let tok;
[t, tok] = next(t); [t, tok] = next(t);
@ -402,7 +406,7 @@ function parseExprAtom(startT: Token[]): [Token[], Expr<Parsed>] {
// It's a block. // It's a block.
if (peek.kind === ";") { if (peek.kind === ";") {
const exprs = [expr]; const exprs = [expr];
while (next(t)[1].kind !== ")") { while (peekKind(t) !== ")") {
[t] = expectNext(t, ";"); [t] = expectNext(t, ";");
[t, expr] = parseExpr(t); [t, expr] = parseExpr(t);
exprs.push(expr); exprs.push(expr);
@ -578,14 +582,14 @@ function parseType(t: Token[]): [Token[], Type<Parsed>] {
// `()` is a the unit type, an empty tuple. // `()` is a the unit type, an empty tuple.
// `(T)` is just `T` // `(T)` is just `T`
// `(T,)` is a tuple // `(T,)` is a tuple
if (next(t)[1]?.kind === ")") { if (peekKind(t) === ")") {
[t] = next(t); [t] = next(t);
return [t, { kind: "tuple", elems: [], span }]; return [t, { kind: "tuple", elems: [], span }];
} }
let head; let head;
[t, head] = parseType(t); [t, head] = parseType(t);
if (next(t)[1]?.kind === ")") { if (peekKind(t) === ")") {
[t] = next(t); [t] = next(t);
// Just a type inside parens, not a tuple. `(T,)` is a tuple. // Just a type inside parens, not a tuple. `(T,)` is a tuple.
return [t, head]; return [t, head];
@ -618,7 +622,7 @@ function parseCommaSeparatedList<R>(
// () | (a) | (a,) | (a, b) // () | (a) | (a,) | (a, b)
while (next(t)[1]?.kind !== terminator) { while (peekKind(t) !== terminator) {
let nextValue; let nextValue;
[t, nextValue] = parser(t); [t, nextValue] = parser(t);
@ -629,7 +633,7 @@ function parseCommaSeparatedList<R>(
if (!comma) { if (!comma) {
// No comma? Fine, you don't like trailing commas. // No comma? Fine, you don't like trailing commas.
// But this better be the end. // But this better be the end.
if (next(t)[1]?.kind !== terminator) { if (peekKind(t) !== terminator) {
unexpectedToken(next(t)[1], `, or ${terminator}`); unexpectedToken(next(t)[1], `, or ${terminator}`);
} }
break; break;
@ -645,19 +649,28 @@ function eat<T extends BaseToken>(
t: Token[], t: Token[],
kind: T["kind"] kind: T["kind"]
): [Token[], T | undefined] { ): [Token[], T | undefined] {
const [tnext, tok] = next(t); if (peekKind(t) === kind) {
if (tok.kind === kind) { return expectNext(t, kind);
return [tnext, tok as unknown as T];
} }
return [t, undefined]; return [t, undefined];
} }
function peekKind(t: Token[]): Token["kind"] | undefined {
return maybeNextT(t)?.[1]?.kind;
}
function expectNext<T extends BaseToken>( function expectNext<T extends BaseToken>(
t: Token[], t: Token[],
kind: T["kind"] kind: T["kind"]
): [Token[], T & Token] { ): [Token[], T & Token] {
let tok; let tok;
[t, tok] = next(t); [t, tok] = maybeNextT(t);
if (!tok) {
throw new CompilerError(
`expected \`${kind}\`, found end of file`,
EOF_SPAN
);
}
if (tok.kind !== kind) { if (tok.kind !== kind) {
throw new CompilerError( throw new CompilerError(
`expected \`${kind}\`, found \`${tok.kind}\``, `expected \`${kind}\`, found \`${tok.kind}\``,
@ -670,10 +683,7 @@ function expectNext<T extends BaseToken>(
function next(t: Token[]): [Token[], Token] { function next(t: Token[]): [Token[], Token] {
const [rest, next] = maybeNextT(t); const [rest, next] = maybeNextT(t);
if (!next) { if (!next) {
throw new CompilerError("unexpected end of file", { throw new CompilerError("unexpected end of file", EOF_SPAN);
start: Number.MAX_SAFE_INTEGER,
end: Number.MAX_SAFE_INTEGER,
});
} }
return [rest, next]; return [rest, next];
} }
@ -757,6 +767,7 @@ function buildCrate(
crateId: number crateId: number
): Crate<Built> { ): Crate<Built> {
const itemId = new Ids(); const itemId = new Ids();
itemId.next(); // crate root ID
const loopId = new Ids(); const loopId = new Ids();
const ast: Crate<Built> = { const ast: Crate<Built> = {
@ -789,5 +800,7 @@ function buildCrate(
}, },
}; };
return foldAst(ast, assigner); const crate = foldAst(ast, assigner);
return crate;
} }

View file

@ -18,6 +18,7 @@ import {
superFoldType, superFoldType,
ExternItem, ExternItem,
Typecked, Typecked,
findCrateItem,
} from "./ast"; } from "./ast";
import { CompilerError, Span, spanMerge } from "./error"; import { CompilerError, Span, spanMerge } from "./error";
import { ComplexMap, Ids, unwrap } from "./utils"; import { ComplexMap, Ids, unwrap } from "./utils";
@ -40,6 +41,13 @@ type Context = {
crateId: Ids; crateId: Ids;
}; };
function findItem(cx: Context, id: ItemId): Item<Built> {
const crate = unwrap(
[cx.ast, ...cx.crates].find((crate) => crate.id === id.crateId)
);
return findCrateItem(crate, id);
}
function resolveModItem( function resolveModItem(
cx: Context, cx: Context,
mod: ModItem<Built> | ExternItem, mod: ModItem<Built> | ExternItem,
@ -56,21 +64,15 @@ function resolveModItem(
if ("contents" in mod) { if ("contents" in mod) {
contents = new Map(mod.contents.map((item) => [item.node.name, item.id])); contents = new Map(mod.contents.map((item) => [item.node.name, item.id]));
} else { } else {
console.log("resolve...");
const [loadedCrate, itsDeps] = cx.crateLoader( const [loadedCrate, itsDeps] = cx.crateLoader(
item.node.name, item.node.name,
item.span, item.span,
cx.crateId, cx.crateId,
cx.crates cx.crates
); );
console.log("hmm");
cx.crates.push(loadedCrate); cx.crates.push(loadedCrate);
cx.crates.push(...itsDeps); cx.crates.push(...itsDeps);
console.log(cx.crates);
contents = new Map( contents = new Map(
loadedCrate.rootItems.map((item) => [item.node.name, item.id]) loadedCrate.rootItems.map((item) => [item.node.name, item.id])
); );
@ -157,6 +159,13 @@ function resolveModule(
}; };
} }
if (ident.name === cx.ast.packageName) {
return {
kind: "item",
id: new ItemId(cx.ast.id, 0),
};
}
if (BUILTIN_SET.has(ident.name)) { if (BUILTIN_SET.has(ident.name)) {
return { kind: "builtin", name: ident.name as BuiltinName }; return { kind: "builtin", name: ident.name as BuiltinName };
} }
@ -271,7 +280,8 @@ function resolveModule(
lhs.kind === "ident" ? [lhs.value.name] : lhs.segments; lhs.kind === "ident" ? [lhs.value.name] : lhs.segments;
if (res.kind === "item") { if (res.kind === "item") {
const module = unwrap(cx.ast.itemsById.get(res.id)); const module = findItem(cx, res.id);
if (module.kind === "mod" || module.kind === "extern") { if (module.kind === "mod" || module.kind === "extern") {
if (typeof expr.field.value === "number") { if (typeof expr.field.value === "number") {
throw new CompilerError( throw new CompilerError(

View file

@ -29,6 +29,7 @@ import {
Typecked, Typecked,
TyStruct, TyStruct,
Item, Item,
findCrateItem,
} from "./ast"; } from "./ast";
import { CompilerError, Span } from "./error"; import { CompilerError, Span } from "./error";
import { printTy } from "./printer"; import { printTy } from "./printer";
@ -79,6 +80,12 @@ function typeOfBuiltinValue(name: BuiltinName, span: Span): Ty {
return mkTyFn([TY_STRING], TY_I32); return mkTyFn([TY_STRING], TY_I32);
case "__string_len": case "__string_len":
return mkTyFn([TY_STRING], TY_I32); return mkTyFn([TY_STRING], TY_I32);
case "__memory_size":
return mkTyFn([], TY_I32);
case "__memory_grow":
return mkTyFn([TY_I32], TY_I32);
case "__i32_extend_to_i64_u":
return mkTyFn([TY_I32], TY_INT);
default: { default: {
throw new CompilerError(`\`${name}\` cannot be used as a value`, span); throw new CompilerError(`\`${name}\` cannot be used as a value`, span);
} }
@ -123,12 +130,10 @@ export function typeck(
function typeOfItem(itemId: ItemId, cause: Span): Ty { function typeOfItem(itemId: ItemId, cause: Span): Ty {
if (itemId.crateId !== ast.id) { if (itemId.crateId !== ast.id) {
console.log(otherCrates);
const crate = unwrap( const crate = unwrap(
otherCrates.find((crate) => crate.id === itemId.crateId) otherCrates.find((crate) => crate.id === itemId.crateId)
); );
const item = unwrap(crate.itemsById.get(itemId)); const item = findCrateItem(crate, itemId);
switch (item.kind) { switch (item.kind) {
case "function": case "function":
case "import": case "import":
@ -150,7 +155,7 @@ export function typeck(
} }
} }
const item = unwrap(ast.itemsById.get(itemId)); const item = findCrateItem(ast, itemId);
const ty = itemTys.get(itemId); const ty = itemTys.get(itemId);
if (ty) { if (ty) {
return ty; return ty;
@ -236,12 +241,11 @@ export function typeck(
function findItem(itemId: ItemId): Item<Resolved> { function findItem(itemId: ItemId): Item<Resolved> {
if (itemId.crateId === ast.id) { if (itemId.crateId === ast.id) {
return unwrap(ast.itemsById.get(itemId)); return findCrateItem(ast, itemId);
} }
return unwrap( return findCrateItem(
unwrap( unwrap(otherCrates.find((crate) => crate.id === itemId.crateId)),
otherCrates.find((crate) => crate.id === itemId.crateId) itemId
).itemsById.get(itemId)
); );
} }
@ -1056,6 +1060,10 @@ function checkBinary(
return { ...expr, lhs, rhs, ty: TY_BOOL }; return { ...expr, lhs, rhs, ty: TY_BOOL };
} }
if (lhsTy.kind === "i32" && rhsTy.kind === "i32") {
return { ...expr, lhs, rhs, ty: TY_BOOL };
}
if (lhsTy.kind === "string" && rhsTy.kind === "string") { if (lhsTy.kind === "string" && rhsTy.kind === "string") {
return { ...expr, lhs, rhs, ty: TY_BOOL }; return { ...expr, lhs, rhs, ty: TY_BOOL };
} }
@ -1096,7 +1104,7 @@ function checkUnary(
if ( if (
expr.unaryKind === "!" && expr.unaryKind === "!" &&
(rhsTy.kind === "int" || rhsTy.kind === "bool") (rhsTy.kind === "int" || rhsTy.kind === "i32" || rhsTy.kind === "bool")
) { ) {
return { ...expr, rhs, ty: rhsTy }; return { ...expr, rhs, ty: rhsTy };
} }

View file

@ -4,7 +4,7 @@
// Base types. // Base types.
export type u32 = number; export type u32 = number;
export type u64 = number; export type u64 = bigint;
export type f32 = number; export type f32 = number;
export type f64 = number; export type f64 = number;
export type VecByte = Uint8Array; export type VecByte = Uint8Array;
@ -73,7 +73,7 @@ export type BitWidth = "32" | "64";
export type Sign = "u" | "s"; export type Sign = "u" | "s";
export type NumericInstr = export type NumericInstr =
| { kind: "i32.const"; imm: u32 } | { kind: "i32.const"; imm: bigint }
| { kind: "i64.const"; imm: u64 } | { kind: "i64.const"; imm: u64 }
| { kind: "f32.const"; imm: f32 } | { kind: "f32.const"; imm: f32 }
| { kind: "f64.const"; imm: f64 } | { kind: "f64.const"; imm: f64 }

View file

@ -29,7 +29,7 @@ const EXAMPLE_MODULE: Module = {
type: { kind: "typeidx", idx: 1 }, type: { kind: "typeidx", idx: 1 },
instrs: [{ kind: "local.get", imm: 0 }], instrs: [{ kind: "local.get", imm: 0 }],
}, },
{ kind: "i32.const", imm: 1 }, { kind: "i32.const", imm: 1n },
{ kind: "i32.add" }, { kind: "i32.add" },
], ],
}, },
@ -49,7 +49,7 @@ const EXAMPLE_MODULE: Module = {
globals: [ globals: [
{ {
type: { mut: "const", type: "i32" }, type: { mut: "const", type: "i32" },
init: [{ kind: "i32.const", imm: 0 }], init: [{ kind: "i32.const", imm: 0n }],
_name: "globalling", _name: "globalling",
}, },
], ],
@ -70,7 +70,7 @@ const EXAMPLE_MODULE: Module = {
mode: { mode: {
kind: "active", kind: "active",
memory: 0, memory: 0,
offset: [{ kind: "i32.const", imm: 0 }], offset: [{ kind: "i32.const", imm: 0n }],
}, },
init: new Uint8Array(), init: new Uint8Array(),
_name: "very-active-data", _name: "very-active-data",
@ -86,7 +86,7 @@ it("should print a Wasm module with the correct formatting", () => {
(type (func (param i32) (result i32))) (type (func (param i32) (result i32)))
(type (func (param) (result i32))) (type (func (param) (result i32)))
(import "left-pad" "padLeft" (func (type 0))) (import "left-pad" "padLeft" (func (type 0)))
(func $addOne (type 0) (func $addOne (;1;) (type 0)
(local i32 i32) (local i32 i32)
local.set 0 local.set 0
block (type 1) block (type 1)

View file

@ -72,7 +72,7 @@ class FmtCtx {
this.word(word, chalk.blue); this.word(word, chalk.blue);
} }
type(word: string | number) { type(word: string | number | bigint) {
this.word(word, chalk.green); this.word(word, chalk.green);
} }
@ -90,7 +90,10 @@ class FmtCtx {
} }
} }
word(word: string | number, color: (s: string) => string = identity) { word(
word: string | number | bigint,
color: (s: string) => string = identity
) {
const last = this.wordsInSexpr.length - 1; const last = this.wordsInSexpr.length - 1;
if (this.wordsInSexpr[last] > 0 && !this.freshLinebreak) { if (this.wordsInSexpr[last] > 0 && !this.freshLinebreak) {
// The first word hugs the left parenthesis. // The first word hugs the left parenthesis.
@ -436,10 +439,17 @@ function printInstr(instr: Instr, f: FmtCtx) {
case "call": { case "call": {
f.controlFlow(instr.kind); f.controlFlow(instr.kind);
f.word(instr.func); f.word(instr.func);
const name = f.mod.funcs[instr.func]?._name; if (instr.func < f.mod.imports.length) {
const name = f.mod.imports[instr.func]?.name;
if (name !== undefined) { if (name !== undefined) {
f.comment(name); f.comment(name);
} }
} else {
const name = f.mod.funcs[instr.func - f.mod.imports.length]?._name;
if (name !== undefined) {
f.comment(name);
}
}
break; break;
} }
case "call_indirect": case "call_indirect":

64
std.nil
View file

@ -1,8 +1,15 @@
function printlnI32(x: I32) = (
printI32(x);
print("\n");
);
function printlnInt(x: Int) = ( function printlnInt(x: Int) = (
printInt(x); printInt(x);
print("\n"); print("\n");
); );
function printI32(x: I32) = printInt(i32ToInt(x));
function printInt(x: Int) = ( function printInt(x: Int) = (
let mag = log10(x); let mag = log10(x);
@ -58,8 +65,59 @@ function println(s: String) = (
print("\n"); print("\n");
); );
mod alloc ( mod rt (
function allocateItem(size: I32): I32 = ( // Start the heap at 1024. In practice this could probably be as low as we want.
global BASE_PTR: I32 = 1024_I32;
global HEAD_PTR: I32 = 1024_I32;
/*
Every struct has a header of an I32 as a refcount.
*/
// Allocate a new item. We do not deallocate anything yet.
// lol.
function allocateItem(objSize: I32, align: I32): I32 = (
if align < 4_I32 then std.abort("invalid alignment");
// Include the refcount header.
let actualSize = 4_I32 + objSize;
// Let's see whether we can fit the refcount into the align bits.
// I happen to know that everything will always be at least 4 bytes aligned.
let alignedPtr = std.alignUp(HEAD_PTR, align);
let actualObjPtr = if (alignedPtr - HEAD_PTR) > align then (
alignedPtr - 4_I32
) else (
// Take up the next spot.
alignedPtr + align - 4_I32
);
let newHeadPtr = actualObjPtr + actualSize;
if newHeadPtr > __memory_size() then (
// 16 pages, very arbitrary.
let result = __memory_grow(16_I32);
// If allocation failed we get -1. We don't have negative numbers yet, lol.
if result > 100000000_I32 then (
std.abort("failed to grow memory");
);
);
0_I32
);
);
function alignUp(x: I32, align: I32): I32 = (x + (align - 1_I32)) & !(align - 1_I32);
function i32ToInt(x: I32): Int = __i32_extend_to_i64_u(x);
function abort(message: String) = (
print("fatal error: ");
print(message);
println(".. aborting");
trap();
);
function main() = (
std.rt.allocateItem(100000000_I32, 8_I32);
); );
)

View file

@ -11,7 +11,7 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */ /* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, "target": "ES2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */ // "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */