Skip to content

Commit b272c80

Browse files
committed
[js] Start of work on compiling code in CPS transformed way.
1 parent 3dffa81 commit b272c80

File tree

4 files changed

+94
-32
lines changed

4 files changed

+94
-32
lines changed

src/vm/js/QAST/Compiler.nqp

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,7 +1375,12 @@ class QAST::OperationsJS {
13751375
add_simple_op('curcode', $T_OBJ, []);
13761376
add_simple_op('callercode', $T_OBJ, []);
13771377

1378-
method compile_op($comp, $op, :$want) {
1378+
add_simple_op('continuationreset', $T_OBJ, [$T_OBJ, $T_OBJ], :sideffects, :ctx);
1379+
add_simple_op('continuationinvoke', $T_OBJ, [$T_OBJ, $T_OBJ], :sideffects, :ctx);
1380+
add_simple_op('continuationcontrol', $T_OBJ, [$T_INT, $T_OBJ, $T_OBJ], :sideffects);
1381+
1382+
method compile_op($comp, $op, :$want, :$cps) {
1383+
# TODO cps
13791384
my str $name := $op.op;
13801385
if nqp::existskey(%ops, $name) {
13811386
%ops{$name}($comp, $op, :$want);
@@ -2274,13 +2279,17 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
22742279
}
22752280
}
22762281

2277-
method compile_sig(@params) {
2282+
method compile_sig(@params, :$cps) {
22782283
my $slurpy_named; # *%foo
22792284
my $slurpy; # *@foo
22802285

22812286
my @sig := ['caller_ctx','_NAMED'];
22822287
my @setup;
22832288

2289+
if $cps {
2290+
@sig.push('cont');
2291+
}
2292+
22842293
my $bind_named := '';
22852294
for @params {
22862295
if $_.slurpy {
@@ -2491,7 +2500,7 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
24912500
# TODO
24922501
}
24932502

2494-
proto method as_js($node, :$want) {
2503+
proto method as_js($node, :$want, :$cps) {
24952504
if nqp::defined($want) {
24962505
if nqp::istype($node, QAST::Want) {
24972506
self.NYI("QAST::Want");
@@ -2518,7 +2527,7 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
25182527
}
25192528

25202529
# TODO save the value of the last statement
2521-
method compile_all_the_statements(QAST::Stmts $node, $want, :$resultchild) {
2530+
method compile_all_the_statements(QAST::Stmts $node, $want, :$resultchild, :$cps) {
25222531
my @setup;
25232532
my @stmts := $node.list;
25242533
my int $n := +@stmts;
@@ -2533,15 +2542,15 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
25332542

25342543
my int $i := 0;
25352544
for @stmts {
2536-
my $chunk := self.as_js($_, :want($i == $resultchild ?? $want !! $T_VOID));
2545+
my $chunk := self.as_js($_, :want($i == $resultchild ?? $want !! $T_VOID), :$cps);
25372546
$result := $chunk.expr if $i == $resultchild;
25382547
nqp::push(@setup, $chunk);
25392548
$i := $i + 1;
25402549
}
25412550
Chunk.new($want, $result, @setup);
25422551
}
25432552

2544-
multi method as_js(QAST::Block $node, :$want) {
2553+
multi method as_js(QAST::Block $node, :$want, :$cps) {
25452554
my $outer := try $*BLOCK;
25462555
my $outer_loop := try $*LOOP;
25472556
self.compile_block($node, $outer, $outer_loop, :$want);
@@ -2640,6 +2649,8 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
26402649

26412650
@clone_inners.push("$reg = $cuid.closure");
26422651
@clone_inners.push(%*BLOCKS_DONE{$_.key});
2652+
@clone_inners.push(".cps");
2653+
@clone_inners.push(%*BLOCKS_DONE_CPS{$_.key});
26432654
@clone_inners.push(";\n");
26442655
}
26452656
Chunk.void(|@clone_inners);
@@ -2680,11 +2691,27 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
26802691

26812692
my $sig := self.compile_sig($*BLOCK.params);
26822693

2694+
my @function := [
2695+
"function({$sig.expr}) \{\n",
2696+
self.setup_setting($node),
2697+
self.declare_js_vars($*BLOCK.tmps),
2698+
self.declare_js_vars($*BLOCK.js_lexicals),
2699+
$create_ctx,
2700+
$sig,
2701+
self.clone_inners($*BLOCK),
2702+
self.capture_inners($*BLOCK),
2703+
$stmts,
2704+
"return {$stmts.expr};\n",
2705+
"\}"
2706+
];
26832707

2708+
# The CPS version
26842709

2710+
my $stmts_cps := self.compile_all_the_statements($node, $body_want, :cps);
26852711

2712+
my $sig_cps := self.compile_sig($*BLOCK.params, :cps);
26862713

2687-
my @function := [
2714+
my @function_cps := [
26882715
"function({$sig.expr}) \{\n",
26892716
self.setup_setting($node),
26902717
self.declare_js_vars($*BLOCK.tmps),
@@ -2699,6 +2726,9 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
26992726
];
27002727

27012728
%*BLOCKS_DONE{$node.cuid} := Chunk.void("(", |@function, ")");
2729+
2730+
%*BLOCKS_DONE_CPS{$node.cuid} := Chunk.void("(", |@function_cps, ")");
2731+
27022732

27032733
if self.is_block_part_of_sc($node) {
27042734
if $node.blocktype eq 'immediate' {
@@ -2764,29 +2794,29 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
27642794
"var $name = new nqp.Ctx(caller_ctx, {self.outer_ctx});\n";
27652795
}
27662796

2767-
multi method as_js(QAST::IVal $node, :$want) {
2797+
multi method as_js(QAST::IVal $node, :$want, :$cps) {
27682798
Chunk.new($T_INT,'('~$node.value()~')',[],:$node);
27692799
}
27702800

2771-
multi method as_js(QAST::NVal $node, :$want) {
2801+
multi method as_js(QAST::NVal $node, :$want, :$cps) {
27722802
Chunk.new($T_NUM,'('~$node.value()~')',[],:$node);
27732803
}
27742804

2775-
multi method as_js(QAST::SVal $node, :$want) {
2805+
multi method as_js(QAST::SVal $node, :$want, :$cps) {
27762806
Chunk.new($T_STR,quote_string($node.value()),[],:$node);
27772807
}
27782808

2779-
multi method as_js(QAST::BVal $node, :$want) {
2809+
multi method as_js(QAST::BVal $node, :$want, :$cps) {
27802810
self.as_js($node.value, :$want);
27812811
}
27822812

27832813
# Helps with register allocation on other backends
27842814
# We don't do allocate registers so just ignore that
2785-
multi method as_js(QAST::Stmt $node, :$want) {
2786-
self.as_js($node[0], :$want);
2815+
multi method as_js(QAST::Stmt $node, :$want, :$cps) {
2816+
self.as_js($node[0], :$want, :$cps);
27872817
}
27882818

2789-
multi method as_js(QAST::Stmts $node, :$want) {
2819+
multi method as_js(QAST::Stmts $node, :$want, :$cps) {
27902820
# for performance purposes we use the native js lexicals as much as possible, that means we need hacks for things that other backends can do easily with all the various ctx ops
27912821
if self.is_ctxsave($node) {
27922822
my @lexicals;
@@ -2795,17 +2825,17 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
27952825
}
27962826
Chunk.void("nqp.ctxsave(\{{nqp::join(',', @lexicals)}\});\n");
27972827
} else {
2798-
self.compile_all_the_statements($node, $want);
2828+
self.compile_all_the_statements($node, $want, :$cps);
27992829
}
28002830
}
28012831

2802-
multi method as_js(QAST::VM $node, :$want) {
2832+
multi method as_js(QAST::VM $node, :$want, :$cps) {
28032833
# We ignore QAST::VM as we don't support a js specific one, and the ones nqp generate contain parrot specific stuff we don't care about.
28042834
Chunk.new($T_VOID,'',[]);
28052835
}
28062836

2807-
multi method as_js(QAST::Op $node, :$want) {
2808-
QAST::OperationsJS.compile_op(self, $node, :$want);
2837+
multi method as_js(QAST::Op $node, :$want, :$cps) {
2838+
QAST::OperationsJS.compile_op(self, $node, :$want, :$cps);
28092839
}
28102840

28112841
method create_sc($ast) {
@@ -2894,7 +2924,7 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
28942924
}
28952925
}
28962926

2897-
multi method as_js(QAST::CompUnit $node, :$want) {
2927+
multi method as_js(QAST::CompUnit $node, :$want, :$cps) {
28982928
# Should have a single child which is the outer block.
28992929
if +@($node) != 1 || !nqp::istype($node[0], QAST::Block) {
29002930
nqp::die("QAST::CompUnit should have one child that is a QAST::Block");
@@ -2907,6 +2937,7 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
29072937

29082938
# Blocks we've seen while compiling.
29092939
my %*BLOCKS_DONE;
2940+
my %*BLOCKS_DONE_CPS;
29102941

29112942
# A fake outer block
29122943
my $*BLOCK := BlockInfo.new(NQPMu, NQPMu);
@@ -2988,12 +3019,13 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
29883019
}
29893020
}
29903021

2991-
multi method as_js(QAST::Var $node, :$want) {
3022+
multi method as_js(QAST::Var $node, :$want, :$cps) {
29923023
self.declare_var($node);
29933024
self.compile_var($node);
29943025
}
29953026

2996-
multi method as_js(QAST::VarWithFallback $node, :$want) {
3027+
multi method as_js(QAST::VarWithFallback $node, :$want, :$cps) {
3028+
# TODO CPS
29973029
my $var := self.compile_var($node);
29983030
if $var.type == $T_OBJ {
29993031
my $fallback := self.as_js($node.fallback, :want($T_OBJ));
@@ -3009,7 +3041,8 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
30093041
}
30103042
}
30113043

3012-
multi method as_js(QAST::Regex $node, :$want) {
3044+
multi method as_js(QAST::Regex $node, :$want, :$cps) {
3045+
# TODO CPS
30133046
RegexCompiler.new(compiler => self).compile($node);
30143047
}
30153048

@@ -3020,17 +3053,17 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
30203053
"nqp.wval({quote_string($handle)},$idx)";
30213054
}
30223055

3023-
multi method as_js(QAST::WVal $node, :$want) {
3056+
multi method as_js(QAST::WVal $node, :$want, :$cps) {
30243057
Chunk.new($T_OBJ, self.value_as_js($node.value), []);
30253058
}
30263059

30273060
method var_is_lexicalish(QAST::Var $var) {
30283061
$var.scope eq 'lexical' || $var.scope eq 'typevar';
30293062
}
30303063

3031-
method as_js_clear_bindval($node, :$want) {
3064+
method as_js_clear_bindval($node, :$want, :$cps) {
30323065
my $*BINDVAL := 0;
3033-
self.as_js($node, :$want);
3066+
self.as_js($node, :$want, :$cps);
30343067
}
30353068

30363069
method is_dynamic_var($var) {
@@ -3053,10 +3086,10 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
30533086
Chunk.new($T_OBJ, "nqp.op.atpos({$array_chunk.expr},{$index_chunk.expr})", [$array_chunk, $index_chunk], :node($node));
30543087
}
30553088

3056-
method compile_var(QAST::Var $var) {
3089+
method compile_var(QAST::Var $var, :$cps) {
30573090
if self.var_is_lexicalish($var) && self.is_dynamic_var($var) {
30583091
if $*BINDVAL {
3059-
my $bindval := self.as_js_clear_bindval($*BINDVAL, :want($T_OBJ));
3092+
my $bindval := self.as_js_clear_bindval($*BINDVAL, :want($T_OBJ), :$cps);
30603093
if $var.decl eq 'var' {
30613094
self.stored_result(Chunk.new($T_OBJ, "({$*CTX}[{quote_string($var.name)}] = {$bindval.expr})", [$bindval]));
30623095
} else {
@@ -3075,7 +3108,7 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
30753108
if $*BINDVAL {
30763109
# TODO better source mapping
30773110
# TODO use the proper type
3078-
my $bindval := self.as_js_clear_bindval($*BINDVAL, :want($T_OBJ));
3111+
my $bindval := self.as_js_clear_bindval($*BINDVAL, :want($T_OBJ), :$cps);
30793112
Chunk.new($type,$mangled, [$bindval,'('~$mangled~' = ('~ $bindval.expr ~ "));\n"]);
30803113
} else {
30813114
# TODO get the proper type
@@ -3110,17 +3143,17 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
31103143
# TODO take second argument into account
31113144
# TODO figure out if the second argument can be always assumed to be a WVal
31123145
# TODO types
3113-
my $self := self.as_js_clear_bindval($var[0], :want($T_OBJ));
3146+
my $self := self.as_js_clear_bindval($var[0], :want($T_OBJ), :$cps);
31143147
my $attr := Chunk.new($T_OBJ, "{$self.expr}[{quote_string($var.name)}]", [$self]);
31153148
if $*BINDVAL {
3116-
my $bindval := self.as_js_clear_bindval($*BINDVAL, :want($T_OBJ));
3149+
my $bindval := self.as_js_clear_bindval($*BINDVAL, :want($T_OBJ), :$cps);
31173150
Chunk.new($T_OBJ, $bindval.expr, [$attr, $bindval, "{$attr.expr} = {$bindval.expr};\n"]);
31183151
} else {
31193152
$attr;
31203153
}
31213154
} elsif ($var.scope eq 'contextual') {
31223155
if $*BINDVAL {
3123-
my $bindval := self.as_js_clear_bindval($*BINDVAL, :want($T_OBJ));
3156+
my $bindval := self.as_js_clear_bindval($*BINDVAL, :want($T_OBJ), :$cps);
31243157
self.stored_result(Chunk.new($T_OBJ, "{$*CTX}.bind_dynamic({quote_string($var.name)},{$bindval.expr})", [$bindval]));
31253158
} else {
31263159
Chunk.new($T_OBJ, "{$*CTX}.lookup_dynamic({quote_string($var.name)})", []);
@@ -3148,7 +3181,7 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
31483181

31493182

31503183

3151-
multi method as_js($unknown, :$want) {
3184+
multi method as_js($unknown, :$want, :$cps) {
31523185
self.NYI("Unimplemented QAST node type: " ~ $unknown.HOW.name($unknown));
31533186
}
31543187

src/vm/js/nqp-runtime/code-ref.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ CodeRef.prototype.closure = function(block) {
4444
return closure;
4545
};
4646

47+
CodeRef.prototype.cps = function(block) {
48+
this.$callCPS = block;
49+
this.$callCPS.codeRef = this;
50+
return this;
51+
};
52+
4753
CodeRef.prototype.setCodeObj = function(codeObj) {
4854
this.codeObj = codeObj;
4955
return this;

src/vm/js/nqp-runtime/core.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,3 +688,17 @@ op.typeparameterized = function(type) {
688688
var nqp = require('nqp-runtime');
689689
return st.parametricType ? st.parametricType : null;
690690
};
691+
692+
function startTrampoline(thunk_) {
693+
var thunk = thunk_;
694+
while (thunk) {
695+
thunk = thunk();
696+
}
697+
};
698+
699+
op.continuationreset = function(ctx, tag, continuation) {
700+
startTrampoline(function() {
701+
continuation.$callCPS(ctx, {});
702+
});
703+
};
704+

src/vm/js/nqp-runtime/runtime.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,3 +421,12 @@ exports.intAttrHack = function(attrValue) {
421421
exports.args = function(module) {
422422
return require.main === module ? process.argv.slice(1) : [];
423423
};
424+
425+
function runCPS(thunk_) {
426+
var thunk = thunk_;
427+
while (thunk) {
428+
thunk = thunk();
429+
}
430+
}
431+
432+
exports.runCPS = runCPS;

0 commit comments

Comments
 (0)