Skip to content

Commit

Permalink
Tail call stack flattening (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
AjaniBilby authored Mar 27, 2024
1 parent 0ee3d57 commit 3a318cb
Show file tree
Hide file tree
Showing 22 changed files with 430 additions and 291 deletions.
5 changes: 2 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
"files.associations": {},
"editor.insertSpaces": false,
"files.eol": "\n",
"editor.rulers": [ 80, 120 ],
"cSpell.words": [
"bitcode",
"codegen",
"Fuwawa",
"impls",
"iovs",
"Yeet"
"iovs"
],
"deno.enable": true
}
10 changes: 5 additions & 5 deletions source/bnf/syntax.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ block ::= %( "{" w* ) block_stmt* %( w* "}" w* ) ;
func_call ::= access func_call_body;
func_call_body ::= %( w* "(" w* ) ( expr %w* ( %( "," w* ) expr %w* )* )? %( ")" w* ) ;

return ::= %"return" "_call"? ( %w+ expr)? %( w* ";" w* );
return ::= %"return" "_tail"? ( %w+ expr)? %( w* ";" w* );
raise ::= %"raise" %w+ expr %( ";" w* ); # TODO rename to lift
# drop ::= %"drop" %w+ expr %( ";" w* );

Expand All @@ -108,15 +108,15 @@ expr ::= expr_arg %w* ( ...expr_infix %w* expr_arg %w* )* ;
| "as" | "instanceof"
| "." | "->" ;
expr_postfix ::= expr_call | expr_get | expr_param | expr_loan ;
expr_param ::= %"#[" %w* arg_list %w* %"]" ;
expr_call ::= %"(" %w* arg_list %w* %")" ;
expr_get ::= %"[" %w* arg_list %w* %"]" ;
expr_param ::= %"#[" %w* arg_list? %w* %"]" ;
expr_call ::= %"(" %w* arg_list? %w* %")" ;
expr_get ::= %"[" %w* arg_list? %w* %"]" ;
expr_loan ::= "@" | "$" ;
expr_arg ::= expr_prefix? %w* expr_val %w* expr_postfix* ;
expr_val ::= constant | expr_brackets | block | container | if | name ;
expr_brackets ::= %( "(" w* ) expr %( w* ")" ) ;

arg_list ::= ( expr %w* ","? %w* )* ;
arg_list ::= expr ( %(w* "," w*) expr )* ;

if ::= %("if" w*) expr %w* expr %w* ( %"else" %w* expr )? ;

Expand Down
12 changes: 6 additions & 6 deletions source/bnf/syntax.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -937,7 +937,7 @@ export type Term_Return = {
count: number,
ref: _Shared.ReferenceRange,
value: [
{ type: '(...)?', value: [] | [_Literal & {value: "\x5fcall"}], start: number, end: number, count: number, ref: _Shared.ReferenceRange },
{ type: '(...)?', value: [] | [_Literal & {value: "\x5ftail"}], start: number, end: number, count: number, ref: _Shared.ReferenceRange },
{ type: '(...)?', value: [] | [{
type: '(...)',
start: number,
Expand Down Expand Up @@ -1060,7 +1060,7 @@ export type Term_Expr_param = {
count: number,
ref: _Shared.ReferenceRange,
value: [
Term_Arg_list
{ type: '(...)?', value: [] | [Term_Arg_list], start: number, end: number, count: number, ref: _Shared.ReferenceRange }
]
}
export declare function Parse_Expr_param (i: string, refMapping?: boolean): _Shared.ParseError | {
Expand All @@ -1077,7 +1077,7 @@ export type Term_Expr_call = {
count: number,
ref: _Shared.ReferenceRange,
value: [
Term_Arg_list
{ type: '(...)?', value: [] | [Term_Arg_list], start: number, end: number, count: number, ref: _Shared.ReferenceRange }
]
}
export declare function Parse_Expr_call (i: string, refMapping?: boolean): _Shared.ParseError | {
Expand All @@ -1094,7 +1094,7 @@ export type Term_Expr_get = {
count: number,
ref: _Shared.ReferenceRange,
value: [
Term_Arg_list
{ type: '(...)?', value: [] | [Term_Arg_list], start: number, end: number, count: number, ref: _Shared.ReferenceRange }
]
}
export declare function Parse_Expr_get (i: string, refMapping?: boolean): _Shared.ParseError | {
Expand Down Expand Up @@ -1181,15 +1181,15 @@ export type Term_Arg_list = {
count: number,
ref: _Shared.ReferenceRange,
value: [
Term_Expr,
{ type: '(...)*', value: Array<{
type: '(...)',
start: number,
end: number,
count: number,
ref: _Shared.ReferenceRange,
value: [
Term_Expr,
{ type: '(...)?', value: [] | [_Literal & {value: "\x2c"}], start: number, end: number, count: number, ref: _Shared.ReferenceRange }
Term_Expr
]
}>, start: number, end: number, count: number, ref: _Shared.ReferenceRange }
]
Expand Down
2 changes: 1 addition & 1 deletion source/bnf/syntax.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion source/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ TimerEnd("serialize");
TimerStart("wasm2wat");
const command = new Deno.Command(
"wasm2wat",
{ args: ["-v", "out.wasm", "-o", "out.wat"] }
{ args: ["-v", "out.wasm", "-o", "out.wat", "--enable-all"] }
);
const { code, stdout, stderr } = await command.output();
if (code !== 0) {
Expand Down
2 changes: 1 addition & 1 deletion source/compiler/codegen/allocation/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export class StackAllocator {
}

resolve() {
if (this.checkpointRef.hasAllocations()) throw new Error(
if (this.checkpointRef.hasAllocations()) console.warn(
`Stack leak: ${this.checkpointRef.getAllocationCount()} stack values are still allocated after stack frame end `
+ this.checkpointRef.getAllocations()
);
Expand Down
63 changes: 42 additions & 21 deletions source/compiler/codegen/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import Function from "~/compiler/function.ts";
import { BasePointerType, LinearType, OperandType, SolidType, IsRuntimeType, IsSolidType } from "~/compiler/codegen/expression/type.ts";
import { IntrinsicType, IntrinsicValue, none, never } from "~/compiler/intrinsic.ts";
import { Instruction, AnyInstruction } from "~/wasm/index.ts";
import { ReferenceRange, SourceView } from "~/parser.ts";
import { ResolveLinearType, Store } from "~/compiler/codegen/expression/helper.ts"
import { AssertUnreachable } from "~/helper.ts";
import { ReferenceRange } from "~/parser.ts";
import { CompileExpr } from "~/compiler/codegen/expression/index.ts";
import { VirtualType } from "~/compiler/intrinsic.ts";
import { Variable } from "~/compiler/codegen/variable.ts";
Expand Down Expand Up @@ -59,6 +59,11 @@ export class Context {
}
}

markFailure(reason: string, ref: ReferenceRange) {
console.error(reason + SourceView(this.file.path, this.file.name, ref));
this.file.markFailure();
}

mergeBlock() {
if (this.block.length !== 1) return;
if (!(this.block[0] instanceof Block)) return;
Expand All @@ -83,10 +88,10 @@ function CompileDeclare(ctx: Context, syntax: Syntax.Term_Declare) {
const expr = syntax.value[2].value[0];



if (banned.namespaces.includes(name)) Panic(
// Worth continuing compilation, to test the validity of the invalid variable name's use
if (banned.namespaces.includes(name)) ctx.markFailure(
`${colors.red("Error")}: You're not allowed to call a variable ${name}\n`,
{ path: ctx.file.path, name: ctx.file.name, ref: syntax.value[0].value[0].ref }
syntax.value[0].value[0].ref
)

if (ctx.scope.hasVariable(name)) Panic(
Expand Down Expand Up @@ -152,16 +157,18 @@ function CompileDeclare(ctx: Context, syntax: Syntax.Term_Declare) {
if (resolveType instanceof IntrinsicValue) {
const alloc = ctx.scope.stack.allocate(resolveType.type.size, resolveType.type.align);
linear = LinearType.make(resolveType.type.value, alloc, ctx.file.owner.project.stackBase);

variable = ctx.scope.registerVariable(name, linear, syntax.ref);
linear.markConsumed(syntax.ref); // uninited
linear.pin();
} else if (resolveType instanceof LinearType) {
linear = resolveType;
// Just claim ownership of the container created in the expr
variable = ctx.scope.registerVariable(name, resolveType, syntax.ref);
variable.type.pin();
return;
} else AssertUnreachable(resolveType);

variable = ctx.scope.registerVariable(name, linear, syntax.ref);
linear.markConsumed(syntax.ref); // uninited
linear.pin();
}


Assign(ctx, variable.type, resolveType, syntax.ref);
}

Expand Down Expand Up @@ -218,9 +225,9 @@ export function Assign(ctx: Context, target: LinearType, expr: OperandType, ref:
{ path: ctx.file.path, name: ctx.file.name, ref: ref }
)

const error = () => Panic(
const error = () => ctx.markFailure(
`${colors.red("Error")}: ${target.type.getTypeName()} != ${expr.getTypeName()}\n`,
{ path: ctx.file.path, name: ctx.file.name, ref: ref }
ref
);

if (expr instanceof IntrinsicValue) {
Expand All @@ -241,7 +248,7 @@ export function Assign(ctx: Context, target: LinearType, expr: OperandType, ref:
ctx.block.push(Instruction.local.get(reg.ref));
reg.free();

Store(ctx, expr.type, target.offset);
Store(ctx, expr.type, target.offset, ref);
target.markDefined();
return;
}
Expand Down Expand Up @@ -275,6 +282,10 @@ export function Assign(ctx: Context, target: LinearType, expr: OperandType, ref:
function CompileStatement(ctx: Context, syntax: Syntax.Term_Statement) {
const res = CompileExpr(ctx, syntax.value[0]);

if (res instanceof LinearType) res.dispose();

// TOOD: drop structs properly

if (res !== none && res !== never) {
ctx.block.push(Instruction.drop());
}
Expand All @@ -287,10 +298,23 @@ function CompileReturn(ctx: Context, syntax: Syntax.Term_Return): typeof never {
const isTail = syntax.value[0].value.length > 0;
const ref = syntax.ref;

if (isTail) Panic(
`${colors.red("Error")}: Unimplemented tail call return\n`,
{ path: ctx.file.path, name: ctx.file.name, ref }
);
// Guard: tail call
if (isTail) {
if (!maybe_expr) Panic(
`${colors.red("Error")}: Missing return_call expression\n`,
{ path: ctx.file.path, name: ctx.file.name, ref }
);

if (maybe_expr.value[0].value[0].value.length != 0) Panic(
`${colors.red("Error")}: Missing return_call expression\n`,
{ path: ctx.file.path, name: ctx.file.name, ref }
);

const expr = CompileExpr(ctx, maybe_expr, undefined, true);
if (expr !== never) throw new Error("Expected a never returning expression");

return never;
}

// Guard: return none
if (ctx.function.returns instanceof VirtualType) {
Expand Down Expand Up @@ -324,10 +348,7 @@ function CompileReturn(ctx: Context, syntax: Syntax.Term_Return): typeof never {

// Guard: simple intrinsic return
if (goal.type instanceof IntrinsicType) {
if (!goal.type.like(expr)) Panic(
`${colors.red("Error")}: Return type miss-match, expected ${colors.cyan(goal.type.getTypeName())} got ${colors.cyan(expr.getTypeName())}\n`,
{ path: ctx.file.path, name: ctx.file.name, ref }
);
if (!goal.type.like(expr)) ctx.markFailure(`${colors.red("Error")}: Return type miss-match, expected ${colors.cyan(goal.type.getTypeName())} got ${colors.cyan(expr.getTypeName())}\n`, ref);

if (expr instanceof LinearType) {
ResolveLinearType(ctx, expr, ref, true);
Expand Down
75 changes: 22 additions & 53 deletions source/compiler/codegen/expression/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { IntrinsicValue } from "~/compiler/intrinsic.ts";
import { Instruction } from "~/wasm/index.ts";
import { SolidType } from "~/compiler/codegen/expression/type.ts";
import { Context } from "~/compiler/codegen/context.ts";
import { Panic } from "~/compiler/helper.ts";

export function CompileConstant(ctx: Context, syntax: Syntax.Term_Constant, expect?: SolidType): IntrinsicValue {
if (!(expect instanceof IntrinsicType)) expect = undefined;
Expand All @@ -34,110 +33,80 @@ export function CompileBool(ctx: Context, syntax: Syntax.Term_Boolean) {
}

function CompileInt(ctx: Context, syntax: Syntax.Term_Integer, expect?: IntrinsicType) {
const num = Number(syntax.value[0].value);
let num = Number(syntax.value[0].value);

if (isNaN(num))
Panic(`${colors.red("Error")}: Invalid number ${syntax.value[0].value}\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});
if (isNaN(num)) {
ctx.markFailure(`${colors.red("Error")}: Invalid number ${syntax.value[0].value}\n`, syntax.ref);
num = 0;
}

if (!Number.isInteger(num))
Panic(`${colors.red("Error")}: Invalid integer ${syntax.value[0].value}\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});
if (!Number.isInteger(num)) {
ctx.markFailure(`${colors.red("Error")}: Invalid integer ${syntax.value[0].value}\n`, syntax.ref)
num = 0;
};

const unsigned = expect === u8 || expect === u16 || expect === u32 || expect === u64;
const size = expect?.size || 4;

if (size === 8) {
ctx.block.push(Instruction.const.i64(num));
if (unsigned) {
if (num > 2**64) Panic(`${colors.red("Error")}: Value too big for size\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});
if (num > 2**64) ctx.markFailure(`${colors.red("Error")}: Value too big for size\n`, syntax.ref);

return u64.value;
}

if (num > 2**63) Panic(`${colors.red("Error")}: Value too big for size\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});

if (num < -(2**63)) Panic(`${colors.red("Error")}: Value too small for size\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});
if (num < -(2**63)) ctx.markFailure(`${colors.red("Error")}: Value too small for size\n`, syntax.ref);
if (num > 2**63) ctx.markFailure(`${colors.red("Error")}: Value too big for size\n`, syntax.ref);

return i64.value;
}

if (size === 2) {
ctx.block.push(Instruction.const.i32(num));
if (unsigned) {
if (num > 2**16) Panic(`${colors.red("Error")}: Value too big for size\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});
if (num > 2**16) ctx.markFailure(`${colors.red("Error")}: Value too big for size\n`, syntax.ref);

return u16.value;
}

if (num > 2**15) Panic(`${colors.red("Error")}: Value too big for size\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});

if (num < -(2**15)) Panic(`${colors.red("Error")}: Value too small for size\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});
if (num < -(2**15)) ctx.markFailure(`${colors.red("Error")}: Value too small for size\n`, syntax.ref);
if (num > 2**15) ctx.markFailure(`${colors.red("Error")}: Value too big for size\n`, syntax.ref);

return i16.value;
}

if (size === 1) {
ctx.block.push(Instruction.const.i32(num));
if (unsigned) {
if (num > 2**8) Panic(`${colors.red("Error")}: Value too big for size\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});
if (num > 2**8) ctx.markFailure(`${colors.red("Error")}: Value too big for size\n`, syntax.ref);

return u8.value;
}

if (num > 2**7) Panic(`${colors.red("Error")}: Value too big for size\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});

if (num < -(2**7)) Panic(`${colors.red("Error")}: Value too small for size\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});
if (num < -(2**7)) ctx.markFailure(`${colors.red("Error")}: Value too small for size\n`, syntax.ref);
if (num > 2**7) ctx.markFailure(`${colors.red("Error")}: Value too big for size\n`, syntax.ref);

return i8.value;
}

ctx.block.push(Instruction.const.i32(num));
if (unsigned) {
if (num > 2**32) Panic(`${colors.red("Error")}: Value too big for size\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});
if (num > 2**32) ctx.markFailure(`${colors.red("Error")}: Value too big for size\n`, syntax.ref);

return u32.value;
}

if (num > 2**31) Panic(`${colors.red("Error")}: Value too big for size\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});

if (num < -(2**31)) Panic(`${colors.red("Error")}: Value too small for size\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});
if (num < -(2**31)) ctx.markFailure(`${colors.red("Error")}: Value too small for size\n`, syntax.ref);
if (num > 2**31) ctx.markFailure(`${colors.red("Error")}: Value too big for size\n`, syntax.ref);

return i32.value;
}

function CompileFloat(ctx: Context, syntax: Syntax.Term_Float, expect?: IntrinsicType) {
const num = Number(syntax.value[0].value);

if (isNaN(num)) Panic(`${colors.red("Error")}: Invalid number ${syntax.value[0].value}\n`, {
path: ctx.file.path, name: ctx.file.name, ref: syntax.ref
});
if (isNaN(num)) ctx.markFailure(`${colors.red("Error")}: Invalid number ${syntax.value[0].value}\n`, syntax.ref);

if (expect === f64) {
ctx.block.push(Instruction.const.f64(num));
Expand Down
Loading

0 comments on commit 3a318cb

Please sign in to comment.