Skip to content

Try-catch experiment #963

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 130 additions & 12 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ export class Compiler extends DiagnosticEmitter {
/** Expressions known to have skipped an autorelease. Usually function returns. */
skippedAutoreleases: Set<ExpressionRef> = new Set();
/** Registered event types. */
events: Map<string, EventRef> = new Map();
events: Map<string,EventRef> = new Map();

/** Compiles a {@link Program} to a {@link Module} using the specified options. */
static compile(program: Program, options: Options | null = null): Module {
Expand Down Expand Up @@ -2460,9 +2460,15 @@ export class Compiler extends DiagnosticEmitter {
return currentBlock;
}

makeThrow(expr: ExpressionRef): ExpressionRef {
this.ensureEventType("exception", [ this.options.usizeType ]);
return this.module.throw("exception", [ expr ]);
}

compileThrowStatement(
statement: ThrowStatement
): ExpressionRef {
var module = this.module;
var flow = this.currentFlow;

// Remember that this branch throws
Expand All @@ -2471,28 +2477,140 @@ export class Compiler extends DiagnosticEmitter {
var stmts = new Array<ExpressionRef>();
this.finishAutoreleases(flow, stmts);

// TODO: requires exception-handling spec.
var value = statement.value;
var message: Expression | null = null;
if (value.kind == NodeKind.NEW) {
let newArgs = (<NewExpression>value).arguments;
if (newArgs.length) message = newArgs[0]; // FIXME: naively assumes type string
if (this.options.hasFeature(Feature.EXCEPTION_HANDLING)) {
let thrown = this.compileExpression(statement.value, Type.auto, Constraints.WILL_RETAIN | Constraints.MUST_WRAP);
let thrownType = this.currentType;
if (!(
thrownType.isManaged &&
assert(thrownType.classReference).extends(this.program.errorInstance.prototype))
) {
this.error(
DiagnosticCode.Operation_0_cannot_be_applied_to_type_1,
statement.value.range, "throw", thrownType.toString()
);
stmts.push(module.unreachable());
} else {
let expr = this.makeThrow(thrown);
if (!this.skippedAutoreleases.has(thrown)) expr = this.makeRetain(expr);
stmts.push(expr);
}
} else {
let value = statement.value;
let message: Expression | null = null;
if (value.kind == NodeKind.NEW) {
let newArgs = (<NewExpression>value).arguments;
if (newArgs.length) message = newArgs[0]; // FIXME: naively assumes type string
}
stmts.push(compileAbort(this, message, statement));
}
stmts.push(compileAbort(this, message, statement));

return flatten(this.module, stmts, NativeType.None);
this.currentType = Type.void;
return flatten(module, stmts, NativeType.None);
}

compileTryStatement(
statement: TryStatement
): ExpressionRef {
// TODO: can't yet support something like: try { return ... } finally { ... }
// worthwhile to investigate lowering returns to block results (here)?
var module = this.module;

if (this.options.hasFeature(Feature.EXCEPTION_HANDLING)) {

// TODO: finally has various edge cases, for example when returning from
// a finally block or even executing when throwing/returning in try/catch
let finallyStatements = statement.finallyStatements;
if (finallyStatements) {
this.error(
DiagnosticCode.Not_implemented,
statement.range
);
return module.unreachable();
}

let errorType = this.program.errorInstance.type;
let outerFlow = this.currentFlow;

let tryFlow = outerFlow.fork();
this.currentFlow = tryFlow;
let tryStmts = this.compileStatements(statement.statements);
if (!tryFlow.is(FlowFlags.TERMINATES)) {
this.performAutoreleases(tryFlow, tryStmts);
}
tryFlow.freeScopedLocals();

let catchFlow = outerFlow.fork();
this.currentFlow = catchFlow;
let breakLabel = "catch|" + outerFlow.pushBreakLabel();
let exnTemp = catchFlow.getTempLocal(Type.exnref);
let catchStmts = new Array<ExpressionRef>();
catchStmts.push(
module.local_set(exnTemp.index,
module.pop(NativeType.Exnref)
)
);
let caughtExpr = module.block(breakLabel, [
module.rethrow(
module.br_on_exn(breakLabel, "exception",
module.local_get(exnTemp.index, NativeType.Exnref)
)
),
], errorType.toNativeType());
let catchStatements = statement.catchStatements;
if (catchStatements) {
let catchVariable = statement.catchVariable;
if (catchVariable) {
let temp = catchFlow.addScopedLocal(catchVariable.text, errorType);
catchStmts.push(
module.local_set(temp.index, caughtExpr)
);
catchFlow.setLocalFlag(temp.index, LocalFlags.RETAINED);
} else {
catchStmts.push(
module.drop(caughtExpr)
);
}
this.compileStatements(catchStatements, false, catchStmts);
if (!catchFlow.is(FlowFlags.TERMINATES)) {
this.performAutoreleases(catchFlow, catchStmts);
}
catchFlow.freeScopedLocals();
} else {
catchStmts.push(
module.drop(caughtExpr)
);
}
catchFlow.freeTempLocal(exnTemp);
outerFlow.inheritMutual(tryFlow, catchFlow);
// TODO: breaks?

// if (!outerFlow.is(FlowFlags.TERMINATES)) {
// let finallyStatements = statement.finallyStatements;
// if (finallyStatements) {
// let finallyFlow = outerFlow.fork();
// this.currentFlow = finallyFlow;
// this.compileStatements(finallyStatements, false, WRONGcatchStmts);
// if (!finallyFlow.is(FlowFlags.TERMINATES)) {
// this.performAutoreleases(finallyFlow, WRONGcatchStmts);
// }
// finallyFlow.freeScopedLocals();
// outerFlow.inherit(finallyFlow);
// }
// }

this.currentFlow = outerFlow;
let ret = module.try(
flatten(module, tryStmts, NativeType.None),
flatten(module, catchStmts, NativeType.None)
);
outerFlow.popBreakLabel();
this.currentType = Type.void;
return ret;
}

this.error(
DiagnosticCode.Not_implemented,
statement.range
);
return this.module.unreachable();
return module.unreachable();
}

/** Compiles a variable statement. Returns `0` if an initializer is not necessary. */
Expand Down
6 changes: 5 additions & 1 deletion src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,8 @@ export class Program extends DiagnosticEmitter {
f64ArrayPrototype: ClassPrototype;
/** String instance reference. */
stringInstance: Class;
/** Error instance reference. */
errorInstance: Class;
/** Abort function reference, if not explicitly disabled. */
abortInstance: Function | null;

Expand Down Expand Up @@ -856,14 +858,16 @@ export class Program extends DiagnosticEmitter {
}
}

// register ArrayBuffer (id=0), String (id=1), ArrayBufferView (id=2)
// register ArrayBuffer (id=0), String (id=1), ArrayBufferView (id=2), Error (id=3)
assert(this.nextClassId == 0);
this.arrayBufferInstance = this.requireClass(CommonSymbols.ArrayBuffer);
assert(this.arrayBufferInstance.id == 0);
this.stringInstance = this.requireClass(CommonSymbols.String);
assert(this.stringInstance.id == 1);
this.arrayBufferViewInstance = this.requireClass(CommonSymbols.ArrayBufferView);
assert(this.arrayBufferViewInstance.id == 2);
this.errorInstance = this.requireClass(CommonSymbols.Error);
assert(this.errorInstance.id == 3);

// register classes backing basic types
this.registerWrapperClass(Type.i8, CommonSymbols.I8);
Expand Down
39 changes: 39 additions & 0 deletions tests/binaryen/try-catch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
var binaryen = require("binaryen");
var mod = binaryen.parseText(`
(module
(event $exception (attr 0) (param i32))
(func $test
(local $0 exnref)
(try
(throw $exception
(i32.const 1)
)
(catch
(local.set $0
(exnref.pop)
)
(drop
(block $catch|0 (result i32)
(rethrow
(br_on_exn $catch|0 $exception
(local.get $0)
)
)
)
)
)
)
)
(export $test $test)
)
`);
mod.setFeatures(binaryen.Features.ExceptionHandling);
if (!mod.validate()) console.log(":-(");
else console.log(mod.emitText());

binaryen.setOptimizeLevel(3); // assertion hit if > 2, different one if == 4
binaryen.setShrinkLevel(0);
mod.optimize();

if (!mod.validate()) console.log(":-(");
else console.log(mod.emitText());
6 changes: 3 additions & 3 deletions tests/compiler/builtins.optimized.wat
Original file line number Diff line number Diff line change
Expand Up @@ -628,9 +628,9 @@
i32.const 5
f64.const 0
f64.const 0
f64.const 12
f64.const 23
f64.const 23
f64.const 13
f64.const 24
f64.const 24
call $~lib/builtins/trace
i32.const 176
i32.const 176
Expand Down
8 changes: 4 additions & 4 deletions tests/compiler/builtins.untouched.wat
Original file line number Diff line number Diff line change
Expand Up @@ -1568,11 +1568,11 @@
local.set $0
i32.const 0
local.set $1
i32.const 12
i32.const 13
local.set $6
i32.const 23
i32.const 24
local.set $7
i32.const 23
i32.const 24
local.set $8
i32.const 104
i32.const 5
Expand Down Expand Up @@ -1612,7 +1612,7 @@
unreachable
end
local.get $6
i32.const 12
i32.const 13
i32.eq
i32.eqz
if
Expand Down
20 changes: 10 additions & 10 deletions tests/compiler/call-super.optimized.wat
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
i32.eqz
if
i32.const 4
i32.const 3
i32.const 4
call $~lib/rt/stub/__alloc
local.set $0
end
Expand All @@ -128,7 +128,7 @@
(func $call-super/B#constructor (; 4 ;) (type $FUNCSIG$i) (result i32)
(local $0 i32)
i32.const 8
i32.const 4
i32.const 5
call $~lib/rt/stub/__alloc
call $call-super/A#constructor
local.tee $0
Expand Down Expand Up @@ -191,13 +191,13 @@
(func $call-super/D#constructor (; 6 ;) (type $FUNCSIG$i) (result i32)
(local $0 i32)
i32.const 8
i32.const 6
i32.const 7
call $~lib/rt/stub/__alloc
local.tee $0
i32.eqz
if
i32.const 4
i32.const 5
i32.const 6
call $~lib/rt/stub/__alloc
local.set $0
end
Expand Down Expand Up @@ -266,7 +266,7 @@
i32.eqz
if
i32.const 4
i32.const 7
i32.const 8
call $~lib/rt/stub/__alloc
local.set $0
end
Expand All @@ -290,7 +290,7 @@
(func $call-super/test3 (; 9 ;) (type $FUNCSIG$v)
(local $0 i32)
i32.const 8
i32.const 8
i32.const 9
call $~lib/rt/stub/__alloc
call $call-super/E#constructor
local.tee $0
Expand Down Expand Up @@ -324,13 +324,13 @@
(func $call-super/H#constructor (; 10 ;) (type $FUNCSIG$i) (result i32)
(local $0 i32)
i32.const 8
i32.const 10
i32.const 11
call $~lib/rt/stub/__alloc
local.tee $0
i32.eqz
if
i32.const 4
i32.const 9
i32.const 10
call $~lib/rt/stub/__alloc
local.set $0
end
Expand Down Expand Up @@ -373,13 +373,13 @@
(func $call-super/J#constructor (; 12 ;) (type $FUNCSIG$i) (result i32)
(local $0 i32)
i32.const 8
i32.const 12
i32.const 13
call $~lib/rt/stub/__alloc
local.tee $0
i32.eqz
if
i32.const 4
i32.const 11
i32.const 12
call $~lib/rt/stub/__alloc
local.set $0
end
Expand Down
Loading