summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Newton <kddnewton@gmail.com>2024-07-15 12:00:22 -0400
committerKevin Newton <kddnewton@gmail.com>2024-07-15 14:04:25 -0400
commit8080de04be8e99e71309745822a9d436cc4ae37c (patch)
tree9425aecb14b788ef5d77a346f889c102553df06e
parentc1e535844801c5a06f0e76181076c51bd3b5d189 (diff)
[PRISM] Optimize inner static literal hashes
-rw-r--r--prism_compile.c83
1 files changed, 74 insertions, 9 deletions
diff --git a/prism_compile.c b/prism_compile.c
index ceeba7ee1d..726543027e 100644
--- a/prism_compile.c
+++ b/prism_compile.c
@@ -1339,6 +1339,12 @@ pm_compile_hash_elements(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_l
int stack_length = 0;
bool first_chunk = true;
+ // This is an optimization wherein we keep track of whether or not the
+ // previous element was a static literal. If it was, then we do not attempt
+ // to check if we have a subhash that can be optimized. If it was not, then
+ // we do check.
+ bool static_literal = false;
+
DECL_ANCHOR(anchor);
INIT_ANCHOR(anchor);
@@ -1365,6 +1371,57 @@ pm_compile_hash_elements(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_l
switch (PM_NODE_TYPE(element)) {
case PM_ASSOC_NODE: {
+ // Pre-allocation check (this branch can be omitted).
+ if (PM_NODE_FLAG_P(element, PM_NODE_FLAG_STATIC_LITERAL) && !static_literal && ((index + min_tmp_hash_length) < elements->size)) {
+ // Count the elements that are statically-known.
+ size_t count = 1;
+ while (index + count < elements->size && PM_NODE_FLAG_P(elements->nodes[index + count], PM_NODE_FLAG_STATIC_LITERAL)) count++;
+
+ if (count >= min_tmp_hash_length) {
+ // The subsequence of elements in this hash is long enough
+ // to merit its own hash.
+ VALUE ary = rb_ary_hidden_new(count);
+
+ // Create a hidden hash.
+ for (size_t tmp_end = index + count; index < tmp_end; index++) {
+ const pm_assoc_node_t *assoc = (const pm_assoc_node_t *) elements->nodes[index];
+
+ VALUE elem[2] = {
+ pm_static_literal_value(iseq, assoc->key, scope_node),
+ pm_static_literal_value(iseq, assoc->value, scope_node)
+ };
+
+ rb_ary_cat(ary, elem, 2);
+ }
+
+ VALUE hash = rb_hash_new_with_size(RARRAY_LEN(ary) / 2);
+ rb_hash_bulk_insert(RARRAY_LEN(ary), RARRAY_CONST_PTR(ary), hash);
+ hash = rb_obj_hide(hash);
+ OBJ_FREEZE(hash);
+
+ // Emit optimized code.
+ FLUSH_CHUNK;
+ if (first_chunk) {
+ PUSH_INSN1(ret, location, duphash, hash);
+ first_chunk = false;
+ }
+ else {
+ PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
+ PUSH_INSN(ret, location, swap);
+ PUSH_INSN1(ret, location, putobject, hash);
+ PUSH_SEND(ret, location, id_core_hash_merge_kwd, INT2FIX(2));
+ }
+
+ break;
+ }
+ else {
+ static_literal = true;
+ }
+ }
+ else {
+ static_literal = false;
+ }
+
// If this is a plain assoc node, then we can compile it directly
// and then add the total number of values on the stack.
pm_compile_node(iseq, element, anchor, false, scope_node);
@@ -1429,10 +1486,10 @@ pm_compile_hash_elements(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_l
PM_COMPILE_NOT_POPPED(element);
PUSH_SEND(ret, location, id_core_hash_merge_kwd, INT2FIX(2));
}
-
- first_chunk = false;
}
+ first_chunk = false;
+ static_literal = false;
break;
}
default:
@@ -5641,7 +5698,13 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret,
static_literal = false;
}
else if (PM_NODE_TYPE_P(element, PM_KEYWORD_HASH_NODE)) {
- FLUSH_CHUNK;
+ if (new_array_size == 0 && first_chunk) {
+ PUSH_INSN1(ret, location, newarray, INT2FIX(0));
+ first_chunk = false;
+ }
+ else {
+ FLUSH_CHUNK;
+ }
// If we get here, then this is the last element of the
// array/arguments, because it cannot be followed by
@@ -5654,14 +5717,16 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret,
// ^^^^^
//
const pm_keyword_hash_node_t *keyword_hash = (const pm_keyword_hash_node_t *) element;
- pm_compile_hash_elements(iseq, element, &keyword_hash->elements, PM_NODE_TYPE_P(node, PM_ARGUMENTS_NODE), ret, scope_node);
+ pm_compile_hash_elements(iseq, element, &keyword_hash->elements, false, ret, scope_node);
// This boolean controls the manner in which we push the
- // hash onto the array. If it's a single element that has a
- // splat, then we can use the very specialized
- // pushtoarraykwsplat instruction to check if it's empty
- // before we push it.
- if (keyword_hash->elements.size == 1 && PM_NODE_TYPE_P(keyword_hash->elements.nodes[0], PM_ASSOC_SPLAT_NODE)) {
+ // hash onto the array. If it's all keyword splats, then we
+ // can use the very specialized pushtoarraykwsplat
+ // instruction to check if it's empty before we push it.
+ size_t splats = 0;
+ while (splats < keyword_hash->elements.size && PM_NODE_TYPE_P(keyword_hash->elements.nodes[splats], PM_ASSOC_SPLAT_NODE)) splats++;
+
+ if (keyword_hash->elements.size == splats) {
PUSH_INSN(ret, location, pushtoarraykwsplat);
}
else {