mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-15 00:45:04 +01:00
generate more code
This commit is contained in:
parent
ccd8008731
commit
42bc96dbce
7 changed files with 306 additions and 51 deletions
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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue