Skip to content

Commit

Permalink
functions can return structs
Browse files Browse the repository at this point in the history
  • Loading branch information
AjaniBilby committed Mar 22, 2024
1 parent 0358d0c commit 031f5c9
Show file tree
Hide file tree
Showing 14 changed files with 176 additions and 92 deletions.
6 changes: 3 additions & 3 deletions source/bnf/syntax.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ container ::= %(w* "[" w*) ( container_item ( %(w* "," w*) container_item )* %w*
# Function
#=============================
function ::= func_head %w* ( block | ";" ) ;
func_head ::= %("fn" w+) ...name %( w* "(" w* ) func_args %(w* ")" w* ":" w*) access ;
func_head ::= %("fn" w+) ...name %( w* "(" w* ) func_args %(w* ")" w*) %(":" w*) access ;
func_args ::= ( func_arg %w* ( %( "," w* ) func_arg )* )? ;
func_arg ::= ...name %( w* ":" w* ) access ;

Expand All @@ -91,15 +91,15 @@ 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" "_tail"? %w+ expr %( ";" w* );
return ::= %"return" "_call"? %w+ expr? %( ";" w* );
raise ::= %"raise" %w+ expr %( ";" w* ); # TODO rename to lift
# drop ::= %"drop" %w+ expr %( ";" w* );

#=============================
# Expression
#=============================
expr ::= expr_arg %w* ( ...expr_infix %w* expr_arg %w* )* ;
expr_prefix ::= "!" | "-" | "return" ;
expr_prefix ::= "!" | "-" ;
expr_infix ::= "&&" | "||" | "^" | "==" | "!=" | "<=" | ">=" | "<" | ">"
| "%" | "*" | "/" | "+" | "-"
| "as" | "instanceof"
Expand Down
6 changes: 3 additions & 3 deletions source/bnf/syntax.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -913,8 +913,8 @@ export type Term_Return = {
count: number,
ref: _Shared.ReferenceRange,
value: [
{ type: '(...)?', value: [] | [_Literal & {value: "\x5ftail"}], start: number, end: number, count: number, ref: _Shared.ReferenceRange },
Term_Expr
{ type: '(...)?', value: [] | [_Literal & {value: "\x5fcall"}], start: number, end: number, count: number, ref: _Shared.ReferenceRange },
{ type: '(...)?', value: [] | [Term_Expr], start: number, end: number, count: number, ref: _Shared.ReferenceRange }
]
}
export declare function Parse_Return (i: string, refMapping?: boolean): _Shared.ParseError | {
Expand Down Expand Up @@ -976,7 +976,7 @@ export type Term_Expr_prefix = {
count: number,
ref: _Shared.ReferenceRange,
value: [
(_Literal & {value: "\x21"} | _Literal & {value: "\x2d"} | _Literal & {value: "return"})
(_Literal & {value: "\x21"} | _Literal & {value: "\x2d"})
]
}
export declare function Parse_Expr_prefix (i: string, refMapping?: boolean): _Shared.ParseError | {
Expand Down
2 changes: 1 addition & 1 deletion source/bnf/syntax.js

Large diffs are not rendered by default.

21 changes: 17 additions & 4 deletions source/compiler/codegen/allocation/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,11 @@ class StackCheckpoint {

free(alloc: StackAllocation) {
const index = this.local.findIndex(l => l === alloc);
if (index === -1) throw new Error(`Attempting to free${alloc?.tag ? ` tag[${alloc?.tag}]` : ""} non-local allocation`);

this.timeline.push(new StackEvent(StackEventType.free, alloc));
this.local.splice(index, 1);

if (index === -1) console.warn(`Warn: Attempting to free${alloc?.tag ? ` tag[${alloc?.tag}]` : ""} non-local allocation`);
else this.local.splice(index, 1);
}

private bind(alloc: StackAllocation) {
Expand All @@ -81,6 +82,10 @@ class StackCheckpoint {
return this.local.length;
}

getAllocations() {
return this.local.map(x => x.tag || ".unknown");
}

rewind() {
if (!this.previous) throw new Error("Cannot rewind root StackCheckpoint");

Expand Down Expand Up @@ -161,9 +166,14 @@ export class StackAllocator {
return this.latentSize;
}

getAllocationCount() {
return this.checkpointRef.getAllocationCount();
}

resolve() {
if (this.checkpointRef.hasAllocations()) throw new Error(
`Stack leak: ${this.checkpointRef.getAllocationCount()} stack values are still allocated after stack frame end`
`Stack leak: ${this.checkpointRef.getAllocationCount()} stack values are still allocated after stack frame end `
+ this.checkpointRef.getAllocations()
);

const table: Region[] = [];
Expand Down Expand Up @@ -236,7 +246,10 @@ export class StackAllocator {
}

function free(alloc: StackAllocation): void {
if (!alloc.inUse) throw new Error("Double free on stack allocation");
if (!alloc.inUse) {
console.warn("Warn: Double free on stack allocation");
return
}
if (alloc.isAlias()) return;

alloc.inUse = false;
Expand Down
97 changes: 84 additions & 13 deletions source/compiler/codegen/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { File } from "~/compiler/file.ts";

import * as banned from "~/compiler/codegen/banned.ts";
import Structure from "~/compiler/structure.ts";
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";
Expand All @@ -18,14 +19,16 @@ import { Block } from "~/wasm/instruction/control-flow.ts";

export class Context {
file: File;
function: Function;
scope: Scope;
done: boolean;

raiseType: OperandType;

block: AnyInstruction[];

constructor(file: File, scope: Scope, block: AnyInstruction[]) {
constructor(file: File, func: Function, scope: Scope, block: AnyInstruction[]) {
this.function = func;
this.raiseType = none;
this.scope = scope;
this.block = block;
Expand Down Expand Up @@ -62,7 +65,7 @@ export class Context {
}

child() {
return new Context(this.file, this.scope.child(), []);
return new Context(this.file, this.function, this.scope.child(), []);
}

cleanup() {
Expand Down Expand Up @@ -251,7 +254,8 @@ export function Assign(ctx: Context, target: LinearType, expr: OperandType, ref:
ResolveLinearType(ctx, target, ref, false);
// Source address
ResolveLinearType(ctx, expr, ref, false);
// Bytes

// Transfer
ctx.block.push(Instruction.const.i32(target.getSize()));
ctx.block.push(Instruction.copy(0, 0));

Expand All @@ -276,22 +280,89 @@ function CompileStatement(ctx: Context, syntax: Syntax.Term_Statement) {



function CompileReturn(ctx: Context, syntax: Syntax.Term_Return): typeof never {
const maybe_expr = syntax.value[1].value[0];
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 }
);

function CompileReturn(ctx: Context, syntax: Syntax.Term_Return) {
const isTail = syntax.value[0].value.length > 0;
const value = syntax.value[1];
// Guard: return none
if (ctx.function.returns.length === 0) {
if (maybe_expr) Panic(
`${colors.red("Error")}: This function should have no return value\n`,
{ path: ctx.file.path, name: ctx.file.name, ref }
);

if (isTail) Panic(`${colors.red("Error")}: Unimplemented tail call return\n`, {
path: ctx.file.path,
name: ctx.file.name,
ref: syntax.ref
});
ctx.scope.cleanup(true);
ctx.block.push(Instruction.return());
ctx.done = true;
return never;
}

CompileExpr(ctx, value);
ctx.scope.cleanup();
if (!maybe_expr) Panic(
`${colors.red("Error")}: Missing return expression\n`,
{ path: ctx.file.path, name: ctx.file.name, ref }
);

if (ctx.function.returns.length !== 1) Panic(
`${colors.red("Error")}: Multi value return is currently not supported\n`,
{ path: ctx.file.path, name: ctx.file.name, ref }
);

const goal = ctx.function.returns[0];
const expr = CompileExpr(ctx, maybe_expr, goal.type || none);
if (!IsRuntimeType(expr)) Panic(
`${colors.red("Error")}: You can only return a runtime type, not ${colors.cyan(expr.getTypeName())}\n`,
{ path: ctx.file.path, name: ctx.file.name, ref }
);

// 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 (expr instanceof LinearType) {
ResolveLinearType(ctx, expr, ref, true);
expr.dispose();
};

ctx.scope.cleanup(true);
ctx.block.push(Instruction.return());
ctx.done = true;
return never;
}

if (expr instanceof IntrinsicValue || !expr.like(goal.type)) 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 }
);

const target = ctx.scope.getVariable("return", true);
if (!target) throw new Error("Missing return variable");

// Destination address
ResolveLinearType(ctx, target.type, maybe_expr.ref, false);

// Source Address
ResolveLinearType(ctx, expr, maybe_expr.ref, true);

// Transfer
ctx.block.push(Instruction.const.i32(goal.type.size));
ctx.block.push(Instruction.copy(0, 0));

expr.dispose();

ctx.scope.cleanup(true);
ctx.block.push(Instruction.return());
ctx.done = true;

return never;
}

function CompileRaise(ctx: Context, syntax: Syntax.Term_Raise) {
Expand Down
3 changes: 2 additions & 1 deletion source/compiler/codegen/expression/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export function ResolveLinearType(ctx: Context, type: LinearType, ref: Reference
// Auto load intrinsic value from a linear type
if (baseType instanceof IntrinsicType) {
Load(ctx, baseType, type.offset);
return;
return baseType.value;
}

// Push the complete pointer to the stack
Expand All @@ -89,4 +89,5 @@ export function ResolveLinearType(ctx: Context, type: LinearType, ref: Reference
}

if (type.offset !== 0) ctx.block.push(Instruction.const.i32(type.offset));
return type;
}
17 changes: 7 additions & 10 deletions source/compiler/codegen/expression/infix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ export function CompileInfix(ctx: Context, lhs: PrecedenceTree, op: string, rhs:
if (op === "as") return CompileAs(ctx, lhs, rhs);
if (op === ".") return CompileStaticAccess(ctx, lhs, rhs, expect);

const a = CompilePrecedence(ctx, lhs, expect);
let a = CompilePrecedence(ctx, lhs, expect);
if (a instanceof LinearType && a.type instanceof IntrinsicValue) a = ResolveLinearType(ctx, a, rhs.ref);

if (!(a instanceof IntrinsicValue)) Panic(
`${colors.red("Error")}: Cannot apply arithmetic infix operation to non-intrinsics value\n`, {
`${colors.red("Error")}: Cannot apply arithmetic infix operation to non-intrinsics ${colors.cyan(a.getTypeName())}\n`, {
path: ctx.file.path, name: ctx.file.name, ref: lhs.ref
});

const b = CompilePrecedence(ctx, rhs, a.type);
let b = CompilePrecedence(ctx, rhs, a.type);
if (b instanceof LinearType && b.type instanceof IntrinsicValue) b = ResolveLinearType(ctx, b, rhs.ref);
if (!(b instanceof IntrinsicValue)) Panic(
`${colors.red("Error")}: Cannot apply arithmetic infix operation to non-intrinsics value\n`, {
`${colors.red("Error")}: Cannot apply arithmetic infix operation to non-intrinsics ${colors.cyan(b.getTypeName())}\n`, {
path: ctx.file.path, name: ctx.file.name, ref: rhs.ref
});

Expand Down Expand Up @@ -118,12 +121,6 @@ function CompileStaticAccess(ctx: Context, lhs: PrecedenceTree, rhs: PrecedenceT
path: ctx.file.path, name: ctx.file.name, ref: rhs.ref
});


if (attr.type instanceof IntrinsicValue) {
ResolveLinearType(ctx, attr, rhs.ref);
return attr.type;
}

return attr;
}

Expand Down
10 changes: 4 additions & 6 deletions source/compiler/codegen/expression/operand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,6 @@ function CompileName(ctx: Context, syntax: Syntax.Term_Name) {
return found;
}

const linear = variable.type;
if (linear.type instanceof IntrinsicValue) {
ResolveLinearType(ctx, linear, syntax.ref);
return linear.type;
}

return variable.type;
}

Expand Down Expand Up @@ -108,6 +102,8 @@ function CompileIf(ctx: Context, syntax: Syntax.Term_If, expect?: SolidType) {
`${colors.red("Error")}: Type miss-match between if statement results, ${typeIf.getTypeName()} != ${typeElse.getTypeName()}\n`,
{ path: ctx.file.path, name: ctx.file.name, ref: syntax.ref }
);

if (scopeIf.done && scopeElse.done) ctx.done = true;
}

let typeIdx = 0x40;
Expand All @@ -127,6 +123,8 @@ function CompileBlock(ctx: Context, syntax: Syntax.Term_Block, expect?: SolidTyp
child.compile(syntax.value[0].value);
child.cleanup();

if (child.done) ctx.done = true;

ctx.block.push(Instruction.block(0x40, child.block));
return child.raiseType;
}
23 changes: 14 additions & 9 deletions source/compiler/codegen/expression/postfix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import Function from "~/compiler/function.ts";
import { AssertUnreachable, Panic } from "~/helper.ts";
import { IntrinsicType, i32 } from "~/compiler/intrinsic.ts";
import { ResolveLinearType } from "~/compiler/codegen/expression/helper.ts";
import { IntrinsicValue } from "~/compiler/intrinsic.ts";
import { OperandType } from "~/compiler/codegen/expression/type.ts";
import { CompileExpr } from "~/compiler/codegen/expression/index.ts";
import { IsNamespace } from "~/compiler/file.ts";
import { Instruction } from "~/wasm/index.ts";
import { VirtualType } from "~/compiler/intrinsic.ts";
import { LinearType } from "~/compiler/codegen/expression/type.ts";
import { Context } from "~/compiler/codegen/context.ts";
import { none } from "~/compiler/intrinsic.ts";
Expand Down Expand Up @@ -47,16 +49,18 @@ function CompileCall(ctx: Context, syntax: Syntax.Term_Expr_call, operand: Opera
if (!operand.ref) throw new Error("A function somehow compiled with no reference generated");

const stackReg = ctx.file.owner.project.stackReg.ref;
let returnType;
if (operand.returns.length == 0) {
returnType = none;
} else if (operand.returns.length == 1) {
let returnType: VirtualType | IntrinsicValue | LinearType = none;

if (operand.returns.length == 1) {
const primary = operand.returns[0];
if (primary instanceof IntrinsicType) {
returnType = primary.value;
if (primary.type instanceof IntrinsicType) {
returnType = primary.type.value;
} else {
const alloc = ctx.scope.stack.allocate(primary.size, primary.align);
returnType = LinearType.make(primary, alloc, ctx.file.owner.project.stackBase);
const alloc = ctx.scope.stack.allocate(primary.type.size, primary.type.align);
const forward = primary.type instanceof IntrinsicType
? primary.type.value
: primary.type;
returnType = LinearType.make(forward, alloc, ctx.file.owner.project.stackBase);

ctx.block.push(Instruction.global.get(stackReg));
ctx.block.push(Instruction.const.i32(alloc.getOffset()));
Expand All @@ -74,14 +78,15 @@ function CompileCall(ctx: Context, syntax: Syntax.Term_Expr_call, operand: Opera

for (let i=0; i<args.length; i++) {
const signature = operand.arguments[i];

const res = CompileExpr(ctx, args[i], signature.type);
if (IsNamespace(res)) Panic(
`${colors.red("Error")}: Cannot use a namespace as a runtime argument\n`,
{ path: ctx.file.path, name: ctx.file.name, ref: args[i].ref }
);

if (!res.like(signature.type)) Panic(
`${colors.red("Error")}: Call argument type miss-match, expected ${signature.type.name} got ${res.getTypeName()}\n`,
`${colors.red("Error")}: Call argument type miss-match, expected ${colors.cyan(signature.type.name)} got ${colors.cyan(res.getTypeName())}\n`,
{ path: ctx.file.path, name: ctx.file.name, ref: args[i].ref }
);

Expand Down
Loading

0 comments on commit 031f5c9

Please sign in to comment.