generate more code

This commit is contained in:
nora 2023-07-26 20:50:48 +02:00
parent ccd8008731
commit 42bc96dbce
7 changed files with 306 additions and 51 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
/target
*.tsbuildinfo
/*-example.wat
/out.wat

View file

@ -155,8 +155,8 @@ const BINARY_KIND_PREC_CLASS = new Map<BinaryKind, number>([
export function binaryExprPrecedenceClass(k: BinaryKind): number {
const cls = BINARY_KIND_PREC_CLASS.get(k);
if (!cls) {
throw new Error(`Invalid binary kind: ${k}`);
if (cls === undefined) {
throw new Error(`Invalid binary kind: '${k}'`);
}
return cls;
}
@ -209,15 +209,27 @@ export type Resolution =
}
| {
kind: "builtin";
name: string;
name: BuiltinName;
};
export const BUILTINS = [
"print",
"String",
"Int",
"Bool",
"true",
"false",
] as const;
export type BuiltinName = (typeof BUILTINS)[number];
export type TyString = {
kind: "string";
};
export type TyInt = {
kind: "int";
signed: boolean;
};
export type TyBool = {
@ -259,7 +271,7 @@ export function tyIsUnit(ty: Ty): ty is TyUnit {
export const TY_UNIT: Ty = { kind: "tuple", elems: [] };
export const TY_STRING: Ty = { kind: "string" };
export const TY_BOOL: Ty = { kind: "bool" };
export const TY_INT: Ty = { kind: "int" };
export const TY_INT: Ty = { kind: "int", signed: false };
// folders

View file

@ -6,13 +6,17 @@ import { printAst } from "./printer";
import { resolve } from "./resolve";
import { typeck } from "./typeck";
import { writeModuleWatToString } from "./wasm/wat";
import fs from "fs";
import { exec } from "child_process";
const input = `
function main(i: Int): Int = 0;
function main(i: Int, j: Int): Bool = false == (i == 0);
`;
function main() {
withErrorHandler(input, () => {
const start = Date.now();
const tokens = tokenize(input);
console.log("-----TOKENS------------");
console.log(tokens);
@ -37,8 +41,31 @@ function main() {
console.log("-----wasm--------------");
const wasmModule = lowerToWasm(typecked);
const moduleStringColor = writeModuleWatToString(wasmModule, true);
const moduleString = writeModuleWatToString(wasmModule);
console.log(moduleString);
console.log(moduleStringColor);
fs.writeFileSync("out.wat", moduleString);
console.log("--validate wasm-tools--");
exec("wasm-tools validate out.wat", (error, stdout, stderr) => {
if (error && error.code === 1) {
console.log(stderr);
} else if (error) {
console.error(`failed to spawn wasm-tools: ${error}`);
} else {
if (stderr) {
console.log(stderr);
}
if (stdout) {
console.log(stdout);
}
}
console.log(`finished in ${Date.now() - start}ms`);
});
});
}

View file

@ -1,4 +1,4 @@
import { Ast, FunctionDef, Item, Ty, TyFn, varUnreachable } from "./ast";
import { Ast, Expr, FunctionDef, Item, Ty, TyFn, varUnreachable } from "./ast";
import * as wasm from "./wasm/defs";
type StringifiedForMap<T> = string;
@ -39,13 +39,7 @@ export function lower(ast: Ast): wasm.Module {
ast.forEach((item) => {
switch (item.kind) {
case "function": {
const fcx: FuncContext = {
cx,
item,
func: item.node,
};
lowerFunc(fcx);
lowerFunc(cx, item, item.node);
}
}
});
@ -57,6 +51,8 @@ type FuncContext = {
cx: Context;
item: Item;
func: FunctionDef;
wasm: wasm.Func;
varLocations: VarLocation[];
};
type Abi = { params: ArgAbi[]; ret: RetAbi };
@ -64,22 +60,210 @@ type Abi = { params: ArgAbi[]; ret: RetAbi };
type ArgAbi = { kind: "scalar"; type: wasm.ValType } | { kind: "zst" };
type RetAbi = { kind: "scalar"; type: wasm.ValType } | { kind: "zst" };
function lowerFunc(fcx: FuncContext) {
const abi = computeAbi(fcx.func.ty!);
const wasmType = wasmTypeForAbi(abi);
const type = internFuncType(fcx.cx, wasmType);
type VarLocation = { kind: "local"; idx: number } | { kind: "zst" };
function lowerFunc(cx: Context, item: Item, func: FunctionDef) {
const abi = computeAbi(func.ty!);
const { type: wasmType, paramLocations } = wasmTypeForAbi(abi);
const type = internFuncType(cx, wasmType);
const wasmFunc: wasm.Func = {
_name: func.name,
type,
locals: [],
body: [],
};
const fcx: FuncContext = {
cx,
item,
func,
wasm: wasmFunc,
varLocations: paramLocations,
};
lowerExpr(fcx, wasmFunc.body, fcx.func.body);
const idx = fcx.cx.mod.funcs.length;
fcx.cx.mod.funcs.push(wasmFunc);
fcx.cx.funcIndices.set(fcx.item.id, idx);
}
/*
Expression lowering.
- the result of an expression evaluation is stored on the top of the stack
*/
function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
const ty = expr.ty!;
switch (expr.kind) {
case "empty":
// A ZST, do nothing.
return;
case "let":
// Let, that's complicated.
todo("let");
case "block":
if (expr.exprs.length === 1) {
lowerExpr(fcx, instrs, expr.exprs[0]);
}
break;
case "literal":
switch (expr.value.kind) {
case "str":
todo("strings");
case "int":
instrs.push({ kind: "i64.const", imm: expr.value.value });
}
break;
case "ident":
const res = expr.value.res!;
switch (res.kind) {
case "local": {
const location =
fcx.varLocations[fcx.varLocations.length - 1 - res.index];
loadVariable(instrs, location);
break;
}
case "item":
todo("item ident res");
case "builtin":
switch (res.name) {
case "false":
instrs.push({ kind: "i32.const", imm: 0 });
break;
case "true":
instrs.push({ kind: "i32.const", imm: 1 });
break;
case "print":
todo("print function");
default: {
throw new Error(`${res.name}#B is not a value`);
}
}
}
break;
case "binary":
// By evaluating the LHS first, the RHS is on top, which
// is correct as it's popped first. Evaluating the LHS first
// is correct for the source language too so great, no swapping.
lowerExpr(fcx, instrs, expr.lhs);
lowerExpr(fcx, instrs, expr.rhs);
if (expr.lhs.ty!.kind === "int" && expr.rhs.ty!.kind === "int") {
let kind: wasm.Instr["kind"];
switch (expr.binaryKind) {
case "+":
kind = "i64.add";
break;
case "-":
kind = "i64.sub";
break;
case "*":
kind = "i64.mul";
break;
case "/":
kind = "i64.div_u";
break;
case "&":
kind = "i64.and";
break;
case "|":
kind = "i64.or";
break;
case "<":
kind = "i64.lt_u";
break;
case ">":
kind = "i64.gt_u";
break;
case "==":
kind = "i64.eq";
break;
case "<=":
kind = "i64.le_u";
break;
case ">=":
kind = "i64.ge_u";
break;
case "!=":
kind = "i64.ne";
break;
}
instrs.push({ kind });
} else if (expr.lhs.ty!.kind === "bool" && expr.rhs.ty!.kind === "bool") {
let kind: wasm.Instr["kind"];
switch (expr.binaryKind) {
case "&":
kind = "i32.and";
break;
case "|":
kind = "i32.or";
break;
case "==":
kind = "i32.eq";
break;
case "!=":
kind = "i32.ne";
break;
case "<":
case ">":
case "<=":
case ">=":
case "+":
case "-":
case "*":
case "/":
throw new Error(`Invalid bool binary expr: ${expr.binaryKind}`);
}
instrs.push({ kind });
} else {
todo("non int/bool binary expr");
}
break;
case "unary":
lowerExpr(fcx, instrs, expr.rhs);
switch (expr.unaryKind) {
case "!":
if (ty.kind === "bool") {
// `xor RHS, 1` flips the lowermost bit.
instrs.push({ kind: "i64.const", imm: 1 });
instrs.push({ kind: "i64.xor" });
} else if (ty.kind === "int") {
// `xor RHS, -1` flips all bits.
todo("Thanks to JS, we cannot represent -1 i64 yet");
}
break;
case "-":
todo("negation");
}
break;
case "call":
todo("call");
case "if":
todo("ifs");
}
}
function loadVariable(instrs: wasm.Instr[], loc: VarLocation) {
switch (loc.kind) {
case "local": {
instrs.push({ kind: "local.get", imm: loc.idx });
break;
}
case "zst":
// Load the ZST:
// ...
// 🪄 poof, the ZST is on the stack now.
break;
}
}
function computeAbi(ty: TyFn): Abi {
const scalar = (type: wasm.ValType): ArgAbi & RetAbi =>
({ kind: "scalar", type } as const);
@ -141,18 +325,24 @@ function computeAbi(ty: TyFn): Abi {
return { params, ret };
}
function wasmTypeForAbi(abi: Abi): wasm.FuncType {
const params = abi.params
.map((arg) => {
switch (arg.kind) {
case "scalar":
return arg.type;
case "zst":
return undefined;
}
})
.filter(exists);
function wasmTypeForAbi(abi: Abi): {
type: wasm.FuncType;
paramLocations: VarLocation[];
} {
const params: wasm.ValType[] = [];
const paramLocations: VarLocation[] = [];
abi.params.forEach((arg) => {
switch (arg.kind) {
case "scalar":
paramLocations.push({ kind: "local", idx: params.length });
params.push(arg.type);
break;
case "zst":
paramLocations.push({ kind: "zst" });
return undefined;
}
});
let returns: wasm.ValType[];
switch (abi.ret.kind) {
case "scalar":
@ -163,7 +353,31 @@ function wasmTypeForAbi(abi: Abi): wasm.FuncType {
break;
}
return { params, returns };
return { type: { params, returns }, paramLocations };
}
function wasmTypeForBody(ty: Ty): wasm.ValType | undefined {
switch (ty.kind) {
case "string":
todo("string types");
case "int":
return "i64";
case "bool":
return "i32";
case "list":
todo("list types");
case "tuple":
if (ty.elems.length === 0) {
return undefined;
} else if (ty.elems.length === 1) {
return wasmTypeForBody(ty.elems[0]);
}
todo("complex tuples");
case "fn":
todo("fn types");
case "var":
varUnreachable();
}
}
function todo(msg: string): never {

View file

@ -1,5 +1,7 @@
import {
Ast,
BUILTINS,
BuiltinName,
DEFAULT_FOLDER,
Folder,
Identifier,
@ -10,14 +12,7 @@ import {
} from "./ast";
import { CompilerError } from "./error";
const BUILTINS = new Set<string>([
"print",
"String",
"Int",
"Bool",
"true",
"false",
]);
const BUILTIN_SET = new Set<string>(BUILTINS);
export function resolve(ast: Ast): Ast {
const items = new Map<string, number>();
@ -40,7 +35,7 @@ export function resolve(ast: Ast): Ast {
const popped = scopes.pop();
if (popped !== expected) {
throw new Error(
`Scopes corrupted, wanted to pop ${name} but popped ${popped}`
`Scopes corrupted, wanted to pop ${expected} but popped ${popped}`
);
}
};
@ -66,8 +61,8 @@ export function resolve(ast: Ast): Ast {
};
}
if (BUILTINS.has(ident.name)) {
return { kind: "builtin", name: ident.name };
if (BUILTIN_SET.has(ident.name)) {
return { kind: "builtin", name: ident.name as BuiltinName };
}
throw new CompilerError(`cannot find ${ident.name}`, ident.span);
@ -88,7 +83,9 @@ export function resolve(ast: Ast): Ast {
item.node.params.forEach(({ name }) => scopes.push(name));
const body = superFoldExpr(item.node.body, this);
item.node.params.forEach(({ name }) => popScope(name));
const revParams = item.node.params.slice();
revParams.reverse();
revParams.forEach(({ name }) => popScope(name));
return {
kind: "function",

View file

@ -506,11 +506,11 @@ function checkBinary(expr: Expr & ExprBinary): Expr {
let lhsTy = expr.lhs.ty!;
let rhsTy = expr.rhs.ty!;
if (lhsTy.kind === "int" && rhsTy.kind === "int") {
return { ...expr, ty: TY_INT };
}
if (COMPARISON_KINDS.includes(expr.binaryKind)) {
if (lhsTy.kind === "int" && rhsTy.kind === "int") {
return { ...expr, ty: TY_BOOL };
}
if (lhsTy.kind === "string" && rhsTy.kind === "string") {
return { ...expr, ty: TY_BOOL };
}
@ -522,6 +522,10 @@ function checkBinary(expr: Expr & ExprBinary): Expr {
}
}
if (lhsTy.kind === "int" && rhsTy.kind === "int") {
return { ...expr, ty: TY_INT };
}
if (LOGICAL_KINDS.includes(expr.binaryKind)) {
if (lhsTy.kind === "bool" && rhsTy.kind === "bool") {
return { ...expr, ty: TY_BOOL };
@ -547,7 +551,7 @@ function checkUnary(expr: Expr & ExprUnary): Expr {
}
if (expr.unaryKind === "-" && rhsTy.kind == "int") {
return { ...expr, ty: rhsTy };
// Negating an unsigned integer is a bad idea.
}
throw new CompilerError(

View file

@ -100,7 +100,7 @@ class Formatter {
}
}
export function writeModuleWatToString(module: Module, color = true): string {
export function writeModuleWatToString(module: Module, color = false): string {
const parts: string[] = [];
const writer = (s: string) => parts.push(s);
printModule(module, new Formatter(writer, color));