mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-14 16:35:03 +01:00
generate more code
This commit is contained in:
parent
ccd8008731
commit
42bc96dbce
7 changed files with 306 additions and 51 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -2,3 +2,4 @@
|
|||
/target
|
||||
*.tsbuildinfo
|
||||
/*-example.wat
|
||||
/out.wat
|
||||
|
|
|
|||
20
src/ast.ts
20
src/ast.ts
|
|
@ -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
|
||||
|
||||
|
|
|
|||
31
src/index.ts
31
src/index.ts
|
|
@ -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`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
262
src/lower.ts
262
src/lower.ts
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue