Skip to content

Commit

Permalink
[js] Turn nqp::throwpayloadlex into an direct return when possible.
Browse files Browse the repository at this point in the history
  • Loading branch information
pmurias committed Jan 11, 2017
1 parent 10fa808 commit 1b9f96a
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 98 deletions.
104 changes: 30 additions & 74 deletions src/vm/js/Operations.nqp
Expand Up @@ -1390,108 +1390,64 @@ class QAST::OperationsJS {
add_simple_op('findnotcclass', $T_INT, [$T_INT, $T_STR, $T_INT, $T_INT]);
add_simple_op('iscclass', $T_INT, [$T_INT, $T_STR, $T_INT]);

# Simple payload handler.
# We assume if handlepayload is present is contains the whole meat of the function
add_op('handlepayload', :!inlinable, sub ($comp, $node, :$want, :$cps) {
if +$node.list != 3 {
nqp::die("The 'handlepayload' op requires three children");
}
my str $type := $node[1];

my $unwind_marker := $*BLOCK.add_tmp;

my $handle_result := $comp.as_js(:want($T_OBJ), $node[2]);
my str $result := $*BLOCK.add_tmp;
if $type ne 'RETURN' {
$comp.NYI("handlepayload with type '$type'");
}

my $*RETURN;

my @toplevels := $*BLOCK.qast.list;
my $*RETURN := ReturnInfo.new(block => $*BLOCK);

my $is_last_toplevel := +@toplevels && nqp::eqaddr(@toplevels[+@toplevels - 1], $node);
my $protected := $comp.as_js($node[0], :$want);

my $just_return_payload := $node[2] ~~ QAST::Op && $node[2].op eq 'lastexpayload';

if $is_last_toplevel && $just_return_payload {
$*RETURN := TopLevelInBlock.new(block => $*BLOCK);
}
else {
$*RETURN := NotTopLevel.new();
}

my $protected := $comp.as_js(:want($T_OBJ), $node[0]);

# HACK - we assume how return is used on the nqp backend and avoid handling some exceptions
if $*HLL ne 'nqp' || $*RETURN.is_used {
my $handle_result := $comp.as_js(:want($T_OBJ), $node[2]);

Chunk.new($T_OBJ, $result, [
"try \{\n",
$protected,
"$result = {$protected.expr};\n",
"\} catch (\$\$e) \{\n",
"if (\$\$e instanceof nqp.ControlReturn) \{\n",
$handle_result,
"$result = {$handle_result.expr};\n",
"\} else \{",
"throw \$\$e;\n",
"\}",
"\}"
]);
}
else {
$protected;
}

Chunk.new($T_OBJ, $result, [
"$unwind_marker = \{\};\n",
"$*CTX.unwind = $unwind_marker;\n",
"$*CTX.\$\${$type} = function() \{\n",
$handle_result,
"$result = {$handle_result.expr};\n",
"\};\n",
"try \{",
$protected,
"$result = {$protected.expr};\n",
"\} catch (e) \{if (e === $unwind_marker) \{\} else \{throw e\}\}",
]);
});

my sub is_CONTROL_RETURN($category) {
$category ~~ QAST::IVal && $category.value == nqp::const::CONTROL_RETURN
|| $category ~~ QAST::Op && $category.op eq 'const' && $category.name eq 'CONTROL_RETURN';
}

add_op('throwpayloadlex', :!inlinable, sub ($comp, $node, :$want, :$cps) {
my $category := $node[0];
my $payload := $comp.as_js(:want($T_OBJ), $node[1]);
add_simple_op('throwpayloadlexcaller', $T_VOID, [$T_INT, $T_OBJ], :side_effects, :!inlinable,
sub ($category, $payload) {"$*CTX.throwpayloadlexcaller($category, $payload)"});

unless is_CONTROL_RETURN($category) {
return $comp.NYI("throwpayloadlex with something else then a CONTROL_RETURN literal");
}

if $*RETURN.as_return {
return Chunk.new($T_VOID, "", [
$payload,
"return {$payload.expr};\n"
]);
}
else {
$*RETURN.mark_used();
}

Chunk.new($T_VOID, "", [
$payload,
"throw new nqp.ControlReturn({$payload.expr});\n"
]);
});

add_op('throwpayloadlexcaller', :!inlinable, sub ($comp, $node, :$want, :$cps) {
my $category := $node[0];
add_op('throwpayloadlex', :!inlinable, sub ($comp, $node, :$want, :$cps) {
my $payload := $comp.as_js(:want($T_OBJ), $node[1]);

unless is_CONTROL_RETURN($category) {
return $comp.NYI("throwpayloadlexcaller with something else then a CONTROL_RETURN literal");
if is_CONTROL_RETURN($node[0]) {
if $*RETURN.as_return {
return Chunk.void($payload, "return {$payload.expr};\n");
}
else {
$*RETURN.mark_used;
}
}

$*BLOCK.use_passing_on_of_exceptions();

Chunk.new($T_VOID, "", [
$payload,
"throw new nqp.PassExceptionToCaller(new nqp.ControlReturn({$payload.expr}));\n"
]);
my $category := $comp.as_js(:want($T_INT), $node[0]);
Chunk.void($category, $payload, "$*CTX.throwpayloadlex({$category.expr}, {$payload.expr});\n");
});

add_simple_op('throwextype', $T_VOID, [$T_INT], :side_effects, :ctx);

add_simple_op('lastexpayload', $T_OBJ, [], sub () { '$$e.payload' }, :!inlinable, :side_effects);
add_simple_op('lastexpayload', $T_OBJ, [], :!inlinable);


add_op('control', sub ($comp, $node, :$want, :$cps) {
Expand Down
10 changes: 1 addition & 9 deletions src/vm/js/ReturnInfo.nqp
@@ -1,22 +1,14 @@
my class ReturnInfo {
has $!block;
has int $!used;
method mark_used() {
$!used := 1;
}
method is_used() {
$!used;
}
}

my class TopLevelInBlock is ReturnInfo {
has $!block;
method as_return() {
return nqp::eqaddr($!block, $*BLOCK);
}
}

my class NotTopLevel is ReturnInfo {
method as_return() {
return 0;
}
}
2 changes: 2 additions & 0 deletions src/vm/js/nqp-runtime/bootstrap.js
Expand Up @@ -244,3 +244,5 @@ module.exports.core = core;
BOOT.Array = bootType('BOOTArray', 'VMArray');
BOOT.Array._STable.setboolspec(8, Null);
BOOT.Array._STable.hllRole = 4;

BOOT.Exception = bootType('BOOTException', 'VMException');
13 changes: 8 additions & 5 deletions src/vm/js/nqp-runtime/core.js
Expand Up @@ -601,11 +601,11 @@ op.setmessage = function(exception, message) {
};

op.getpayload = function(exception) {
return Object.prototype.hasOwnProperty.call(exception, 'payload') ? exception.payload : Null;
return Object.prototype.hasOwnProperty.call(exception, '$$payload') ? exception.$$payload : Null;
};

op.setpayload = function(exception, payload) {
return (exception.payload = payload);
return (exception.$$payload = payload);
};

op.isnum = function(value) {
Expand Down Expand Up @@ -952,15 +952,14 @@ op.setdimensions = function(array, dimensions) {
};
});

var BOOTException = bootstrap.bootType('BOOTException', 'VMException');
/* TODO HLL support */
op.newexception = function() {
var exType = BOOTException;
var exType = BOOT.Exception;
return exType._STable.REPR.allocate(exType._STable);
};

op.throwextype = function(ctx, category) {
var exType = BOOTException;
var exType = BOOT.Exception;
let ex = exType._STable.REPR.allocate(exType._STable);
ex.$$category = category;
ctx.throw(ex);
Expand Down Expand Up @@ -1091,6 +1090,10 @@ op.exception = function() {
return exceptionsStack[exceptionsStack.length - 1];
};

op.lastexpayload = function() {
return exceptionsStack[exceptionsStack.length - 1].$$payload;
};

op.setextype = function(exception, category) {
exception.$$category = category;
return exception;
Expand Down
50 changes: 50 additions & 0 deletions src/vm/js/nqp-runtime/ctx.js
Expand Up @@ -6,6 +6,8 @@ var NQPObject = require('./nqp-object.js');
var Null = require('./null.js');
var exceptionsStack = require('./exceptions-stack.js');

var BOOT = require('./BOOT.js');

var categoryToName = {
4: 'NEXT',
8: 'REDO',
Expand Down Expand Up @@ -123,6 +125,54 @@ class Ctx extends NQPObject {
this.propagateException(exception);
}

throwpayloadlexcaller(category, payload) {
let ctx = this.$$caller;

let isThunkOrCompilerStub = code => {code.isThunk || code.isCompilerStub};
while (ctx && isThunkOrCompilerStub(ctx.codeRef().staticCode)) {
ctx = ctx.$$caller;
}

ctx.throwpayloadlex(category, payload);
}

throwpayloadlex(category, payload) {
let exType = BOOT.Exception;
let exception = exType._STable.REPR.allocate(exType._STable);
exception.$$category = category;
exception.$$payload = payload;
let handler = '$$' + categoryToName[category];

let ctx = this;


while (ctx) {
if (ctx[handler] || ctx.$$CONTROL) {
exception.caught = ctx;
exception.resume = false;
ctx.exception = exception;

exceptionsStack.push(exception);
try {
if (ctx[handler]) {
ctx.unwind.ret = ctx[handler]();
} else {
ctx.unwind.ret = ctx.$$CONTROL();
}
} finally {
exceptionsStack.pop();
}

if (exception.resume) {
return;
} else {
throw ctx.unwind;
}
}
ctx = ctx.$$outer;
}
}

lookupDynamic(name) {
var ctx = this;
while (ctx) {
Expand Down
10 changes: 0 additions & 10 deletions src/vm/js/nqp-runtime/runtime.js
Expand Up @@ -351,16 +351,6 @@ exports.wrapException = function(e) {
return new NQPException(e.message);
};

function ControlReturn(payload) {
this.payload = payload;
}
exports.ControlReturn = ControlReturn;

function PassExceptionToCaller(exception) {
this.exception = exception;
}
exports.PassExceptionToCaller = PassExceptionToCaller;

exports.setCodeRefHLL = function(codeRefs, hllName) {
for (var i = 0; i < codeRefs.length; i++) {
codeRefs[i].hll = hll.hllConfigs[hllName];
Expand Down

0 comments on commit 1b9f96a

Please sign in to comment.