Skip to content

Commit 8bc870e

Browse files
committed
add opt_str_new
1 parent 841921f commit 8bc870e

7 files changed

Lines changed: 125 additions & 10 deletions

File tree

compile.c

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9656,6 +9656,27 @@ compile_call(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, co
96569656
LABEL *not_basic_new = NEW_LABEL(nd_line(node));
96579657
LABEL *not_basic_new_finish = NEW_LABEL(nd_line(node));
96589658

9659+
// Detect String.new(..., capacity: <n>): emit opt_string_new so the
9660+
// allocation can be pre-sized before initialize runs. The instruction
9661+
// guards at runtime that the receiver is String and "new" is unredefined,
9662+
// so a compile-time name match is sufficient here. string_new_capa_loc is
9663+
// the TOPN index of the capacity argument, or -1 when not applicable.
9664+
int string_new_capa_loc = -1;
9665+
if (inline_new && keywords != NULL &&
9666+
!(flag & (VM_CALL_ARGS_SPLAT | VM_CALL_KW_SPLAT | VM_CALL_FORWARDING))) {
9667+
const NODE *recv_node = get_nd_recv(node);
9668+
if (recv_node && nd_type_p(recv_node, NODE_CONST) &&
9669+
RNODE_CONST(recv_node)->nd_vid == rb_intern("String")) {
9670+
VALUE capacity_sym = ID2SYM(rb_intern("capacity"));
9671+
for (int i = 0; i < keywords->keyword_len; i++) {
9672+
if (keywords->keywords[i] == capacity_sym) {
9673+
string_new_capa_loc = keywords->keyword_len - 1 - i;
9674+
break;
9675+
}
9676+
}
9677+
}
9678+
}
9679+
96599680
if (inline_new) {
96609681
// Jump unless the receiver uses the "basic" implementation of "new"
96619682
VALUE ci;
@@ -9665,7 +9686,12 @@ compile_call(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, co
96659686
else {
96669687
ci = (VALUE)new_callinfo(iseq, mid, NUM2INT(argc), flag, keywords, 0);
96679688
}
9668-
ADD_INSN2(ret, node, opt_new, ci, not_basic_new);
9689+
if (string_new_capa_loc >= 0) {
9690+
ADD_INSN3(ret, node, opt_string_new, ci, not_basic_new, INT2FIX(string_new_capa_loc));
9691+
}
9692+
else {
9693+
ADD_INSN2(ret, node, opt_new, ci, not_basic_new);
9694+
}
96699695
LABEL_REF(not_basic_new);
96709696

96719697
// optimized path

insns.def

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,37 @@ opt_new
941941
}
942942
}
943943

944+
/* Specialized String.new: if "new" is still the builtin and the capacity
945+
* argument is a fixnum, allocate a right-sized String and use it as the
946+
* receiver for the following initialize. Otherwise jump to the generic new.
947+
* capa_loc is the TOPN index of the capacity argument on the stack. */
948+
DEFINE_INSN
949+
opt_string_new
950+
(CALL_DATA cd, OFFSET dst, rb_num_t capa_loc)
951+
()
952+
()
953+
// attr bool leaf = false;
954+
{
955+
VALUE argc = vm_ci_argc(cd->ci);
956+
VALUE recv = TOPN(argc);
957+
958+
// The bookkeeping slot should be empty.
959+
RUBY_ASSERT(TOPN(argc + 1) == Qnil);
960+
961+
VALUE capa = TOPN(capa_loc);
962+
963+
if (recv == rb_cString &&
964+
FIXNUM_P(capa) &&
965+
vm_method_cfunc_is(GET_CFP(), cd, recv, rb_str_s_new)) {
966+
VALUE str = rb_str_new_capa_for_init(FIX2LONG(capa));
967+
TOPN(argc) = str;
968+
TOPN(argc + 1) = str;
969+
}
970+
else {
971+
JUMP(dst);
972+
}
973+
}
974+
944975
/* Convert object to string using to_s or equivalent. */
945976
DEFINE_INSN
946977
objtostring

internal/string.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ VALUE rb_id_quote_unprintable(ID);
131131
VALUE rb_sym_proc_call(ID mid, int argc, const VALUE *argv, int kw_splat, VALUE passed_proc);
132132
VALUE rb_enc_literal_str(const char *ptr, long len, rb_encoding *enc);
133133

134+
/* opt_string_new support: builtin String.new and the right-sized allocation
135+
* the instruction hands to String#initialize. */
136+
VALUE rb_str_s_new(int argc, VALUE *argv, VALUE klass);
137+
VALUE rb_str_new_capa_for_init(long capa);
138+
134139
struct rb_execution_context_struct;
135140
VALUE rb_ec_str_resurrect(struct rb_execution_context_struct *ec, VALUE str, bool chilled);
136141

prism_compile.c

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3877,6 +3877,28 @@ pm_compile_call(rb_iseq_t *iseq, const pm_call_node_t *call_node, LINK_ANCHOR *c
38773877
call_node->block == NULL &&
38783878
(flags & VM_CALL_ARGS_BLOCKARG) == 0;
38793879

3880+
// Detect String.new(..., capacity: <n>): emit opt_string_new so the
3881+
// allocation can be pre-sized before initialize runs. The instruction
3882+
// guards at runtime that the receiver is String and "new" is unredefined,
3883+
// so a compile-time name match is sufficient here. string_new_capa_loc is
3884+
// the TOPN index of the capacity argument, or -1 when not applicable.
3885+
int string_new_capa_loc = -1;
3886+
if (inline_new && kw_arg != NULL &&
3887+
!(flags & (VM_CALL_ARGS_SPLAT | VM_CALL_KW_SPLAT | VM_CALL_FORWARDING)) &&
3888+
call_node->receiver != NULL &&
3889+
PM_NODE_TYPE_P(call_node->receiver, PM_CONSTANT_READ_NODE)) {
3890+
const pm_constant_read_node_t *recv = (const pm_constant_read_node_t *) call_node->receiver;
3891+
if (pm_constant_id_lookup(scope_node, recv->name) == rb_intern("String")) {
3892+
VALUE capacity_sym = ID2SYM(rb_intern("capacity"));
3893+
for (int i = 0; i < kw_arg->keyword_len; i++) {
3894+
if (kw_arg->keywords[i] == capacity_sym) {
3895+
string_new_capa_loc = kw_arg->keyword_len - 1 - i;
3896+
break;
3897+
}
3898+
}
3899+
}
3900+
}
3901+
38803902
if (inline_new) {
38813903
if (LAST_ELEMENT(ret) == opt_new_prelude) {
38823904
PUSH_INSN(ret, location, putnil);
@@ -3896,7 +3918,12 @@ pm_compile_call(rb_iseq_t *iseq, const pm_call_node_t *call_node, LINK_ANCHOR *c
38963918
ci = (VALUE)new_callinfo(iseq, method_id, orig_argc, flags, kw_arg, 0);
38973919
}
38983920

3899-
PUSH_INSN2(ret, location, opt_new, ci, not_basic_new);
3921+
if (string_new_capa_loc >= 0) {
3922+
PUSH_INSN3(ret, location, opt_string_new, ci, not_basic_new, INT2FIX(string_new_capa_loc));
3923+
}
3924+
else {
3925+
PUSH_INSN2(ret, location, opt_new, ci, not_basic_new);
3926+
}
39003927
LABEL_REF(not_basic_new);
39013928
// optimized path
39023929
PUSH_SEND_R(ret, location, rb_intern("initialize"), INT2FIX(orig_argc), block_iseq, INT2FIX(flags | VM_CALL_FCALL), kw_arg);

string.c

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1736,6 +1736,19 @@ rb_str_buf_new(long capa)
17361736
return str;
17371737
}
17381738

1739+
/* Allocate a String suitable for becoming the receiver of String#initialize
1740+
* with a `capacity:` of +capa+. Floors to STR_BUF_MIN_SIZE so that the buffer
1741+
* rb_str_init computes for the same capacity matches and is reused as-is
1742+
* (see the "reuse it as-is" branch in rb_str_init). Used by opt_string_new. */
1743+
VALUE
1744+
rb_str_new_capa_for_init(long capa)
1745+
{
1746+
if (capa < STR_BUF_MIN_SIZE) {
1747+
capa = STR_BUF_MIN_SIZE;
1748+
}
1749+
return rb_str_buf_new(capa);
1750+
}
1751+
17391752
VALUE
17401753
rb_str_buf_new_cstr(const char *ptr)
17411754
{
@@ -2097,8 +2110,13 @@ rb_str_init(rb_execution_context_t *ec, VALUE str, VALUE orig, VALUE no_str, VAL
20972110
if (orig == str) n = 0;
20982111
}
20992112
str_modifiable(str);
2100-
if (STR_EMBED_P(str) || FL_TEST(str, STR_SHARED|STR_NOFREE)) {
2101-
/* make noembed always */
2113+
if (!FL_TEST(str, STR_SHARED|STR_NOFREE) && str_capacity(str, termlen) >= (size_t)capa) {
2114+
/* The receiver already owns a big-enough buffer (e.g. opt_string_new
2115+
* pre-sized this object for us). Reuse it as-is, whether it is an
2116+
* embedded slot or an owned heap buffer -- do not reallocate. */
2117+
}
2118+
else if (STR_EMBED_P(str) || FL_TEST(str, STR_SHARED|STR_NOFREE)) {
2119+
/* Move to an owned heap buffer of the requested size. */
21022120
const size_t size = (size_t)capa + termlen;
21032121
const char *const old_ptr = RSTRING_PTR(str);
21042122
const size_t osize = RSTRING_LEN(str) + TERM_LEN(str);
@@ -2107,19 +2125,21 @@ rb_str_init(rb_execution_context_t *ec, VALUE str, VALUE orig, VALUE no_str, VAL
21072125
memcpy(new_ptr, old_ptr, osize < size ? osize : size);
21082126
FL_UNSET_RAW(str, STR_SHARED|STR_NOFREE);
21092127
RSTRING(str)->as.heap.ptr = new_ptr;
2128+
FL_SET(str, STR_NOEMBED);
2129+
RSTRING(str)->as.heap.aux.capa = capa;
21102130
}
2111-
else if (STR_HEAP_SIZE(str) != (size_t)capa + termlen) {
2131+
else {
2132+
/* Owned heap buffer that is too small: grow it. */
21122133
SIZED_REALLOC_N(RSTRING(str)->as.heap.ptr, char,
21132134
(size_t)capa + termlen, STR_HEAP_SIZE(str));
2135+
RSTRING(str)->as.heap.aux.capa = capa;
21142136
}
21152137
STR_SET_LEN(str, len);
2116-
TERM_FILL(&RSTRING(str)->as.heap.ptr[len], termlen);
2138+
TERM_FILL(RSTRING_PTR(str) + len, termlen);
21172139
if (n == 1) {
2118-
memcpy(RSTRING(str)->as.heap.ptr, RSTRING_PTR(orig), len);
2140+
memcpy(RSTRING_PTR(str), RSTRING_PTR(orig), len);
21192141
rb_enc_cr_str_exact_copy(str, orig);
21202142
}
2121-
FL_SET(str, STR_NOEMBED);
2122-
RSTRING(str)->as.heap.aux.capa = capa;
21232143
}
21242144
else if (n == 1) {
21252145
rb_str_replace(str, orig);
@@ -2136,7 +2156,7 @@ rb_str_init(rb_execution_context_t *ec, VALUE str, VALUE orig, VALUE no_str, VAL
21362156
}
21372157

21382158
/* :nodoc: */
2139-
static VALUE
2159+
VALUE
21402160
rb_str_s_new(int argc, VALUE *argv, VALUE klass)
21412161
{
21422162
if (klass != rb_cString) {

string.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ def ascii_only?
2323
Primitive.cexpr! 'rb_str_is_ascii_only_p(self)'
2424
end
2525

26+
# call-seq:
27+
# String.new(string = ''.encode(Encoding::ASCII_8BIT), **options) -> new_string
28+
#
29+
# :include: doc/string/new.rdoc
30+
#
2631
def initialize(orig = (no_str = true; nil),
2732
encoding: (no_encoding = true; nil),
2833
capacity: (no_capacity = true; nil))

vm.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "internal/re.h"
2828
#include "internal/ruby_parser.h"
2929
#include "internal/st.h"
30+
#include "internal/string.h"
3031
#include "internal/symbol.h"
3132
#include "internal/thread.h"
3233
#include "internal/transcode.h"

0 commit comments

Comments
 (0)