Skip to content

Commit c2cea52

Browse files
committed
[js] Refactor the way lexical scopes are handled
Instead of building up closures by passing all the surrounding ctxs as arguments have an .outerCtx on codeRefs. This is done both to simplify things as well as cut a large amount of startup time from loading of the rakudo.js setting.
1 parent 67a74d2 commit c2cea52

File tree

7 files changed

+184
-127
lines changed

7 files changed

+184
-127
lines changed

src/vm/js/Compiler.nqp

Lines changed: 129 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -239,13 +239,30 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
239239
}
240240

241241

242-
method ctx_for_var($var) {
242+
method ctx_for_var($var, :$from_outer) {
243243
my $info := self;
244+
my int $depth := 0;
245+
246+
my $reached_closure_template := 0;
247+
248+
if $from_outer {
249+
$reached_closure_template := $info.qast.blocktype ne 'immediate';
250+
$depth := $depth + 1 if $reached_closure_template;
251+
$info := $info.outer;
252+
}
253+
254+
244255
while $info {
256+
$reached_closure_template := $reached_closure_template || $info.qast.blocktype ne 'immediate';
257+
245258
if $info.has_own_variable($var.name) {
259+
%*USED_CTXS{$info.ctx} := $depth unless nqp::existskey(%*USED_CTXS, $info.ctx);
246260
return $info.ctx;
247261
}
248262
$info := $info.outer;
263+
264+
$depth := $depth + 1 if $reached_closure_template;
265+
249266
}
250267
}
251268
}
@@ -786,13 +803,32 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
786803
nqp::existskey(%!cuids, $node.cuid);
787804
}
788805

806+
has %!serialized_code_ref_info;
807+
808+
my class SerializedCodeRefInfo {
809+
has $!closure_template;
810+
has $!lexicals_type_info;
811+
has $!outer_cuid;
812+
has $!static_lexicals;
813+
has $!statevars;
814+
method outer_cuid() {$!outer_cuid}
815+
method lexicals_type_info() {$!lexicals_type_info}
816+
method closure_template() {$!closure_template}
817+
method static_lexicals() {$!static_lexicals}
818+
method statevars() {$!statevars}
819+
}
820+
789821
method setup_cuids() {
790822
my @declared;
791823
my @vars;
792824
for %!cuids {
793825
my str $var := self.mangled_cuid($_.key);
794826
@vars.push($var);
795-
@declared.push("$var = new nqp.CodeRef({quote_string($_.value.name)},{quote_string($_.key)})");
827+
828+
my int $has_statevars := nqp::existskey(%!serialized_code_ref_info, $_.key) && %!serialized_code_ref_info{$_.key}.statevars;
829+
830+
my $class := $has_statevars ?? 'CodeRefWithStateVars' !! 'CodeRef';
831+
@declared.push("$var = new nqp.$class({quote_string($_.value.name)},{quote_string($_.key)})");
796832
}
797833
@declared.push("cuids = [{nqp::join(',', @vars)}]");
798834
self.declare_js_vars(@declared);
@@ -829,18 +865,6 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
829865
}
830866
}
831867

832-
has %!serialized_code_ref_info;
833-
834-
my class SerializedCodeRefInfo {
835-
has $!closure_template;
836-
has $!lexicals_type_info;
837-
has $!outer_cuid;
838-
has $!static_lexicals;
839-
method outer_cuid() {$!outer_cuid}
840-
method lexicals_type_info() {$!lexicals_type_info}
841-
method closure_template() {$!closure_template}
842-
method static_lexicals() {$!static_lexicals}
843-
}
844868

845869
method type_info_for_lexicals(BlockInfo $block) {
846870
my @type_info;
@@ -885,39 +909,38 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
885909
}
886910

887911
self.wrap_static_block($block_info.outer, @clone_inners, -> {
888-
if %*BLOCKS_DONE{$kv.key} {
912+
my $outer := $block_info.outer.ctx;
913+
if self.has_closure_template($block_info) {
914+
if nqp::existskey($block.captured_inners, $kv.key) {
915+
@clone_inners.push("$reg = $cuid.captureAndClosureCtx($outer);\n");
916+
}
917+
else {
918+
@clone_inners.push("$reg = $cuid.closureCtx($outer);\n");
919+
}
920+
}
921+
else {
922+
unless %*BLOCKS_DONE{$kv.key} {
923+
nqp::die("//clone_inners - broken block: {$kv.key}");
924+
}
925+
889926
# Avoid emitting duplicated code with both .capture and .closure
890927
if nqp::existskey($block.captured_inners, $kv.key) {
891-
if self.has_closure_template($block_info) {
892-
@clone_inners.push("$reg = $cuid.captureAndClosureCtx({self.outer_ctxs($block_info)});\n");
893-
}
894-
else {
895-
my $outer := self.is_serializable($kv.key) ?? $block_info.outer.ctx !! 'null';
896-
@clone_inners.push("$reg = $cuid.captureAndClosure($outer, ");
897-
@clone_inners.push(%*BLOCKS_DONE{$kv.key});
898-
@clone_inners.push(");\n");
899-
}
928+
my $set_outer := self.is_serializable($kv.key) ?? $outer !! 'null';
929+
@clone_inners.push("$reg = $cuid.captureAndClosure($set_outer, ");
930+
@clone_inners.push(%*BLOCKS_DONE{$kv.key});
931+
@clone_inners.push(");\n");
900932
}
901933
else {
902-
if self.has_closure_template($block_info) {
903-
@clone_inners.push("$reg = $cuid.closureCtx({self.outer_ctxs($block_info)});\n");
934+
@clone_inners.push("$reg = $cuid.closure");
935+
@clone_inners.push(%*BLOCKS_DONE{$kv.key});
936+
if self.is_serializable($kv.key) {
937+
@clone_inners.push(".setOuter($outer);\n");
904938
}
905939
else {
906-
@clone_inners.push("$reg = $cuid.closure");
907-
@clone_inners.push(%*BLOCKS_DONE{$kv.key});
908-
if self.is_serializable($kv.key) {
909-
@clone_inners.push(".setOuter(" ~ $block_info.outer.ctx ~ ");\n");
910-
}
911-
else {
912-
@clone_inners.push(";\n");
913-
}
940+
@clone_inners.push(";\n");
914941
}
915942
}
916943
}
917-
else {
918-
nqp::die("//Broken block: {$kv.key}");
919-
}
920-
921944
});
922945
}
923946
Chunk.void(|@clone_inners);
@@ -940,7 +963,7 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
940963
my $block_info := %*BLOCKS_INFO{$kv.key};
941964
self.wrap_static_block($block_info.outer, @capture_inners, -> {
942965
if self.has_closure_template($block_info) {
943-
@capture_inners.push("$reg = $cuid.captureCtx({self.outer_ctxs($block_info)});\n");
966+
@capture_inners.push("$reg = $cuid.captureCtx({$block_info.outer.ctx});\n");
944967
} else {
945968
@capture_inners.push("$reg = $cuid.capture");
946969

@@ -959,22 +982,13 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
959982
Chunk.void(|@capture_inners);
960983
}
961984

962-
method outer_ctxs(BlockInfo $block) {
963-
my @ctxs;
964-
my $info := $block.outer;
965-
966-
# Avoid the ctx from the fake outer ctx
967-
while $info && !($info.ctx eq 'null' && !$info.outer) {
968-
@ctxs.unshift($info.ctx);
969-
$info := $info.outer;
970-
}
971-
nqp::join(',', @ctxs);
972-
}
973-
974985
method compile_block(QAST::Block $node, $outer, $outer_loop, :$want, :@extra_args=[]) {
975986

976987
my str $outer_ctx := try $*CTX // "null";
977988

989+
my $outer_used_ctx := try %*USED_CTXS;
990+
991+
978992
if self.is_known_cuid($node) {
979993
}
980994
else {
@@ -993,9 +1007,19 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
9931007

9941008
my int $body_want := $node.blocktype eq 'immediate' ?? $want !! $T_OBJ;
9951009

1010+
my int $has_closure_template := $node.blocktype ne 'immediate';
1011+
1012+
my %*USED_CTXS;
1013+
1014+
%*USED_CTXS := $outer_used_ctx unless $has_closure_template;
1015+
9961016
my $stmts := self.compile_all_the_statements($node, $body_want);
9971017

998-
my str $create_ctx := self.create_ctx($*CTX, :code_ref('this'));
1018+
1019+
my $outer_ctx := $has_closure_template ?? "this.outerCtx" !! ($*BLOCK.outer ?? $*BLOCK.outer.ctx !! 'null');
1020+
my str $create_ctx := "var $*CTX = new nqp.Ctx(caller_ctx, $outer_ctx, this);\n";
1021+
1022+
%*USED_CTXS{$*CTX} := 0;
9991023

10001024
my $sig := self.compile_sig($*BLOCK.params);
10011025

@@ -1032,7 +1056,6 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
10321056
$pass_exceptions_end,
10331057
"\}"
10341058
];
1035-
%*BLOCKS_DONE{$node.cuid} := Chunk.void("(", |@function, ")");
10361059

10371060
if $*BLOCK.statevars {
10381061
my @vars;
@@ -1045,42 +1068,66 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
10451068
%*BLOCKS_STATEVARS{$node.cuid} := "var " ~ nqp::join(',', @vars) ~ ";\n";
10461069
}
10471070

1048-
if 1 { # TODO make sure that only blocks that take part in serialization have that info emitted
1049-
my $outer_cuid;
1050-
if nqp::defined($*BLOCK.outer) && $*BLOCK.outer.cuid {
1051-
$outer_cuid := self.mangled_cuid($*BLOCK.outer.cuid);
1052-
}
1053-
my str $lexicals_type_info := self.type_info_for_lexicals($*BLOCK);
1054-
my $closure_template;
1055-
if $node.blocktype ne 'immediate' {
1056-
my @closure_template := nqp::clone(@function);
1057-
@closure_template.unshift(
1058-
"function({self.outer_ctxs($*BLOCK)}) \{\n"
1059-
~ %*BLOCKS_STATEVARS{$node.cuid}
1060-
~ "return ");
1061-
@closure_template.push('}');
1062-
$closure_template := Chunk.new($T_NONVAL, '', @closure_template);
1063-
}
1071+
my $outer_cuid;
1072+
if nqp::defined($*BLOCK.outer) && $*BLOCK.outer.cuid {
1073+
$outer_cuid := self.mangled_cuid($*BLOCK.outer.cuid);
1074+
}
1075+
my str $lexicals_type_info := self.type_info_for_lexicals($*BLOCK);
1076+
my $closure_template;
10641077

1065-
my @static;
1066-
for $*BLOCK.variables -> $var {
1067-
if $var.decl eq 'static' {
1068-
@static.push(quote_string($var.name) ~ ': ' ~ self.value_as_js($var.value));
1078+
my int $statevars;
1079+
1080+
if $has_closure_template {
1081+
my @closure_template := nqp::clone(@function);
1082+
1083+
1084+
my @used_ctxs;
1085+
for %*USED_CTXS -> $kv {
1086+
my int $depth := $kv.value;
1087+
next if $depth == 0;
1088+
my str $ctx := 'this.outerCtx';
1089+
while $depth > 1 {
1090+
$ctx := $ctx ~ '.$$outer';
1091+
$depth := $depth - 1;
10691092
}
1093+
@used_ctxs.push("let {$kv.key} = $ctx;\n");
10701094
}
10711095

1072-
my $static_lexicals;
1073-
if +@static {
1074-
$static_lexicals := '{' ~ nqp::join(',', @static) ~ '}';
1096+
nqp::splice(@closure_template, @used_ctxs, 5, 0);
1097+
1098+
$statevars := nqp::existskey(%*BLOCKS_STATEVARS, $node.cuid);
1099+
1100+
@closure_template.unshift(
1101+
"function() \{\n"
1102+
~ %*BLOCKS_STATEVARS{$node.cuid}
1103+
~ "return ") if $statevars;
1104+
@closure_template.push('}') if $statevars;
1105+
1106+
$closure_template := Chunk.new($T_NONVAL, '', @closure_template);
1107+
}
1108+
else {
1109+
%*BLOCKS_DONE{$node.cuid} := Chunk.void("(", |@function, ")");
1110+
}
1111+
1112+
my @static;
1113+
for $*BLOCK.variables -> $var {
1114+
if $var.decl eq 'static' {
1115+
@static.push(quote_string($var.name) ~ ': ' ~ self.value_as_js($var.value));
10751116
}
1117+
}
10761118

1077-
%!serialized_code_ref_info{$node.cuid} := SerializedCodeRefInfo.new(
1078-
:$closure_template,
1079-
:$outer_cuid,
1080-
:$lexicals_type_info,
1081-
:$static_lexicals
1082-
);
1119+
my $static_lexicals;
1120+
if +@static {
1121+
$static_lexicals := '{' ~ nqp::join(',', @static) ~ '}';
10831122
}
1123+
1124+
%!serialized_code_ref_info{$node.cuid} := SerializedCodeRefInfo.new(
1125+
:$statevars,
1126+
:$closure_template,
1127+
:$outer_cuid,
1128+
:$lexicals_type_info,
1129+
:$static_lexicals
1130+
);
10841131
}
10851132

10861133
if $node.blocktype eq 'raw' {
@@ -1129,13 +1176,6 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
11291176
$prefix~$!unique_vars;
11301177
}
11311178

1132-
method outer_ctx() {
1133-
$*BLOCK.outer ?? $*BLOCK.outer.ctx !! 'null';
1134-
}
1135-
1136-
method create_ctx($name, :$code_ref) {
1137-
"var $name = new nqp.Ctx(caller_ctx, this.forcedOuterCtx || {self.outer_ctx}, $code_ref);\n";
1138-
}
11391179

11401180
multi method as_js(QAST::IVal $node, :$want) {
11411181
Chunk.new($T_INT,'('~$node.value()~')', :$node);

src/vm/js/Operations.nqp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -893,11 +893,13 @@ class QAST::OperationsJS {
893893

894894
# TODO type
895895

896+
my $var := QAST::Var.new(:name($var_name), :scope<lexical>);
897+
896898
if !$block {
897899
Chunk.new($T_OBJ, "{$*BLOCK.ctx}.lookupFromOuter({quote_string($var_name)})", :$node);
898900
}
899-
elsif $comp.is_dynamic_var($block, QAST::Var.new(:name($var_name), :scope<lexical>)) {
900-
Chunk.new($T_OBJ, $block.ctx ~ "[" ~ quote_string($var_name) ~ "]", :$node);
901+
elsif $comp.is_dynamic_var($block, $var) {
902+
Chunk.new($T_OBJ, $*BLOCK.ctx_for_var($var, :from_outer) ~ "[" ~ quote_string($var_name) ~ "]", :$node);
901903
}
902904
else {
903905
Chunk.new($T_OBJ, $*BLOCK.outer.mangle_lexical($var_name) , :$node);

0 commit comments

Comments
 (0)