Skip to content

Commit

Permalink
[js] Refactor redo/last/next handling
Browse files Browse the repository at this point in the history
Use high level exceptions for them instead of Redo/Last/Next js level
ones so that they can be caught by CONTROL.
Also implement redo support in for.
  • Loading branch information
pmurias committed Apr 9, 2017
1 parent 50818fd commit 7eda701
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 61 deletions.
46 changes: 31 additions & 15 deletions src/vm/js/Compiler.nqp
Expand Up @@ -588,35 +588,51 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
$chunk;
}

method handle_control($loop, $body) {
my %control_id;
%control_id<last> := 1;
%control_id<redo> := 2;
%control_id<next> := 3;

method handle_control($loop, $ctx, $body) {
if nqp::elems($loop.handled) > 0 {
my $setup_label := "";
my str $check_label := "e.label === null";
my @setup;

if $loop.label {
$setup_label := self.as_js($loop.label, :want($T_OBJ));
$check_label := $check_label ~ ' || e.label === ' ~ $setup_label.expr;
my $label := self.as_js($loop.label, :want($T_OBJ));
@setup.push($label);
@setup.push("$ctx.\$\$label = {$label.expr};\n");
}


my @handle_exceptions;

my str $action := $*BLOCK.add_tmp;

for $loop.handled -> $type {
@handle_exceptions.push("if (e instanceof nqp.{ucfirst($type)} && ($check_label)) \{ {self.do_control($type, $loop) } \}\n");
my int $id := %control_id{$type};
@setup.push("$ctx.\$\${nqp::uc($type)} = function() \{$action = $id\};\n");
@handle_exceptions.push("if ($action === $id) \{ {self.do_control($type, $loop) } \}\n");
}

my str $unwind_marker := $*BLOCK.add_tmp;
Chunk.new($body.type, $body.expr, [
$setup_label,
"$action = 0;\n",
"try \{\n",
"$ctx = new nqp.Ctx($*CTX, $*CTX, $*CTX.\$\$callThis);\n",
Chunk.void(|@setup),
"$unwind_marker = \{\};\n",
"$ctx.unwind = $unwind_marker;\n",
$body,
"\} catch (e) \{\n",
"if (e === $unwind_marker) \{\n",
Chunk.void(|@handle_exceptions),
"\}\n",
"throw (e);",
"\}\n"
]);
}
else {
$body;
Chunk.new($body.type, $body.expr, ["$ctx = $*CTX;", $body]);
}
}

Expand Down Expand Up @@ -1137,8 +1153,7 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {

my @args := [$outer_ctx, 'null'];
for @extra_args -> $arg {
@args.push($arg.expr);
$setup.push($arg);
@args.push($arg);
}

self.stored_result(Chunk.new($want, $cloned_block~".\$\$call({nqp::join(',', @args)})", $setup, :$node), :$want);
Expand Down Expand Up @@ -1306,14 +1321,15 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
}

method do_control($type, $loop) {
my str $label := " {$loop.js_label}";
if $type eq 'last' {
"break;\n";
"break$label;\n";
}
elsif $type eq 'next' {
"continue;\n";
"continue$label;\n";
}
elsif $type eq 'redo' {
"{$loop.redo} = 1;\n;continue;\n";
"{$loop.redo} = true;\n;continue{$loop.redo_label ?? ' ' ~ $loop.redo_label !! $label};\n";
}
}

Expand Down Expand Up @@ -1345,7 +1361,7 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
}
else {
$loop.handle($type);
return Chunk.void("throw new nqp.{ucfirst($type)}(null);\n");
return Chunk.void("$*CTX.$type();\n");
}
}
}
Expand All @@ -1357,7 +1373,7 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {

if $label {
my $compiled_label := self.as_js($label, :want($T_OBJ));
Chunk.void($compiled_label, "throw new nqp.{ucfirst($type)}({$compiled_label.expr});\n");
Chunk.void($compiled_label, "$*CTX.{$type}Labeled({$compiled_label.expr});\n");
}
else {
self.NYI("can't find surrounding loop for $type");
Expand Down
14 changes: 13 additions & 1 deletion src/vm/js/LoopInfo.nqp
Expand Up @@ -5,6 +5,8 @@ my class LoopInfo {
has $!redo;

has $!label;
has str $!js_label;
has str $!redo_label;

has %!handled;

Expand All @@ -15,21 +17,31 @@ my class LoopInfo {
$!redo;
}
method has_redo() {
nqp::defined($!redo);
nqp::existskey(%!handled, 'redo') || nqp::defined($!redo);
}

method new($outer, :$label) {
my $obj := nqp::create(self);
$obj.BUILD($outer, $label);
$obj
}

method BUILD($outer, $label) {
$!outer := $outer;
$!label := $label;
%!handled := nqp::hash();
$!js_label := QAST::Node.unique('loop_label');
}

method outer() { $!outer }

method js_label() { $!js_label }

method redo_label($value = NO_VALUE) {
$!redo_label := $value unless $value =:= NO_VALUE;
$!redo_label;
}

# do we have to catch the control exception?

method handled() {
Expand Down
56 changes: 36 additions & 20 deletions src/vm/js/Operations.nqp
Expand Up @@ -1075,7 +1075,7 @@ class QAST::OperationsJS {
if needs_cond_passed($node) {
my $block := try $*BLOCK;
my $loop := try $*LOOP;
$comp.compile_block($node, $block, $loop, :$want, :extra_args([$cond_without_sideeffects]));
$comp.compile_block($node, $block, $loop, :$want, :extra_args([$cond.expr]));
}
else {
$comp.as_js($node, :$want);
Expand Down Expand Up @@ -1166,7 +1166,6 @@ class QAST::OperationsJS {
});

add_op('for', sub ($comp, $node, :$want) {
# TODO redo etc.

my int $handler := 1;
my @operands;
Expand Down Expand Up @@ -1195,10 +1194,12 @@ class QAST::OperationsJS {
# TODO think if creating the block once, and the calling it multiple times would be faster

my @body_args;
my @setup;
my $arity := @operands[1].arity || 1;
while $arity > 0 {
my str $iterval := $*BLOCK.add_tmp();
@body_args.push(Chunk.new($T_OBJ, $iterval, ["$iterval = $iterator.\$\$shift();\n"]));
@setup.push("$iterval = $iterator.\$\$shift();\n");
@body_args.push($iterval);
$arity := $arity - 1;
}

Expand All @@ -1207,13 +1208,26 @@ class QAST::OperationsJS {

my $loop := LoopInfo.new($outer_loop, :$label);

my $body := $comp.compile_block(@operands[1], $outer, $loop , :want($T_VOID), :extra_args(@body_args));
my str $control_ctx := $*BLOCK.add_tmp();

my $body;
{
my $*CTX := $control_ctx;
$body := $comp.compile_block(@operands[1], $outer, $loop , :want($T_VOID), :extra_args(@body_args));
}

if $loop.has_redo {
$loop.redo_label(QAST::Node.unique('redo_label'));
}

Chunk.new($T_OBJ, 'null', [
$list,
"$iterator = {$list.expr}.\$\$iterator();\n",
"while ($iterator.\$\$idx < $iterator.\$\$target) \{\n",
$comp.handle_control($loop, $body),
"{$loop.js_label}: while ($iterator.\$\$idx < $iterator.\$\$target) \{\n",
Chunk.void(|@setup),
($loop.has_redo ?? "{$loop.redo_label}: do \{{$loop.redo} = false;\n" !! ''),
$comp.handle_control($loop, $control_ctx, $body),
($loop.has_redo ?? "\} while ({$loop.redo});\n" !! ''),
"\}\n"
], :node($node));

Expand All @@ -1237,19 +1251,21 @@ class QAST::OperationsJS {

my int $cond_type := needs_cond_passed($node[1]) ?? $T_OBJ !! $T_BOOL;

my $control_ctx := $*BLOCK.add_tmp;

my $check_cond;
my $body;
{
my $*LOOP := $loop;
my $*CTX := $control_ctx;

my $cond := $comp.as_js(@operands[0], :want($cond_type));
$check_cond := $comp.coerce($cond, $T_BOOL);
my $cond_without_sideeffects := Chunk.new($cond.type, $cond.expr);

if needs_cond_passed(@operands[1]) {
my $block := try $*BLOCK;
my $loop := try $*LOOP;
$body := $comp.compile_block(@operands[1], $block, $loop, :want($T_VOID), :extra_args([$cond_without_sideeffects]));
$body := $comp.compile_block(@operands[1], $block, $loop, :want($T_VOID), :extra_args([$cond.expr]));
}
else {
$body := $comp.as_js(@operands[1], :want($T_VOID));
Expand All @@ -1266,19 +1282,19 @@ class QAST::OperationsJS {
if $op eq 'while' || $op eq 'until' {
my str $neg := $op eq 'while' ?? '!' !! '';

# handle_control can set redo so we call it here
my $handled := $comp.handle_control($loop, $body);
Chunk.void(
"for (;;", $post, ") \{\n",
($loop.has_redo
?? "if ({$loop.redo}) \{;\n"
~ "{$loop.redo} = false;\n"
~ "\} else \{\n"
!! ''),
$check_cond,
"if ($neg {$check_cond.expr}) \{break;\}\n",
($loop.has_redo ?? "\}\n" !! ''),
$handled,
"{$loop.js_label}: for (;;", $post, ") \{\n",
$comp.handle_control($loop, $control_ctx, Chunk.void(
($loop.has_redo
?? "if ({$loop.redo}) \{;\n"
~ "{$loop.redo} = false;\n"
~ "\} else \{\n"
!! ''),
$check_cond,
"if ($neg {$check_cond.expr}) \{break;\}\n",
($loop.has_redo ?? "\}\n" !! ''),
$body,
)),
"\}"
);
}
Expand Down
87 changes: 72 additions & 15 deletions src/vm/js/nqp-runtime/ctx.js
Expand Up @@ -9,21 +9,38 @@ var exceptionsStack = require('./exceptions-stack.js');

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

var categoryToName = {
4: 'NEXT',
8: 'REDO',
16: 'LAST',
32: 'RETURN',
128: 'TAKE',
256: 'WARN',
512: 'SUCCEED',
1024: 'PROCEED',
4096: 'LABELED',
8192: 'AWAIT',
16384: 'EMIT',
32768: 'DONE'
const NEXT = 4;
const REDO = 8;
const LAST = 16;
const RETURN = 32;
const TAKE = 128;
const WARN = 256;
const SUCCEED = 512;
const PROCEED = 1024;
const LABELED = 4096
const AWAIT = 8192;
const EMIT = 16384;
const DONE = 32768;

let categoryIDs = {
NEXT: NEXT,
REDO: REDO,
LAST: LAST,
RETURN: RETURN,
TAKE: TAKE,
WARN: WARN,
SUCCEED: SUCCEED,
PROCEED: PROCEED,
AWAIT: AWAIT,
EMIT: EMIT,
DONE: DONE
};

let categoryToName = {};
for (let name in categoryIDs) {
categoryToName[categoryIDs[name]] = name;
}

class ResumeException {
constructor(exception) {
this.exception = exception;
Expand All @@ -42,13 +59,53 @@ class Ctx extends NQPObject {
return this.$$callThis;
}

last() {
this.controlException(LAST);
}

lastLabeled(label) {
this.controlExceptionLabeled(label, LAST);
}

next() {
this.controlException(NEXT);
}

nextLabeled(label) {
this.controlExceptionLabeled(label, NEXT);
}

redo() {
this.controlException(REDO);
}

redoLabeled(label) {
this.controlExceptionLabeled(label, REDO);
}

controlException(category) {
let exType = BOOT.Exception;
let exception = exType._STable.REPR.allocate(exType._STable);
exception.$$category = category;
this.propagateControlException(exception);
}

controlExceptionLabeled(label, category) {
let exType = BOOT.Exception;
let exception = exType._STable.REPR.allocate(exType._STable);
exception.$$category = category | LABELED;
exception.$$payload = label;
this.propagateControlException(exception);
}

propagateControlException(exception) {
var handler = '$$' + categoryToName[exception.$$category];
let handler = '$$' + categoryToName[exception.$$category & ~LABELED];
let labeled = exception.$$category & LABELED;

var ctx = this;

while (ctx) {
if (ctx[handler] || ctx.$$CONTROL) {
if ((ctx[handler] || ctx.$$CONTROL) && (!labeled || !ctx.$$label || ctx.$$label === exception.$$payload)) {
exception.caught = ctx;
ctx.exception = exception;

Expand Down
10 changes: 0 additions & 10 deletions src/vm/js/nqp-runtime/runtime.js
Expand Up @@ -315,16 +315,6 @@ exports.regexCommit = function(bstack, mark) {
}
};

exports.Last = function(label) {
this.label = label;
};
exports.Redo = function(label) {
this.label = label;
};
exports.Next = function(label) {
this.label = label;
};

exports.NYI = function(msg) {
console.trace(msg);
return null;
Expand Down

0 comments on commit 7eda701

Please sign in to comment.