summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Newton <kddnewton@gmail.com>2024-02-12 22:50:10 -0500
committerKevin Newton <kddnewton@gmail.com>2024-02-13 09:37:24 -0500
commitc0e121420b084077a50ef7ec2cf56ad1b10e4041 (patch)
tree3eabb91ada5828f950bbf7be7514ba7293ee2c72
parentdedca31804510e42a89946d81c1834c9e5950d6f (diff)
[PRISM] Fix compilation of hash with multiple merges
-rw-r--r--prism_compile.c122
1 files changed, 77 insertions, 45 deletions
diff --git a/prism_compile.c b/prism_compile.c
index cfad11a109..5cbd0caabb 100644
--- a/prism_compile.c
+++ b/prism_compile.c
@@ -5660,66 +5660,98 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret,
// If this hash is popped, then this serves only to ensure we enact
// all side-effects (like method calls) that are contained within
// the hash contents.
- pm_hash_node_t *cast = (pm_hash_node_t *) node;
- // Elements must be non-empty, otherwise it would be static literal
- pm_node_list_t *elements = &cast->elements;
+ const pm_hash_node_t *cast = (const pm_hash_node_t *) node;
+ const pm_node_list_t *elements = &cast->elements;
- pm_node_t *cur_node = elements->nodes[0];
- pm_node_type_t cur_type = PM_NODE_TYPE(cur_node);
- int elements_of_cur_type = 0;
- int allocated_hashes = 0;
-
- if (!PM_NODE_TYPE_P(cur_node, PM_ASSOC_NODE) && !popped) {
- ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
- ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(0));
- allocated_hashes++;
+ if (popped) {
+ // If this hash is popped, then we can iterate through each
+ // element and compile it. The result of each compilation will
+ // only include the side effects of the element itself.
+ for (size_t index = 0; index < elements->size; index++) {
+ PM_COMPILE_POPPED(elements->nodes[index]);
+ }
}
-
- for (size_t index = 0; index < elements->size; index++) {
- pm_node_t *cur_node = elements->nodes[index];
- if (!popped) {
- if (!PM_NODE_TYPE_P(cur_node, cur_type)) {
- if (!allocated_hashes) {
- ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(elements_of_cur_type * 2));
- }
- else {
- if (cur_type == PM_ASSOC_NODE) {
- ADD_SEND(ret, &dummy_line_node, id_core_hash_merge_ptr, INT2FIX(3));
+ else {
+ // If this element is not popped, then we need to create the
+ // hash on the stack. Neighboring plain assoc nodes should be
+ // grouped together (either by newhash or hash merge). Double
+ // splat nodes should be merged using the mege_kwd method call.
+ int assoc_length = 0;
+ bool made_hash = false;
+
+ for (size_t index = 0; index < elements->size; index++) {
+ const pm_node_t *element = elements->nodes[index];
+
+ switch (PM_NODE_TYPE(element)) {
+ case PM_ASSOC_NODE:
+ // If this is a plain assoc node, then we can compile it
+ // directly and then add to the number of assoc nodes
+ // we've seen so far.
+ PM_COMPILE_NOT_POPPED(element);
+ assoc_length++;
+ break;
+ case PM_ASSOC_SPLAT_NODE: {
+ // If we are at a splat and we have already compiled
+ // some elements of the hash, then we need to either
+ // create the first hash or merge the current elements
+ // into the existing hash.
+ if (assoc_length > 0) {
+ if (!made_hash) {
+ ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(assoc_length * 2));
+ made_hash = true;
}
else {
- ADD_SEND(ret, &dummy_line_node, id_core_hash_merge_kwd, INT2FIX(2));
+ // Here we are merging plain assoc nodes into
+ // the hash on the stack.
+ ADD_SEND(ret, &dummy_line_node, id_core_hash_merge_ptr, INT2FIX(assoc_length * 2 + 1));
+
+ // Since we already have a hash on the stack, we
+ // need to set up the method call for the next
+ // merge that will occur.
+ ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
+ PM_SWAP;
}
+
+ assoc_length = 0;
+ }
+
+ // If this is the first time we've seen a splat, then we
+ // need to create a hash that we can merge into.
+ if (!made_hash) {
+ ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
+ ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(0));
+ made_hash = true;
}
+ PM_COMPILE_NOT_POPPED(element);
+ ADD_SEND(ret, &dummy_line_node, id_core_hash_merge_kwd, INT2FIX(2));
+
+ // We know that any subsequent elements will need to be
+ // merged in using one of the special core methods. So
+ // here we will put the receiver of the merge and then
+ // swap it with the hash that is going to be the first
+ // argument.
ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
PM_SWAP;
- PM_COMPILE(elements->nodes[index]);
- allocated_hashes++;
- elements_of_cur_type = 0;
- cur_type = PM_NODE_TYPE(cur_node);
- }
- else {
- elements_of_cur_type++;
- PM_COMPILE(elements->nodes[index]);
+ break;
+ }
+ default:
+ RUBY_ASSERT("Invalid node type for hash" && false);
+ break;
}
}
- else {
- PM_COMPILE(elements->nodes[index]);
- }
- }
- if (!popped) {
- if (!allocated_hashes) {
- ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(elements_of_cur_type * 2));
+ if (!made_hash) {
+ // If we haven't already made the hash, then this means we
+ // only saw plain assoc nodes. In this case, we can just
+ // create the hash directly.
+ ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(assoc_length * 2));
}
else {
- if (cur_type == PM_ASSOC_NODE) {
- ADD_SEND(ret, &dummy_line_node, id_core_hash_merge_ptr, INT2FIX(3));
- }
- else {
- ADD_SEND(ret, &dummy_line_node, id_core_hash_merge_kwd, INT2FIX(2));
- }
+ // If we have already made the hash, then we need to merge
+ // the remaining assoc nodes into the hash on the stack.
+ ADD_SEND(ret, &dummy_line_node, id_core_hash_merge_ptr, INT2FIX(assoc_length * 2 + 1));
}
}
}