summaryrefslogtreecommitdiff
path: root/compile.c
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2019-09-04 10:54:12 -0700
committerJeremy Evans <code@jeremyevans.net>2019-09-05 09:57:43 -0700
commit1d5066efb08cbb328ba528a5f8be1708584b659f (patch)
treef5bd4c65ffb20f2fdbba6691f4e97e5c76e5f428 /compile.c
parent433c9c00d96124e3b416d0a20ff795b0ad4273fa (diff)
Make m(**{}) mean call without keywords
Previously, **{} was removed by the parser: ``` $ ruby --dump=parse -e '{**{}}' @ NODE_SCOPE (line: 1, location: (1,0)-(1,6)) +- nd_tbl: (empty) +- nd_args: | (null node) +- nd_body: @ NODE_HASH (line: 1, location: (1,0)-(1,6))* +- nd_brace: 1 (hash literal) +- nd_head: (null node) ``` Since it was removed by the parser, the compiler did not know about it, and `m(**{})` was therefore treated as `m()`. This modifies the parser to not remove the `**{}`. A simple approach for this is fairly simple by just removing a few lines from the parser, but that would cause two hash allocations every time it was used. The approach taken here modifies both the parser and the compiler, and results in `**{}` not allocating any hashes in the usual case. The basic idea is we use a literal node in the parser containing a frozen empty hash literal. In the compiler, we recognize when that is used, and if it is the only keyword present, we just push it onto the VM stack (no creation of a new hash or merging of keywords). If it is the first keyword present, we push a new empty hash onto the VM stack, so that later keywords can merge into it. If it is not the first keyword present, we can ignore it, since the there is no reason to merge an empty hash into the existing hash. Example instructions for `m(**{})` Before (note ARGS_SIMPLE): ``` == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,7)> (catch: FALSE) 0000 putself ( 1)[Li] 0001 opt_send_without_block <callinfo!mid:m, argc:0, FCALL|ARGS_SIMPLE>, <callcache> 0004 leave ``` After (note putobject and KW_SPLAT): ``` == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,7)> (catch: FALSE) 0000 putself ( 1)[Li] 0001 putobject {} 0003 opt_send_without_block <callinfo!mid:m, argc:1, FCALL|KW_SPLAT>, <callcache> 0006 leave ``` Example instructions for `m(**h, **{})` Before and After (no change): ``` == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,12)> (catch: FALSE) 0000 putself ( 1)[Li] 0001 putspecialobject 1 0003 newhash 0 0005 putself 0006 opt_send_without_block <callinfo!mid:h, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache> 0009 opt_send_without_block <callinfo!mid:core#hash_merge_kwd, argc:2, ARGS_SIMPLE>, <callcache> 0012 opt_send_without_block <callinfo!mid:m, argc:1, FCALL|KW_SPLAT>, <callcache> 0015 leave ``` Example instructions for `m(**{}, **h)` Before: ``` == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,12)> (catch: FALSE) 0000 putself ( 1)[Li] 0001 putspecialobject 1 0003 newhash 0 0005 putself 0006 opt_send_without_block <callinfo!mid:h, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache> 0009 opt_send_without_block <callinfo!mid:core#hash_merge_kwd, argc:2, ARGS_SIMPLE>, <callcache> 0012 opt_send_without_block <callinfo!mid:m, argc:1, FCALL|KW_SPLAT>, <callcache> 0015 leave ``` After (basically the same except for the addition of swap): ``` == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,12)> (catch: FALSE) 0000 putself ( 1)[Li] 0001 newhash 0 0003 putspecialobject 1 0005 swap 0006 putself 0007 opt_send_without_block <callinfo!mid:h, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache> 0010 opt_send_without_block <callinfo!mid:core#hash_merge_kwd, argc:2, ARGS_SIMPLE>, <callcache> 0013 opt_send_without_block <callinfo!mid:m, argc:1, FCALL|KW_SPLAT>, <callcache> 0016 leave ```
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/2428
Diffstat (limited to 'compile.c')
-rw-r--r--compile.c30
1 files changed, 24 insertions, 6 deletions
diff --git a/compile.c b/compile.c
index 3315716cc5..aca710e2b0 100644
--- a/compile.c
+++ b/compile.c
@@ -3940,6 +3940,8 @@ compile_array(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node_ro
else {
int opt_p = 1;
int first = 1, i;
+ int single_kw = 0;
+ int num_kw = 0;
while (node) {
const NODE *start_node = node, *end_node;
@@ -3955,11 +3957,15 @@ compile_array(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node_ro
if (type == COMPILE_ARRAY_TYPE_HASH && !node->nd_head) {
kw = node->nd_next;
+ num_kw++;
node = 0;
if (kw) {
opt_p = 0;
node = kw->nd_next;
kw = kw->nd_head;
+ if (!single_kw && !node) {
+ single_kw = 1;
+ }
}
break;
}
@@ -4053,6 +4059,7 @@ compile_array(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node_ro
}
case COMPILE_ARRAY_TYPE_HASH:
if (i > 0) {
+ num_kw++;
if (first) {
if (!popped) {
ADD_INSN1(anchor, line, newhash, INT2FIX(i));
@@ -4071,16 +4078,27 @@ compile_array(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node_ro
}
}
if (kw) {
- if (!popped) {
- ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
- if (i > 0 || !first) ADD_INSN(ret, line, swap);
- else ADD_INSN1(ret, line, newhash, INT2FIX(0));
+ int empty_kw = nd_type(kw) == NODE_LIT;
+ int first_kw = num_kw == 1;
+ int only_kw = single_kw && first_kw;
+
+ if (!popped && !empty_kw) {
+ ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
+ if (i > 0 || !first) ADD_INSN(ret, line, swap);
+ else ADD_INSN1(ret, line, newhash, INT2FIX(0));
}
- NO_CHECK(COMPILE(ret, "keyword splat", kw));
+
+ if (empty_kw && first_kw && !only_kw) {
+ ADD_INSN1(ret, line, newhash, INT2FIX(0));
+ }
+ else if (!empty_kw || only_kw) {
+ NO_CHECK(COMPILE(ret, "keyword splat", kw));
+ }
+
if (popped) {
ADD_INSN(ret, line, pop);
}
- else {
+ else if (!empty_kw) {
ADD_SEND(ret, line, id_core_hash_merge_kwd, INT2FIX(2));
}
}