From 32680f543c6fc515870d4f5c6829607dc67d59ef Mon Sep 17 00:00:00 2001 From: ydah Date: Thu, 5 Sep 2024 11:07:13 +0900 Subject: [PATCH 01/14] Implement AND/OR NODE operator locations --- ast.c | 8 ++++++++ node_dump.c | 3 ++- parse.y | 22 ++++++++++++---------- rubyparser.h | 1 + test/ruby/test_ast.rb | 16 ++++++++++++++++ 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/ast.c b/ast.c index 12366dd432f7b4..67d121e1ce6c1c 100644 --- a/ast.c +++ b/ast.c @@ -779,6 +779,14 @@ node_locations(VALUE ast_value, const NODE *node) return rb_ary_new_from_args(2, location_new(nd_code_loc(node)), location_new(&RNODE_ALIAS(node)->keyword_loc)); + case NODE_AND: + return rb_ary_new_from_args(2, + location_new(nd_code_loc(node)), + location_new(&RNODE_AND(node)->operator_loc)); + case NODE_OR: + return rb_ary_new_from_args(2, + location_new(nd_code_loc(node)), + location_new(&RNODE_OR(node)->operator_loc)); case NODE_UNDEF: return rb_ary_new_from_args(2, location_new(nd_code_loc(node)), diff --git a/node_dump.c b/node_dump.c index 54a4eb0e64441a..ae948eb66eca0d 100644 --- a/node_dump.c +++ b/node_dump.c @@ -431,8 +431,9 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) break; node = RNODE_AND(node)->nd_2nd; } - LAST_NODE; F_NODE(nd_2nd, RNODE_AND, "right expr"); + LAST_NODE; + F_LOC(operator_loc, RNODE_AND); return; case NODE_MASGN: diff --git a/parse.y b/parse.y index 6ebd60844adc51..42396094be2686 100644 --- a/parse.y +++ b/parse.y @@ -1078,8 +1078,8 @@ static rb_node_begin_t *rb_node_begin_new(struct parser_params *p, NODE *nd_body static rb_node_rescue_t *rb_node_rescue_new(struct parser_params *p, NODE *nd_head, NODE *nd_resq, NODE *nd_else, const YYLTYPE *loc); static rb_node_resbody_t *rb_node_resbody_new(struct parser_params *p, NODE *nd_args, NODE *nd_exc_var, NODE *nd_body, NODE *nd_next, const YYLTYPE *loc); static rb_node_ensure_t *rb_node_ensure_new(struct parser_params *p, NODE *nd_head, NODE *nd_ensr, const YYLTYPE *loc); -static rb_node_and_t *rb_node_and_new(struct parser_params *p, NODE *nd_1st, NODE *nd_2nd, const YYLTYPE *loc); -static rb_node_or_t *rb_node_or_new(struct parser_params *p, NODE *nd_1st, NODE *nd_2nd, const YYLTYPE *loc); +static rb_node_and_t *rb_node_and_new(struct parser_params *p, NODE *nd_1st, NODE *nd_2nd, const YYLTYPE *loc, const YYLTYPE *operator_loc); +static rb_node_or_t *rb_node_or_new(struct parser_params *p, NODE *nd_1st, NODE *nd_2nd, const YYLTYPE *loc, const YYLTYPE *operator_loc); static rb_node_masgn_t *rb_node_masgn_new(struct parser_params *p, NODE *nd_head, NODE *nd_args, const YYLTYPE *loc); static rb_node_lasgn_t *rb_node_lasgn_new(struct parser_params *p, ID nd_vid, NODE *nd_value, const YYLTYPE *loc); static rb_node_dasgn_t *rb_node_dasgn_new(struct parser_params *p, ID nd_vid, NODE *nd_value, const YYLTYPE *loc); @@ -1186,8 +1186,8 @@ static rb_node_error_t *rb_node_error_new(struct parser_params *p, const YYLTYPE #define NEW_RESCUE(b,res,e,loc) (NODE *)rb_node_rescue_new(p,b,res,e,loc) #define NEW_RESBODY(a,v,ex,n,loc) (NODE *)rb_node_resbody_new(p,a,v,ex,n,loc) #define NEW_ENSURE(b,en,loc) (NODE *)rb_node_ensure_new(p,b,en,loc) -#define NEW_AND(f,s,loc) (NODE *)rb_node_and_new(p,f,s,loc) -#define NEW_OR(f,s,loc) (NODE *)rb_node_or_new(p,f,s,loc) +#define NEW_AND(f,s,loc,op_loc) (NODE *)rb_node_and_new(p,f,s,loc,op_loc) +#define NEW_OR(f,s,loc,op_loc) (NODE *)rb_node_or_new(p,f,s,loc,op_loc) #define NEW_MASGN(l,r,loc) rb_node_masgn_new(p,l,r,loc) #define NEW_LASGN(v,val,loc) (NODE *)rb_node_lasgn_new(p,v,val,loc) #define NEW_DASGN(v,val,loc) (NODE *)rb_node_dasgn_new(p,v,val,loc) @@ -5544,7 +5544,7 @@ p_as : p_expr tASSOC p_variable p_alt : p_alt '|' p_expr_basic { - $$ = NEW_OR($1, $3, &@$); + $$ = NEW_OR($1, $3, &@$, &@2); /*% ripper: binary!($:1, ID2VAL(idOr), $:3) %*/ } | p_expr_basic @@ -11489,21 +11489,23 @@ rb_node_ensure_new(struct parser_params *p, NODE *nd_head, NODE *nd_ensr, const } static rb_node_and_t * -rb_node_and_new(struct parser_params *p, NODE *nd_1st, NODE *nd_2nd, const YYLTYPE *loc) +rb_node_and_new(struct parser_params *p, NODE *nd_1st, NODE *nd_2nd, const YYLTYPE *loc, const YYLTYPE *operator_loc) { rb_node_and_t *n = NODE_NEWNODE(NODE_AND, rb_node_and_t, loc); n->nd_1st = nd_1st; n->nd_2nd = nd_2nd; + n->operator_loc = *operator_loc; return n; } static rb_node_or_t * -rb_node_or_new(struct parser_params *p, NODE *nd_1st, NODE *nd_2nd, const YYLTYPE *loc) +rb_node_or_new(struct parser_params *p, NODE *nd_1st, NODE *nd_2nd, const YYLTYPE *loc, const YYLTYPE *operator_loc) { rb_node_or_t *n = NODE_NEWNODE(NODE_OR, rb_node_or_t, loc); n->nd_1st = nd_1st; n->nd_2nd = nd_2nd; + n->operator_loc = *operator_loc; return n; } @@ -14340,7 +14342,7 @@ new_unless(struct parser_params *p, NODE *cc, NODE *left, NODE *right, const YYL return newline_node(NEW_UNLESS(cc, left, right, loc, keyword_loc, then_keyword_loc, end_keyword_loc)); } -#define NEW_AND_OR(type, f, s, loc) (type == NODE_AND ? NEW_AND(f,s,loc) : NEW_OR(f,s,loc)) +#define NEW_AND_OR(type, f, s, loc, op_loc) (type == NODE_AND ? NEW_AND(f,s,loc,op_loc) : NEW_OR(f,s,loc,op_loc)) static NODE* logop(struct parser_params *p, ID id, NODE *left, NODE *right, @@ -14354,12 +14356,12 @@ logop(struct parser_params *p, ID id, NODE *left, NODE *right, while ((second = RNODE_AND(node)->nd_2nd) != 0 && nd_type_p(second, type)) { node = second; } - RNODE_AND(node)->nd_2nd = NEW_AND_OR(type, second, right, loc); + RNODE_AND(node)->nd_2nd = NEW_AND_OR(type, second, right, loc, op_loc); nd_set_line(RNODE_AND(node)->nd_2nd, op_loc->beg_pos.lineno); left->nd_loc.end_pos = loc->end_pos; return left; } - op = NEW_AND_OR(type, left, right, loc); + op = NEW_AND_OR(type, left, right, loc, op_loc); nd_set_line(op, op_loc->beg_pos.lineno); return op; } diff --git a/rubyparser.h b/rubyparser.h index 5383c31f03b330..1a1a1d2b07a9c4 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -382,6 +382,7 @@ typedef struct { struct RNode *nd_1st; struct RNode *nd_2nd; + rb_code_location_t operator_loc; } rb_node_and_t, rb_node_or_t; typedef struct RNode_MASGN { diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index d1422fc2c0d6fc..f03a323ccf24eb 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -1335,6 +1335,22 @@ def test_alias_locations assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]]) end + def test_and_locations + node = RubyVM::AbstractSyntaxTree.parse("1 and 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 2, 1, 5]]) + + node = RubyVM::AbstractSyntaxTree.parse("1 && 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]]) + end + + def test_or_locations + node = RubyVM::AbstractSyntaxTree.parse("1 or 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]]) + + node = RubyVM::AbstractSyntaxTree.parse("1 || 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]]) + end + def test_unless_locations node = RubyVM::AbstractSyntaxTree.parse("unless cond then 1 else 2 end") assert_locations(node.children[-1].locations, [[1, 0, 1, 29], [1, 0, 1, 6], [1, 12, 1, 16], [1, 26, 1, 29]]) From 630bfd36f9a10c74da1084493ea806d3c706eeba Mon Sep 17 00:00:00 2001 From: git Date: Thu, 5 Sep 2024 07:01:45 +0000 Subject: [PATCH 02/14] Update bundled gems list as of 2024-09-04 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 235aeb0b72d0ee..9af65809ee7f78 100644 --- a/NEWS.md +++ b/NEWS.md @@ -95,7 +95,7 @@ The following bundled gems are updated. * rexml 3.3.7 * rss 0.3.1 * net-ftp 0.3.7 -* net-imap 0.4.15 +* net-imap 0.4.16 * net-smtp 0.5.0 * rbs 3.5.3 * typeprof 0.21.11 diff --git a/gems/bundled_gems b/gems/bundled_gems index 0e82d9e2fd5a39..18661d260de736 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -13,7 +13,7 @@ test-unit 3.6.2 https://github.com/test-unit/test-unit rexml 3.3.7 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.7 https://github.com/ruby/net-ftp -net-imap 0.4.15 https://github.com/ruby/net-imap +net-imap 0.4.16 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.0 https://github.com/ruby/net-smtp matrix 0.4.2 https://github.com/ruby/matrix From 08e142b209a4919aac527bb0ce174a0ca0333d5c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 4 Sep 2024 11:44:12 +0900 Subject: [PATCH 03/14] Refactor `getlogin` and `getpw*` functions - Extract functions to check not-found conditions - Set the length to the result of `rb_getlogin` - Reentrant versions return an error numeber but not `errno` - Check maybe-undefined macros with `defined` --- process.c | 102 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/process.c b/process.c index 09b0cc8d1776f4..acb5b661567095 100644 --- a/process.c +++ b/process.c @@ -5700,6 +5700,12 @@ check_gid_switch(void) #if defined(HAVE_PWD_H) +static inline bool +login_not_found(int err) +{ + return (err == ENOTTY || err == ENXIO || err == ENOENT); +} + /** * Best-effort attempt to obtain the name of the login user, if any, * associated with the process. Processes not descended from login(1) (or @@ -5708,18 +5714,18 @@ check_gid_switch(void) VALUE rb_getlogin(void) { -#if ( !defined(USE_GETLOGIN_R) && !defined(USE_GETLOGIN) ) +# if !defined(USE_GETLOGIN_R) && !defined(USE_GETLOGIN) return Qnil; -#else +# else char MAYBE_UNUSED(*login) = NULL; # ifdef USE_GETLOGIN_R -#if defined(__FreeBSD__) +# if defined(__FreeBSD__) typedef int getlogin_r_size_t; -#else +# else typedef size_t getlogin_r_size_t; -#endif +# endif long loginsize = GETLOGIN_R_SIZE_INIT; /* maybe -1 */ @@ -5733,10 +5739,8 @@ rb_getlogin(void) rb_str_set_len(maybe_result, loginsize); int gle; - errno = 0; while ((gle = getlogin_r(login, (getlogin_r_size_t)loginsize)) != 0) { - - if (gle == ENOTTY || gle == ENXIO || gle == ENOENT) { + if (login_not_found(gle)) { rb_str_resize(maybe_result, 0); return Qnil; } @@ -5756,17 +5760,19 @@ rb_getlogin(void) return Qnil; } + rb_str_set_len(maybe_result, strlen(login)); return maybe_result; -# elif USE_GETLOGIN +# elif defined(USE_GETLOGIN) errno = 0; login = getlogin(); - if (errno) { - if (errno == ENOTTY || errno == ENXIO || errno == ENOENT) { + int err = errno; + if (err) { + if (login_not_found(err)) { return Qnil; } - rb_syserr_fail(errno, "getlogin"); + rb_syserr_fail(err, "getlogin"); } return login ? rb_str_new_cstr(login) : Qnil; @@ -5775,10 +5781,26 @@ rb_getlogin(void) #endif } +/* avoid treating as errors errno values that indicate "not found" */ +static inline bool +pwd_not_found(int err) +{ + switch (err) { + case 0: + case ENOENT: + case ESRCH: + case EBADF: + case EPERM: + return true; + default: + return false; + } +} + VALUE rb_getpwdirnam_for_login(VALUE login_name) { -#if ( !defined(USE_GETPWNAM_R) && !defined(USE_GETPWNAM) ) +#if !defined(USE_GETPWNAM_R) && !defined(USE_GETPWNAM) return Qnil; #else @@ -5787,7 +5809,7 @@ rb_getpwdirnam_for_login(VALUE login_name) return Qnil; } - char *login = RSTRING_PTR(login_name); + const char *login = RSTRING_PTR(login_name); struct passwd *pwptr; @@ -5807,11 +5829,8 @@ rb_getpwdirnam_for_login(VALUE login_name) rb_str_set_len(getpwnm_tmp, bufsizenm); int enm; - errno = 0; while ((enm = getpwnam_r(login, &pwdnm, bufnm, bufsizenm, &pwptr)) != 0) { - - if (enm == ENOENT || enm== ESRCH || enm == EBADF || enm == EPERM) { - /* not found; non-errors */ + if (pwd_not_found(enm)) { rb_str_resize(getpwnm_tmp, 0); return Qnil; } @@ -5837,21 +5856,21 @@ rb_getpwdirnam_for_login(VALUE login_name) rb_str_resize(getpwnm_tmp, 0); return result; -# elif USE_GETPWNAM +# elif defined(USE_GETPWNAM) errno = 0; - pwptr = getpwnam(login); - if (pwptr) { - /* found it */ - return rb_str_new_cstr(pwptr->pw_dir); - } - if (errno - /* avoid treating as errors errno values that indicate "not found" */ - && ( errno != ENOENT && errno != ESRCH && errno != EBADF && errno != EPERM)) { - rb_syserr_fail(errno, "getpwnam"); + if (!(pwptr = getpwnam(login))) { + int err = errno; + + if (pwd_not_found(err)) { + return Qnil; + } + + rb_syserr_fail(err, "getpwnam"); } - return Qnil; /* not found */ + /* found it */ + return rb_str_new_cstr(pwptr->pw_dir); # endif #endif @@ -5887,11 +5906,8 @@ rb_getpwdiruid(void) rb_str_set_len(getpwid_tmp, bufsizeid); int eid; - errno = 0; while ((eid = getpwuid_r(ruid, &pwdid, bufid, bufsizeid, &pwptr)) != 0) { - - if (eid == ENOENT || eid== ESRCH || eid == EBADF || eid == EPERM) { - /* not found; non-errors */ + if (pwd_not_found(eid)) { rb_str_resize(getpwid_tmp, 0); return Qnil; } @@ -5920,18 +5936,18 @@ rb_getpwdiruid(void) # elif defined(USE_GETPWUID) errno = 0; - pwptr = getpwuid(ruid); - if (pwptr) { - /* found it */ - return rb_str_new_cstr(pwptr->pw_dir); - } - if (errno - /* avoid treating as errors errno values that indicate "not found" */ - && ( errno == ENOENT || errno == ESRCH || errno == EBADF || errno == EPERM)) { - rb_syserr_fail(errno, "getpwuid"); + if (!(pwptr = getpwuid(ruid))) { + int err = errno; + + if (pwd_not_found(err)) { + return Qnil; + } + + rb_syserr_fail(err, "getpwuid"); } - return Qnil; /* not found */ + /* found it */ + return rb_str_new_cstr(pwptr->pw_dir); # endif #endif /* !defined(USE_GETPWUID_R) && !defined(USE_GETPWUID) */ From 2e5680d304a9cf9a6a2ba582091af6719e839351 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Fri, 26 Jul 2024 16:57:39 +0200 Subject: [PATCH 04/14] [ruby/openssl] Fix test_pkey_rsa.rb in FIPS. * test_sign_verify I created the signature text (`signature_encoded.txt`), that is used as a text to create the `signature0` in the `test_sign_verify` by the following steps with the `openssl` CLI on FIPS module. ``` $ OPENSSL_DIR="${HOME}/.local/openssl-3.4.0-dev-fips-debug-3c6e114959" $ export OPENSSL_CONF="${OPENSSL_DIR}/ssl/openssl_fips.cnf" $ echo -n "Sign me!" > data.txt $ "${OPENSSL_DIR}/bin/openssl" dgst -sha256 -sign test/openssl/fixtures/pkey/rsa2048.pem data.txt > signature.txt $ cat signature.txt | base64 > signature_encoded.txt ``` https://github.com/ruby/openssl/commit/091f3eb421 --- test/openssl/test_pkey_rsa.rb | 247 +++++++++++++++++++++------------- 1 file changed, 152 insertions(+), 95 deletions(-) diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb index 61c55c60b255d0..02693c277bd262 100644 --- a/test/openssl/test_pkey_rsa.rb +++ b/test/openssl/test_pkey_rsa.rb @@ -14,9 +14,7 @@ def test_no_private_exp end if !openssl?(3, 0, 0) # Impossible state in OpenSSL 3.0 def test_private - # Generated by key size and public exponent - key = OpenSSL::PKey::RSA.new(512, 3) - assert(key.private?) + key = Fixtures.pkey("rsa2048") # Generated by DER key2 = OpenSSL::PKey::RSA.new(key.to_der) @@ -46,55 +44,66 @@ def test_private end def test_new - key = OpenSSL::PKey::RSA.new(512) - assert_equal 512, key.n.num_bits + key = OpenSSL::PKey::RSA.new(2048) + assert_equal 2048, key.n.num_bits assert_equal 65537, key.e assert_not_nil key.d + assert(key.private?) + end + + def test_new_public_exponent + # At least 2024-bits RSA key are required in FIPS. + omit_on_fips # Specify public exponent - key2 = OpenSSL::PKey::RSA.new(512, 3) - assert_equal 512, key2.n.num_bits - assert_equal 3, key2.e - assert_not_nil key2.d + key = OpenSSL::PKey::RSA.new(512, 3) + assert_equal 512, key.n.num_bits + assert_equal 3, key.e end def test_s_generate - key1 = OpenSSL::PKey::RSA.generate(512) - assert_equal 512, key1.n.num_bits + key1 = OpenSSL::PKey::RSA.generate(2048) + assert_equal 2048, key1.n.num_bits assert_equal 65537, key1.e + end + + def test_s_generate_public_exponent + # At least 2024-bits RSA key are required in FIPS. + omit_on_fips # Specify public exponent - key2 = OpenSSL::PKey::RSA.generate(512, 3) - assert_equal 512, key2.n.num_bits - assert_equal 3, key2.e - assert_not_nil key2.d + key = OpenSSL::PKey::RSA.generate(512, 3) + assert_equal 512, key.n.num_bits + assert_equal 3, key.e end def test_new_break - assert_nil(OpenSSL::PKey::RSA.new(1024) { break }) + assert_nil(OpenSSL::PKey::RSA.new(2048) { break }) assert_raise(RuntimeError) do - OpenSSL::PKey::RSA.new(1024) { raise } + OpenSSL::PKey::RSA.new(2048) { raise } end end def test_sign_verify - rsa1024 = Fixtures.pkey("rsa1024") + rsa = Fixtures.pkey("rsa2048") data = "Sign me!" - signature = rsa1024.sign("SHA256", data) - assert_equal true, rsa1024.verify("SHA256", signature, data) + signature = rsa.sign("SHA256", data) + assert_equal true, rsa.verify("SHA256", signature, data) signature0 = (<<~'end;').unpack1("m") - oLCgbprPvfhM4pjFQiDTFeWI9Sk+Og7Nh9TmIZ/xSxf2CGXQrptlwo7NQ28+ - WA6YQo8jPH4hSuyWIM4Gz4qRYiYRkl5TDMUYob94zm8Si1HxEiS9354tzvqS - zS8MLW2BtNPuTubMxTItHGTnOzo9sUg0LAHVFt8kHG2NfKAw/gQ= + ooy49i8aeFtkDYUU0RPDsEugGiNw4lZxpbQPnIwtdftEkka945IqKZ/MY3YSw7wKsvBZeaTy8GqL + lSWLThsRFDV+UUS9zUBbQ9ygNIT8OjdV+tNL63ZpKGprczSnw4F05MQIpajNRud/8jiI9rf+Wysi + WwXecjMl2FlXlLJHY4PFQZU5TiametB4VCQRMcjLo1uf26u/yRpiGaYyqn5vxs0SqNtUDM1UL6x4 + NHCAdqLjuFRQPjYp1vGLD3eSl4061pS8x1NVap3YGbYfGUyzZO4VfwFwf1jPdhp/OX/uZw4dGB2H + gSK+q1JiDFwEE6yym5tdKovL1g1NhFYHF6gkZg== end; - assert_equal true, rsa1024.verify("SHA256", signature0, data) + assert_equal true, rsa.verify("SHA256", signature0, data) signature1 = signature0.succ - assert_equal false, rsa1024.verify("SHA256", signature1, data) + assert_equal false, rsa.verify("SHA256", signature1, data) end def test_sign_verify_options - key = Fixtures.pkey("rsa1024") + key = Fixtures.pkey("rsa2048") data = "Sign me!" pssopts = { "rsa_padding_mode" => "pss", @@ -102,7 +111,7 @@ def test_sign_verify_options "rsa_mgf1_md" => "SHA1" } sig_pss = key.sign("SHA256", data, pssopts) - assert_equal 128, sig_pss.bytesize + assert_equal 256, sig_pss.bytesize assert_equal true, key.verify("SHA256", sig_pss, data, pssopts) assert_equal true, key.verify_pss("SHA256", sig_pss, data, salt_length: 20, mgf1_hash: "SHA1") @@ -175,12 +184,12 @@ def test_verify_empty_rsa end def test_sign_verify_pss - key = Fixtures.pkey("rsa1024") + key = Fixtures.pkey("rsa2048") data = "Sign me!" invalid_data = "Sign me?" signature = key.sign_pss("SHA256", data, salt_length: 20, mgf1_hash: "SHA1") - assert_equal 128, signature.bytesize + assert_equal 256, signature.bytesize assert_equal true, key.verify_pss("SHA256", signature, data, salt_length: 20, mgf1_hash: "SHA1") assert_equal true, @@ -196,14 +205,26 @@ def test_sign_verify_pss assert_equal false, key.verify_pss("SHA256", signature, data, salt_length: 20, mgf1_hash: "SHA1") - signature = key.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA1") - assert_equal true, - key.verify_pss("SHA256", signature, data, salt_length: 94, mgf1_hash: "SHA1") - assert_equal true, - key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA1") + # The sign_pss with `salt_length: :max` raises the "invalid salt length" + # error in FIPS. We need to skip the tests in FIPS. + # According to FIPS 186-5 section 5.4, the salt length shall be between zero + # and the output block length of the digest function (inclusive). + # + # FIPS 186-5 section 5.4 PKCS #1 + # https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf + unless OpenSSL.fips_mode + signature = key.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA1") + # Should verify on the following salt_length (sLen). + # sLen <= emLen (octat) - 2 - hLen (octet) = 2048 / 8 - 2 - 256 / 8 = 222 + # https://datatracker.ietf.org/doc/html/rfc8017#section-9.1.1 + assert_equal true, + key.verify_pss("SHA256", signature, data, salt_length: 222, mgf1_hash: "SHA1") + assert_equal true, + key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA1") + end assert_raise(OpenSSL::PKey::RSAError) { - key.sign_pss("SHA256", data, salt_length: 95, mgf1_hash: "SHA1") + key.sign_pss("SHA256", data, salt_length: 223, mgf1_hash: "SHA1") } end @@ -213,8 +234,13 @@ def test_encrypt_decrypt # Defaults to PKCS #1 v1.5 raw = "data" - enc = rsapub.encrypt(raw) - assert_equal raw, rsapriv.decrypt(enc) + # According to the NIST SP 800-131A Rev. 2 section 6, PKCS#1 v1.5 padding is + # not permitted for key agreement and key transport using RSA in FIPS. + # https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar2.pdf + unless OpenSSL.fips_mode + enc = rsapub.encrypt(raw) + assert_equal raw, rsapriv.decrypt(enc) + end # Invalid options assert_raise(OpenSSL::PKey::PKeyError) { @@ -227,11 +253,13 @@ def test_encrypt_decrypt_legacy rsapub = OpenSSL::PKey.read(rsapriv.public_to_der) # Defaults to PKCS #1 v1.5 - raw = "data" - enc_legacy = rsapub.public_encrypt(raw) - assert_equal raw, rsapriv.decrypt(enc_legacy) - enc_new = rsapub.encrypt(raw) - assert_equal raw, rsapriv.private_decrypt(enc_new) + unless OpenSSL.fips_mode + raw = "data" + enc_legacy = rsapub.public_encrypt(raw) + assert_equal raw, rsapriv.decrypt(enc_legacy) + enc_new = rsapub.encrypt(raw) + assert_equal raw, rsapriv.private_decrypt(enc_new) + end # OAEP with default parameters raw = "data" @@ -292,53 +320,67 @@ def test_to_der end def test_RSAPrivateKey - rsa1024 = Fixtures.pkey("rsa1024") + rsa = Fixtures.pkey("rsa2048") asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(0), - OpenSSL::ASN1::Integer(rsa1024.n), - OpenSSL::ASN1::Integer(rsa1024.e), - OpenSSL::ASN1::Integer(rsa1024.d), - OpenSSL::ASN1::Integer(rsa1024.p), - OpenSSL::ASN1::Integer(rsa1024.q), - OpenSSL::ASN1::Integer(rsa1024.dmp1), - OpenSSL::ASN1::Integer(rsa1024.dmq1), - OpenSSL::ASN1::Integer(rsa1024.iqmp) + OpenSSL::ASN1::Integer(rsa.n), + OpenSSL::ASN1::Integer(rsa.e), + OpenSSL::ASN1::Integer(rsa.d), + OpenSSL::ASN1::Integer(rsa.p), + OpenSSL::ASN1::Integer(rsa.q), + OpenSSL::ASN1::Integer(rsa.dmp1), + OpenSSL::ASN1::Integer(rsa.dmq1), + OpenSSL::ASN1::Integer(rsa.iqmp) ]) key = OpenSSL::PKey::RSA.new(asn1.to_der) assert_predicate key, :private? - assert_same_rsa rsa1024, key + assert_same_rsa rsa, key pem = <<~EOF -----BEGIN RSA PRIVATE KEY----- - MIICXgIBAAKBgQDLwsSw1ECnPtT+PkOgHhcGA71nwC2/nL85VBGnRqDxOqjVh7Cx - aKPERYHsk4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbCz0layNqHyywQEVLFmp1cpIt/ - Q3geLv8ZD9pihowKJDyMDiN6ArYUmZczvW4976MU3+l54E6lF/JfFEU5hwIDAQAB - AoGBAKSl/MQarye1yOysqX6P8fDFQt68VvtXkNmlSiKOGuzyho0M+UVSFcs6k1L0 - maDE25AMZUiGzuWHyaU55d7RXDgeskDMakD1v6ZejYtxJkSXbETOTLDwUWTn618T - gnb17tU1jktUtU67xK/08i/XodlgnQhs6VoHTuCh3Hu77O6RAkEA7+gxqBuZR572 - 74/akiW/SuXm0SXPEviyO1MuSRwtI87B02D0qgV8D1UHRm4AhMnJ8MCs1809kMQE - JiQUCrp9mQJBANlt2ngBO14us6NnhuAseFDTBzCHXwUUu1YKHpMMmxpnGqaldGgX - sOZB3lgJsT9VlGf3YGYdkLTNVbogQKlKpB8CQQDiSwkb4vyQfDe8/NpU5Not0fII - 8jsDUCb+opWUTMmfbxWRR3FBNu8wnym/m19N4fFj8LqYzHX4KY0oVPu6qvJxAkEA - wa5snNekFcqONLIE4G5cosrIrb74sqL8GbGb+KuTAprzj5z1K8Bm0UW9lTjVDjDi - qRYgZfZSL+x1P/54+xTFSwJAY1FxA/N3QPCXCjPh5YqFxAMQs2VVYTfg+t0MEcJD - dPMQD5JX6g5HKnHFg2mZtoXQrWmJSn7p8GJK8yNTopEErA== + MIIEpAIBAAKCAQEAuV9ht9J7k4NBs38jOXvvTKY9gW8nLICSno5EETR1cuF7i4pN + s9I1QJGAFAX0BEO4KbzXmuOvfCpD3CU+Slp1enenfzq/t/e/1IRW0wkJUJUFQign + 4CtrkJL+P07yx18UjyPlBXb81ApEmAB5mrJVSrWmqbjs07JbuS4QQGGXLc+Su96D + kYKmSNVjBiLxVVSpyZfAY3hD37d60uG+X8xdW5v68JkRFIhdGlb6JL8fllf/A/bl + NwdJOhVr9mESHhwGjwfSeTDPfd8ZLE027E5lyAVX9KZYcU00mOX+fdxOSnGqS/8J + DRh0EPHDL15RcJjV2J6vZjPb0rOYGDoMcH+94wIDAQABAoIBAAzsamqfYQAqwXTb + I0CJtGg6msUgU7HVkOM+9d3hM2L791oGHV6xBAdpXW2H8LgvZHJ8eOeSghR8+dgq + PIqAffo4x1Oma+FOg3A0fb0evyiACyrOk+EcBdbBeLo/LcvahBtqnDfiUMQTpy6V + seSoFCwuN91TSCeGIsDpRjbG1vxZgtx+uI+oH5+ytqJOmfCksRDCkMglGkzyfcl0 + Xc5CUhIJ0my53xijEUQl19rtWdMnNnnkdbG8PT3LZlOta5Do86BElzUYka0C6dUc + VsBDQ0Nup0P6rEQgy7tephHoRlUGTYamsajGJaAo1F3IQVIrRSuagi7+YpSpCqsW + wORqorkCgYEA7RdX6MDVrbw7LePnhyuaqTiMK+055/R1TqhB1JvvxJ1CXk2rDL6G + 0TLHQ7oGofd5LYiemg4ZVtWdJe43BPZlVgT6lvL/iGo8JnrncB9Da6L7nrq/+Rvj + XGjf1qODCK+LmreZWEsaLPURIoR/Ewwxb9J2zd0CaMjeTwafJo1CZvcCgYEAyCgb + aqoWvUecX8VvARfuA593Lsi50t4MEArnOXXcd1RnXoZWhbx5rgO8/ATKfXr0BK/n + h2GF9PfKzHFm/4V6e82OL7gu/kLy2u9bXN74vOvWFL5NOrOKPM7Kg+9I131kNYOw + Ivnr/VtHE5s0dY7JChYWE1F3vArrOw3T00a4CXUCgYEA0SqY+dS2LvIzW4cHCe9k + IQqsT0yYm5TFsUEr4sA3xcPfe4cV8sZb9k/QEGYb1+SWWZ+AHPV3UW5fl8kTbSNb + v4ng8i8rVVQ0ANbJO9e5CUrepein2MPL0AkOATR8M7t7dGGpvYV0cFk8ZrFx0oId + U0PgYDotF/iueBWlbsOM430CgYEAqYI95dFyPI5/AiSkY5queeb8+mQH62sdcCCr + vd/w/CZA/K5sbAo4SoTj8dLk4evU6HtIa0DOP63y071eaxvRpTNqLUOgmLh+D6gS + Cc7TfLuFrD+WDBatBd5jZ+SoHccVrLR/4L8jeodo5FPW05A+9gnKXEXsTxY4LOUC + 9bS4e1kCgYAqVXZh63JsMwoaxCYmQ66eJojKa47VNrOeIZDZvd2BPVf30glBOT41 + gBoDG3WMPZoQj9pb7uMcrnvs4APj2FIhMU8U15LcPAj59cD6S6rWnAxO8NFK7HQG + 4Jxg3JNNf8ErQoCHb1B3oVdXJkmbJkARoDpBKmTCgKtP8ADYLmVPQw== -----END RSA PRIVATE KEY----- EOF key = OpenSSL::PKey::RSA.new(pem) - assert_same_rsa rsa1024, key + assert_same_rsa rsa, key - assert_equal asn1.to_der, rsa1024.to_der - assert_equal pem, rsa1024.export + assert_equal asn1.to_der, rsa.to_der + assert_equal pem, rsa.export # Unknown PEM prepended - cert = issue_cert(OpenSSL::X509::Name.new([["CN", "nobody"]]), rsa1024, 1, [], nil, nil) - str = cert.to_text + cert.to_pem + rsa1024.to_pem + cert = issue_cert(OpenSSL::X509::Name.new([["CN", "nobody"]]), rsa, 1, [], nil, nil) + str = cert.to_text + cert.to_pem + rsa.to_pem key = OpenSSL::PKey::RSA.new(str) - assert_same_rsa rsa1024, key + assert_same_rsa rsa, key end def test_RSAPrivateKey_encrypted + omit_on_fips + rsa1024 = Fixtures.pkey("rsa1024") # key = abcdef pem = <<~EOF @@ -438,6 +480,8 @@ def test_PUBKEY end def test_pem_passwd + omit_on_fips + key = Fixtures.pkey("rsa1024") pem3c = key.to_pem("aes-128-cbc", "key") assert_match (/ENCRYPTED/), pem3c @@ -484,41 +528,54 @@ def test_private_encoding end def test_private_encoding_encrypted - rsa1024 = Fixtures.pkey("rsa1024") - encoded = rsa1024.private_to_der("aes-128-cbc", "abcdef") + rsa = Fixtures.pkey("rsa2048") + encoded = rsa.private_to_der("aes-128-cbc", "abcdef") asn1 = OpenSSL::ASN1.decode(encoded) # PKCS #8 EncryptedPrivateKeyInfo assert_kind_of OpenSSL::ASN1::Sequence, asn1 assert_equal 2, asn1.value.size - assert_not_equal rsa1024.private_to_der, encoded - assert_same_rsa rsa1024, OpenSSL::PKey.read(encoded, "abcdef") - assert_same_rsa rsa1024, OpenSSL::PKey.read(encoded) { "abcdef" } + assert_not_equal rsa.private_to_der, encoded + assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdef") + assert_same_rsa rsa, OpenSSL::PKey.read(encoded) { "abcdef" } assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.read(encoded, "abcxyz") } - encoded = rsa1024.private_to_pem("aes-128-cbc", "abcdef") + encoded = rsa.private_to_pem("aes-128-cbc", "abcdef") assert_match (/BEGIN ENCRYPTED PRIVATE KEY/), encoded.lines[0] - assert_same_rsa rsa1024, OpenSSL::PKey.read(encoded, "abcdef") + assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdef") - # certtool --load-privkey=test/fixtures/pkey/rsa1024.pem --to-p8 --password=abcdef + # certtool --load-privkey=test/openssl/fixtures/pkey/rsa2048.pem --to-p8 --password=abcdef pem = <<~EOF -----BEGIN ENCRYPTED PRIVATE KEY----- - MIICojAcBgoqhkiG9w0BDAEDMA4ECLqajUdSNfzwAgIEkQSCAoCDWhxr1HUrKLXA - FsFGGQfPT0aKH4gZipaSXXQRl0KwifHwHoDtfo/mAkJVZMnUVOm1AQ4LTFS3EdTy - JUwICGEQHb7QAiokIRoi0K2yHhOxVO8qgbnWuisWpiT6Ru1jCqTs/wcqlqF7z2jM - oXDk/vuekKst1DDXDcHrzhDkwhCQWj6jt1r2Vwaryy0FyeqsWAgBDiK2LsnCgkGD - 21uhNZ/iWMG6tvY9hB8MDdiBJ41YdSG/AKLulAxQ1ibJz0Tasu66TmwFvWhBlME+ - QbqfgmkgWg5buu53SvDfCA47zXihclbtdfW+U3CJ9OJkx0535TVdZbuC1QgKXvG7 - 4iKGFRMWYJqZvZM3GL4xbC75AxjXZsdCfV81VjZxjeU6ung/NRzCuCUcmBOQzo1D - Vv6COwAa6ttQWM0Ti8oIQHdu5Qi+nuOEHDLxCxD962M37H99sEO5cESjmrGVxhEo - 373L4+11geGSCajdp0yiAGnXQfwaKta8cL693bRObN+b1Y+vqtDKH26N9a4R3qgg - 2XwgQ5GH5CODoXZpi0wxncXO+3YuuhGeArtzKSXLNxHzIMlY7wZX+0e9UU03zfV/ - aOe4/q5DpkNxgHePt0oEpamSKY5W3jzVi1dlFWsRjud1p/Grt2zjSWTYClBlJqG1 - A/3IeDZCu+acaePJjFyv5dFffIj2l4bAYB+LFrZlSu3F/EimO/dCDWJ9JGlMK0aF - l9brh7786Mo+YfyklaqMMEHBbbR2Es7PR6Gt7lrcIXmzy9XSsxT6IiD1rG9KKR3i - CQxTup6JAx9w1q+adL+Ypikoy3gGD/ccUY6TtPoCmkQwSCS+JqQnFlCiThDJbu+V - eqqUNkZq + MIIFOTBjBgkqhkiG9w0BBQ0wVjA1BgkqhkiG9w0BBQwwKAQSsTIsinrhNMr4owUz + cwYGgB0lAgMJJ8ACARAwCgYIKoZIhvcNAgkwHQYJYIZIAWUDBAECBBDtDYqmQOLV + Nh0T0DslWgovBIIE0ESbJey2Pjf9brTp9/41CPnI9Ev78CGSv8Ihyuynu6G7oj7N + G7jUB1pVMQ7ivebF5DmM0qHAix6fDqJetB3WCnRQpMLyIdq5VrnKwFNhwGYduWA5 + IyaAc4DHj02e6YLyBTIKpu79OSFxLrnLCRaTbvZIUQaGhyd6pB7iAhqz5YBC0rpa + iMK5TRlNGPYG9n2eGFOhvUsbJ4T8VDzjpVWw0VNRaukXtg4xiR6o1f0qSXqAb5d9 + REq5DfaQfoOKTV9j7KJHDRrBQG81vkU4K+xILrCBfbcYb82aCoinwSep9LC30HaH + LZ0hYQOuD/k/UbgjToS2wyMnkz75MN5ZNhDMZl/mACQdsMMtIxG37Mpo1Ca33uZi + 71TCOEKIblZS11L1YhIni9Af8pOuHJBWwezP2zN2nPwV6OhgL7Jlax7ICQOPC6L/ + yRGgC5eT4lDDAuTy0IdUhr0r5XrFzZR0/5Vgsq9cGfk9QkXOoETRhQVkEfUDdCs6 + 6CK+SwUR9qh5824ShODFG0SQpsqBPIVtkGrypBSUJtICmGMOAsclB7RDN7/opJwp + qv/iRJ5dhWrhRgQ/DfYifvO5On7RgC2hm48gF3Pt6XCA857ryyYxLYeMY42tAUqp + Hmc9HL7bMYF/jl3cJ32+gLvI3PBVvrvyeAhRo6z7MFVe9I04OywV6BHUx1Us6ybF + qkYnSpcJZdu7HyvzXm7XWLFmt7K5BlAgnFsa/8+cI1BGPgQRc1j0SWepXsSwFZX6 + JkNQ0dewq4uRJXbGyQgfh5I5ETpqDhSt2JfBwAoze6cx3DPC711PUamxyWMiejs+ + mYdia4p62NxaUvyXWmCGIEOzajRwywEhf9OLAmfqTN41TIrEL4BUxqtzDyw8Nl8T + KB7nJEC366jFASfumNQkXXyH5yBIF+XwwSKUOObRZVn2rUzFToo51hHu9efxHoXa + jZlpfglWijkmOuwoIGlGHOq8gUn76oq9WbV+YO+fWm/mf4S3ECzmYzxb6a1uCTy/ + Itkm2qOe3yTM1t+oCqZ0/MeTZ84ALQaWv5reQfjronPZ1jeNtxrYz28tJ4KwBn0U + bJReXbOLsHAymipncxlmaevbx4GPTduu/lbpxefoN95w+SpEdyTmVWrfaCTgAbad + EzcRl60my3xOMQ7CaUbRgGiwohqHDvuXzeqoZ96u6CwfAoEfy4jETmKLRH6uTtj7 + 4jdTyoqyizjpvaM8LPspBS+oqFwLxBjpseQuScrZO1BjPxrImLy2/VRqwJ+CF4FB + iijEgDgDc1EMIGe5YmOAV+i22n9RqX+2IvkYp7CWXrB9/lmirLFukd7hT8DLPUGq + AvSZwTPbDPoZKG3DAebC3DbiC7A3x0KZp24doNRLamZ/MyKHo2Rzl0UhkzDU0ly2 + eAnyNYsOAQck+C6L+ieD95Gksm9YJWurwttm5JragbIJwMCrsBQd4bXDkKdRhxS2 + JpS0dT/aoDmgTzoG07x4cZk0rjBkfX1ta0j0b1lz7/PZXl9AbRvFdq5sJpmv4Ryz + S+OERqo4IEfJJq2WJ92WR+HLGV3Gvsdb7znZTEF1tp4pWOLAt83Pry282UJxO7Pe + ySf/868TEmXams06GYvH+7cMiIT2m9Dc+EFgNaPmm0uMmJ+ZjqHKSOLzrL7C -----END ENCRYPTED PRIVATE KEY----- EOF - assert_same_rsa rsa1024, OpenSSL::PKey.read(pem, "abcdef") + assert_same_rsa rsa, OpenSSL::PKey.read(pem, "abcdef") end def test_dup From 63cbe3f6ac9feb44a2e43b1f853e2ca7e049316c Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 29 May 2024 16:46:04 +0200 Subject: [PATCH 05/14] Proof of Concept: Allow to prevent fork from happening in known fork unsafe API [Feature #20590] For better of for worse, fork(2) remain the primary provider of parallelism in Ruby programs. Even though it's frowned uppon in many circles, and a lot of literature will simply state that only async-signal safe APIs are safe to use after `fork()`, in practice most APIs work well as long as you are careful about not forking while another thread is holding a pthread mutex. One of the APIs that is known cause fork safety issues is `getaddrinfo`. If you fork while another thread is inside `getaddrinfo`, a mutex may be left locked in the child, with no way to unlock it. I think we could reduce the impact of these problem by preventing in for the most notorious and common cases, by locking around `fork(2)` and known unsafe APIs with a read-write lock. --- ext/socket/raddrinfo.c | 16 +++++++++++++-- internal/thread.h | 5 +++++ process.c | 5 +++++ thread_none.c | 6 ++++++ thread_pthread.c | 46 ++++++++++++++++++++++++++++++++++++++++++ thread_win32.c | 6 ++++++ 6 files changed, 82 insertions(+), 2 deletions(-) diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index 55bfe44a68a919..7ec700075eb227 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -327,6 +327,12 @@ nogvl_getaddrinfo(void *arg) return (void *)(VALUE)ret; } +static void * +fork_safe_getaddrinfo(void *arg) +{ + return rb_thread_prevent_fork(nogvl_getaddrinfo, arg); +} + static int rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai) { @@ -336,7 +342,7 @@ rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hint arg.service = portp; arg.hints = hints; arg.res = ai; - return (int)(VALUE)rb_thread_call_without_gvl(nogvl_getaddrinfo, &arg, RUBY_UBF_IO, 0); + return (int)(VALUE)rb_thread_call_without_gvl(fork_safe_getaddrinfo, &arg, RUBY_UBF_IO, 0); } #elif GETADDRINFO_IMPL == 2 @@ -477,6 +483,12 @@ do_pthread_create(pthread_t *th, void *(*start_routine) (void *), void *arg) return ret; } +static void * +fork_safe_do_getaddrinfo(void *ptr) +{ + return rb_thread_prevent_fork(do_getaddrinfo, ptr); +} + static int rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai) { @@ -493,7 +505,7 @@ rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hint } pthread_t th; - if (do_pthread_create(&th, do_getaddrinfo, arg) != 0) { + if (do_pthread_create(&th, fork_safe_do_getaddrinfo, arg) != 0) { int err = errno; free_getaddrinfo_arg(arg); errno = err; diff --git a/internal/thread.h b/internal/thread.h index 47273436e3bdbd..a2926febc3c20b 100644 --- a/internal/thread.h +++ b/internal/thread.h @@ -46,6 +46,9 @@ VALUE rb_thread_shield_wait(VALUE self); VALUE rb_thread_shield_release(VALUE self); VALUE rb_thread_shield_destroy(VALUE self); int rb_thread_to_be_killed(VALUE thread); +void rb_thread_acquire_fork_lock(void); +void rb_thread_release_fork_lock(void); +void rb_thread_reset_fork_lock(void); void rb_mutex_allow_trap(VALUE self, int val); VALUE rb_uninterruptible(VALUE (*b_proc)(VALUE), VALUE data); VALUE rb_mutex_owned_p(VALUE self); @@ -64,6 +67,8 @@ void rb_notify_fd_close_wait(struct rb_io_close_wait_list *busy); RUBY_SYMBOL_EXPORT_BEGIN +void *rb_thread_prevent_fork(void *(*func)(void *), void *data); /* for ext/socket/raddrinfo.c */ + /* Temporary. This API will be removed (renamed). */ VALUE rb_thread_io_blocking_region(rb_blocking_function_t *func, void *data1, int fd); VALUE rb_thread_io_blocking_call(rb_blocking_function_t *func, void *data1, int fd, int events); diff --git a/process.c b/process.c index acb5b661567095..88b55ed335cbf6 100644 --- a/process.c +++ b/process.c @@ -4227,12 +4227,17 @@ rb_fork_ruby(int *status) prefork(); before_fork_ruby(); + rb_thread_acquire_fork_lock(); disable_child_handler_before_fork(&old); child.pid = pid = rb_fork(); child.error = err = errno; disable_child_handler_fork_parent(&old); /* yes, bad name */ + rb_thread_release_fork_lock(); + if (pid == 0) { + rb_thread_reset_fork_lock(); + } after_fork_ruby(pid); /* repeat while fork failed but retryable */ diff --git a/thread_none.c b/thread_none.c index 38730df7ba1ff2..158260982123e9 100644 --- a/thread_none.c +++ b/thread_none.c @@ -326,4 +326,10 @@ rb_thread_lock_native_thread(void) return false; } +void * +rb_thread_prevent_fork(void *(*func)(void *), void *data) +{ + return func(data); +} + #endif /* THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION */ diff --git a/thread_pthread.c b/thread_pthread.c index 3ebb67aaa3ac17..3c7fba42d249cf 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -3346,6 +3346,52 @@ native_sleep(rb_thread_t *th, rb_hrtime_t *rel) RUBY_DEBUG_LOG("wakeup"); } +// fork read-write lock (only for pthread) +static pthread_rwlock_t rb_thread_fork_rw_lock = PTHREAD_RWLOCK_INITIALIZER; + +void +rb_thread_release_fork_lock(void) +{ + int r; + if ((r = pthread_rwlock_unlock(&rb_thread_fork_rw_lock))) { + rb_bug_errno("pthread_rwlock_unlock", r); + } +} + +void +rb_thread_reset_fork_lock(void) +{ + int r; + if ((r = pthread_rwlock_destroy(&rb_thread_fork_rw_lock))) { + rb_bug_errno("pthread_rwlock_destroy", r); + } + + if ((r = pthread_rwlock_init(&rb_thread_fork_rw_lock, NULL))) { + rb_bug_errno("pthread_rwlock_init", r); + } +} + +void * +rb_thread_prevent_fork(void *(*func)(void *), void *data) +{ + int r; + if ((r = pthread_rwlock_rdlock(&rb_thread_fork_rw_lock))) { + rb_bug_errno("pthread_rwlock_rdlock", r); + } + void *result = func(data); + rb_thread_release_fork_lock(); + return result; +} + +void +rb_thread_acquire_fork_lock(void) +{ + int r; + if ((r = pthread_rwlock_wrlock(&rb_thread_fork_rw_lock))) { + rb_bug_errno("pthread_rwlock_wrlock", r); + } +} + // thread internal event hooks (only for pthread) struct rb_internal_thread_event_hook { diff --git a/thread_win32.c b/thread_win32.c index 1e2d4bdd1927c1..3bca58cbac0090 100644 --- a/thread_win32.c +++ b/thread_win32.c @@ -1011,4 +1011,10 @@ rb_thread_lock_native_thread(void) return false; } +void * +rb_thread_prevent_fork(void *(*func)(void *), void *data) +{ + return func(data); +} + #endif /* THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION */ From a99707cd9c6a1d53cf8ebc883dc210219bd67a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 29 Jul 2024 12:15:02 +0200 Subject: [PATCH 06/14] Optimized instruction for Array#freeze If an Array which is empty or only using literals is frozen, we detect this as a peephole optimization and change the instructions to be `opt_ary_freeze`. [Feature #20684] Co-authored-by: Jean Boussier --- array.c | 4 + compile.c | 53 ++++- insns.def | 15 ++ internal/array.h | 1 + lib/ruby_vm/rjit/insn_compiler.rb | 19 ++ test/ruby/test_optimization.rb | 46 +++++ vm.c | 2 +- vm_insnhelper.c | 11 ++ yjit/src/codegen.rs | 18 ++ yjit/src/cruby_bindings.inc.rs | 314 +++++++++++++++--------------- 10 files changed, 324 insertions(+), 159 deletions(-) diff --git a/array.c b/array.c index 318449abb48292..91f75d915f79fb 100644 --- a/array.c +++ b/array.c @@ -38,6 +38,7 @@ #include "ruby_assert.h" VALUE rb_cArray; +VALUE rb_cArray_empty_frozen; /* Flags of RArray * @@ -8833,6 +8834,9 @@ Init_Array(void) rb_define_method(rb_cArray, "freeze", rb_ary_freeze, 0); rb_define_method(rb_cArray, "deconstruct", rb_ary_deconstruct, 0); + + rb_cArray_empty_frozen = rb_ary_freeze(rb_ary_new()); + rb_vm_register_global_object(rb_cArray_empty_frozen); } #include "array.rbinc" diff --git a/compile.c b/compile.c index cf75dce619d1bf..32bcbf69afd20f 100644 --- a/compile.c +++ b/compile.c @@ -3235,6 +3235,8 @@ ci_argc_set(const rb_iseq_t *iseq, const struct rb_callinfo *ci, int argc) return nci; } +#define vm_ci_simple(ci) (vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE) + static int iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcallopt) { @@ -3401,6 +3403,55 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal } } + /* + * duparray [...] + * send , nil + * => + * opt_ary_freeze [...], + */ + if (IS_INSN_ID(iobj, duparray)) { + LINK_ELEMENT *next = iobj->link.next; + if (IS_INSN(next) && (IS_INSN_ID(next, send))) { + const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT(next, 0); + const rb_iseq_t *blockiseq = (rb_iseq_t *)OPERAND_AT(next, 1); + + if (vm_ci_simple(ci) && vm_ci_argc(ci) == 0 && blockiseq == NULL && vm_ci_mid(ci) == idFreeze) { + VALUE ary = iobj->operands[0]; + rb_obj_reveal(ary, rb_cArray); + + iobj->insn_id = BIN(opt_ary_freeze); + iobj->operand_size = 2; + iobj->operands = compile_data_calloc2(iseq, iobj->operand_size, sizeof(VALUE)); + iobj->operands[0] = ary; + iobj->operands[1] = (VALUE)ci; + ELEM_REMOVE(next); + } + } + } + + /* + * newarray 0 + * send , nil + * => + * opt_ary_freeze [], + */ + if (IS_INSN_ID(iobj, newarray) && iobj->operands[0] == INT2FIX(0)) { + LINK_ELEMENT *next = iobj->link.next; + if (IS_INSN(next) && (IS_INSN_ID(next, send))) { + const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT(next, 0); + const rb_iseq_t *blockiseq = (rb_iseq_t *)OPERAND_AT(next, 1); + + if (vm_ci_simple(ci) && vm_ci_argc(ci) == 0 && blockiseq == NULL && vm_ci_mid(ci) == idFreeze) { + iobj->insn_id = BIN(opt_ary_freeze); + iobj->operand_size = 2; + iobj->operands = compile_data_calloc2(iseq, iobj->operand_size, sizeof(VALUE)); + iobj->operands[0] = rb_cArray_empty_frozen; + iobj->operands[1] = (VALUE)ci; + ELEM_REMOVE(next); + } + } + } + if (IS_INSN_ID(iobj, branchif) || IS_INSN_ID(iobj, branchnil) || IS_INSN_ID(iobj, branchunless)) { @@ -3986,8 +4037,6 @@ insn_set_specialized_instruction(rb_iseq_t *iseq, INSN *iobj, int insn_id) return COMPILE_OK; } -#define vm_ci_simple(ci) (vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE) - static int iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj) { diff --git a/insns.def b/insns.def index c8dcd61bec3f46..8a763d6187988a 100644 --- a/insns.def +++ b/insns.def @@ -919,6 +919,21 @@ objtostring } } +DEFINE_INSN +opt_ary_freeze +(VALUE ary, CALL_DATA cd) +() +(VALUE val) +{ + val = vm_opt_ary_freeze(ary, BOP_FREEZE, idFreeze); + + if (UNDEF_P(val)) { + RUBY_DTRACE_CREATE_HOOK(ARRAY, RARRAY_LEN(ary)); + PUSH(rb_ary_resurrect(ary)); + CALL_SIMPLE_METHOD(); + } +} + DEFINE_INSN opt_str_freeze (VALUE str, CALL_DATA cd) diff --git a/internal/array.h b/internal/array.h index 39f6fcbea64ed1..398676df4aa80d 100644 --- a/internal/array.h +++ b/internal/array.h @@ -37,6 +37,7 @@ size_t rb_ary_size_as_embedded(VALUE ary); void rb_ary_make_embedded(VALUE ary); bool rb_ary_embeddable_p(VALUE ary); VALUE rb_ary_diff(VALUE ary1, VALUE ary2); +RUBY_EXTERN VALUE rb_cArray_empty_frozen; static inline VALUE rb_ary_entry_internal(VALUE ary, long offset); static inline bool ARY_PTR_USING_P(VALUE ary); diff --git a/lib/ruby_vm/rjit/insn_compiler.rb b/lib/ruby_vm/rjit/insn_compiler.rb index 7a9a0f3aa5c48f..c6b886e192c45c 100644 --- a/lib/ruby_vm/rjit/insn_compiler.rb +++ b/lib/ruby_vm/rjit/insn_compiler.rb @@ -90,6 +90,7 @@ def compile(jit, ctx, asm, insn) when :opt_send_without_block then opt_send_without_block(jit, ctx, asm) when :objtostring then objtostring(jit, ctx, asm) when :opt_str_freeze then opt_str_freeze(jit, ctx, asm) + when :opt_ary_freeze then opt_ary_freeze(jit, ctx, asm) when :opt_nil_p then opt_nil_p(jit, ctx, asm) # opt_str_uminus when :opt_newarray_send then opt_newarray_send(jit, ctx, asm) @@ -1490,6 +1491,24 @@ def objtostring(jit, ctx, asm) end end + # @param jit [RubyVM::RJIT::JITState] + # @param ctx [RubyVM::RJIT::Context] + # @param asm [RubyVM::RJIT::Assembler] + def opt_ary_freeze(jit, ctx, asm) + unless Invariants.assume_bop_not_redefined(jit, C::ARRAY_REDEFINED_OP_FLAG, C::BOP_FREEZE) + return CantCompile; + end + + ary = jit.operand(0, ruby: true) + + # Push the return value onto the stack + stack_ret = ctx.stack_push(Type::CArray) + asm.mov(:rax, to_value(ary)) + asm.mov(stack_ret, :rax) + + KeepCompiling + end + # @param jit [RubyVM::RJIT::JITState] # @param ctx [RubyVM::RJIT::Context] # @param asm [RubyVM::RJIT::Assembler] diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index a7a0582dbb49c5..671f7d7ed5810b 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -749,6 +749,52 @@ def test_peephole_dstr end end + def test_peephole_array_freeze + code = "#{<<~'begin;'}\n#{<<~'end;'}" + begin; + [1].freeze + end; + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_ary_freeze/, insn) + assert_no_match(/duparray/, insn) + assert_no_match(/send/, insn) + assert_predicate([1].freeze, :frozen?) + assert_in_out_err([], <<~RUBY, [":ok"]) + class Array + prepend Module.new { + def freeze + :ok + end + } + end + p [1].freeze + RUBY + end + + def test_peephole_array_freeze_empty + code = "#{<<~'begin;'}\n#{<<~'end;'}" + begin; + [].freeze + end; + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_ary_freeze/, insn) + assert_no_match(/duparray/, insn) + assert_no_match(/send/, insn) + assert_predicate([].freeze, :frozen?) + assert_in_out_err([], <<~RUBY, [":ok"]) + class Array + prepend Module.new { + def freeze + :ok + end + } + end + p [].freeze + RUBY + end + def test_branch_condition_backquote bug = '[ruby-core:80740] [Bug #13444] redefined backquote should be called' class << self diff --git a/vm.c b/vm.c index b1e0780d9e5b5c..0f0c3c91b19ccf 100644 --- a/vm.c +++ b/vm.c @@ -2232,7 +2232,7 @@ vm_init_redefined_flag(void) OP(EmptyP, EMPTY_P), (C(Array), C(String), C(Hash)); OP(Succ, SUCC), (C(Integer), C(String)); OP(EqTilde, MATCH), (C(Regexp), C(String)); - OP(Freeze, FREEZE), (C(String)); + OP(Freeze, FREEZE), (C(String), C(Array)); OP(UMinus, UMINUS), (C(String)); OP(Max, MAX), (C(Array)); OP(Min, MIN), (C(Array)); diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 227bfa20047d48..bc133f4013a9e8 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -6109,6 +6109,17 @@ vm_objtostring(const rb_iseq_t *iseq, VALUE recv, CALL_DATA cd) return Qundef; } +static VALUE +vm_opt_ary_freeze(VALUE ary, int bop, ID id) +{ + if (BASIC_OP_UNREDEFINED_P(bop, ARRAY_REDEFINED_OP_FLAG)) { + return ary; + } + else { + return Qundef; + } +} + static VALUE vm_opt_str_freeze(VALUE str, int bop, ID id) { diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 493e61c28b55b4..08aaa60731319e 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -4187,6 +4187,23 @@ fn gen_opt_str_freeze( Some(KeepCompiling) } +fn gen_opt_ary_freeze( + jit: &mut JITState, + asm: &mut Assembler, +) -> Option { + if !assume_bop_not_redefined(jit, asm, ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) { + return None; + } + + let str = jit.get_arg(0); + + // Push the return value onto the stack + let stack_ret = asm.stack_push(Type::CArray); + asm.mov(stack_ret, str.into()); + + Some(KeepCompiling) +} + fn gen_opt_str_uminus( jit: &mut JITState, asm: &mut Assembler, @@ -10228,6 +10245,7 @@ fn get_gen_fn(opcode: VALUE) -> Option { YARVINSN_opt_gt => Some(gen_opt_gt), YARVINSN_opt_ge => Some(gen_opt_ge), YARVINSN_opt_mod => Some(gen_opt_mod), + YARVINSN_opt_ary_freeze => Some(gen_opt_ary_freeze), YARVINSN_opt_str_freeze => Some(gen_opt_str_freeze), YARVINSN_opt_str_uminus => Some(gen_opt_str_uminus), YARVINSN_opt_newarray_send => Some(gen_opt_newarray_send), diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 4720b4f4ea76c2..7492a28f22ed09 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -783,162 +783,164 @@ pub const YARVINSN_send: ruby_vminsn_type = 55; pub const YARVINSN_sendforward: ruby_vminsn_type = 56; pub const YARVINSN_opt_send_without_block: ruby_vminsn_type = 57; pub const YARVINSN_objtostring: ruby_vminsn_type = 58; -pub const YARVINSN_opt_str_freeze: ruby_vminsn_type = 59; -pub const YARVINSN_opt_nil_p: ruby_vminsn_type = 60; -pub const YARVINSN_opt_str_uminus: ruby_vminsn_type = 61; -pub const YARVINSN_opt_newarray_send: ruby_vminsn_type = 62; -pub const YARVINSN_invokesuper: ruby_vminsn_type = 63; -pub const YARVINSN_invokesuperforward: ruby_vminsn_type = 64; -pub const YARVINSN_invokeblock: ruby_vminsn_type = 65; -pub const YARVINSN_leave: ruby_vminsn_type = 66; -pub const YARVINSN_throw: ruby_vminsn_type = 67; -pub const YARVINSN_jump: ruby_vminsn_type = 68; -pub const YARVINSN_branchif: ruby_vminsn_type = 69; -pub const YARVINSN_branchunless: ruby_vminsn_type = 70; -pub const YARVINSN_branchnil: ruby_vminsn_type = 71; -pub const YARVINSN_once: ruby_vminsn_type = 72; -pub const YARVINSN_opt_case_dispatch: ruby_vminsn_type = 73; -pub const YARVINSN_opt_plus: ruby_vminsn_type = 74; -pub const YARVINSN_opt_minus: ruby_vminsn_type = 75; -pub const YARVINSN_opt_mult: ruby_vminsn_type = 76; -pub const YARVINSN_opt_div: ruby_vminsn_type = 77; -pub const YARVINSN_opt_mod: ruby_vminsn_type = 78; -pub const YARVINSN_opt_eq: ruby_vminsn_type = 79; -pub const YARVINSN_opt_neq: ruby_vminsn_type = 80; -pub const YARVINSN_opt_lt: ruby_vminsn_type = 81; -pub const YARVINSN_opt_le: ruby_vminsn_type = 82; -pub const YARVINSN_opt_gt: ruby_vminsn_type = 83; -pub const YARVINSN_opt_ge: ruby_vminsn_type = 84; -pub const YARVINSN_opt_ltlt: ruby_vminsn_type = 85; -pub const YARVINSN_opt_and: ruby_vminsn_type = 86; -pub const YARVINSN_opt_or: ruby_vminsn_type = 87; -pub const YARVINSN_opt_aref: ruby_vminsn_type = 88; -pub const YARVINSN_opt_aset: ruby_vminsn_type = 89; -pub const YARVINSN_opt_aset_with: ruby_vminsn_type = 90; -pub const YARVINSN_opt_aref_with: ruby_vminsn_type = 91; -pub const YARVINSN_opt_length: ruby_vminsn_type = 92; -pub const YARVINSN_opt_size: ruby_vminsn_type = 93; -pub const YARVINSN_opt_empty_p: ruby_vminsn_type = 94; -pub const YARVINSN_opt_succ: ruby_vminsn_type = 95; -pub const YARVINSN_opt_not: ruby_vminsn_type = 96; -pub const YARVINSN_opt_regexpmatch2: ruby_vminsn_type = 97; -pub const YARVINSN_invokebuiltin: ruby_vminsn_type = 98; -pub const YARVINSN_opt_invokebuiltin_delegate: ruby_vminsn_type = 99; -pub const YARVINSN_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 100; -pub const YARVINSN_getlocal_WC_0: ruby_vminsn_type = 101; -pub const YARVINSN_getlocal_WC_1: ruby_vminsn_type = 102; -pub const YARVINSN_setlocal_WC_0: ruby_vminsn_type = 103; -pub const YARVINSN_setlocal_WC_1: ruby_vminsn_type = 104; -pub const YARVINSN_putobject_INT2FIX_0_: ruby_vminsn_type = 105; -pub const YARVINSN_putobject_INT2FIX_1_: ruby_vminsn_type = 106; -pub const YARVINSN_trace_nop: ruby_vminsn_type = 107; -pub const YARVINSN_trace_getlocal: ruby_vminsn_type = 108; -pub const YARVINSN_trace_setlocal: ruby_vminsn_type = 109; -pub const YARVINSN_trace_getblockparam: ruby_vminsn_type = 110; -pub const YARVINSN_trace_setblockparam: ruby_vminsn_type = 111; -pub const YARVINSN_trace_getblockparamproxy: ruby_vminsn_type = 112; -pub const YARVINSN_trace_getspecial: ruby_vminsn_type = 113; -pub const YARVINSN_trace_setspecial: ruby_vminsn_type = 114; -pub const YARVINSN_trace_getinstancevariable: ruby_vminsn_type = 115; -pub const YARVINSN_trace_setinstancevariable: ruby_vminsn_type = 116; -pub const YARVINSN_trace_getclassvariable: ruby_vminsn_type = 117; -pub const YARVINSN_trace_setclassvariable: ruby_vminsn_type = 118; -pub const YARVINSN_trace_opt_getconstant_path: ruby_vminsn_type = 119; -pub const YARVINSN_trace_getconstant: ruby_vminsn_type = 120; -pub const YARVINSN_trace_setconstant: ruby_vminsn_type = 121; -pub const YARVINSN_trace_getglobal: ruby_vminsn_type = 122; -pub const YARVINSN_trace_setglobal: ruby_vminsn_type = 123; -pub const YARVINSN_trace_putnil: ruby_vminsn_type = 124; -pub const YARVINSN_trace_putself: ruby_vminsn_type = 125; -pub const YARVINSN_trace_putobject: ruby_vminsn_type = 126; -pub const YARVINSN_trace_putspecialobject: ruby_vminsn_type = 127; -pub const YARVINSN_trace_putstring: ruby_vminsn_type = 128; -pub const YARVINSN_trace_putchilledstring: ruby_vminsn_type = 129; -pub const YARVINSN_trace_concatstrings: ruby_vminsn_type = 130; -pub const YARVINSN_trace_anytostring: ruby_vminsn_type = 131; -pub const YARVINSN_trace_toregexp: ruby_vminsn_type = 132; -pub const YARVINSN_trace_intern: ruby_vminsn_type = 133; -pub const YARVINSN_trace_newarray: ruby_vminsn_type = 134; -pub const YARVINSN_trace_pushtoarraykwsplat: ruby_vminsn_type = 135; -pub const YARVINSN_trace_duparray: ruby_vminsn_type = 136; -pub const YARVINSN_trace_duphash: ruby_vminsn_type = 137; -pub const YARVINSN_trace_expandarray: ruby_vminsn_type = 138; -pub const YARVINSN_trace_concatarray: ruby_vminsn_type = 139; -pub const YARVINSN_trace_concattoarray: ruby_vminsn_type = 140; -pub const YARVINSN_trace_pushtoarray: ruby_vminsn_type = 141; -pub const YARVINSN_trace_splatarray: ruby_vminsn_type = 142; -pub const YARVINSN_trace_splatkw: ruby_vminsn_type = 143; -pub const YARVINSN_trace_newhash: ruby_vminsn_type = 144; -pub const YARVINSN_trace_newrange: ruby_vminsn_type = 145; -pub const YARVINSN_trace_pop: ruby_vminsn_type = 146; -pub const YARVINSN_trace_dup: ruby_vminsn_type = 147; -pub const YARVINSN_trace_dupn: ruby_vminsn_type = 148; -pub const YARVINSN_trace_swap: ruby_vminsn_type = 149; -pub const YARVINSN_trace_opt_reverse: ruby_vminsn_type = 150; -pub const YARVINSN_trace_topn: ruby_vminsn_type = 151; -pub const YARVINSN_trace_setn: ruby_vminsn_type = 152; -pub const YARVINSN_trace_adjuststack: ruby_vminsn_type = 153; -pub const YARVINSN_trace_defined: ruby_vminsn_type = 154; -pub const YARVINSN_trace_definedivar: ruby_vminsn_type = 155; -pub const YARVINSN_trace_checkmatch: ruby_vminsn_type = 156; -pub const YARVINSN_trace_checkkeyword: ruby_vminsn_type = 157; -pub const YARVINSN_trace_checktype: ruby_vminsn_type = 158; -pub const YARVINSN_trace_defineclass: ruby_vminsn_type = 159; -pub const YARVINSN_trace_definemethod: ruby_vminsn_type = 160; -pub const YARVINSN_trace_definesmethod: ruby_vminsn_type = 161; -pub const YARVINSN_trace_send: ruby_vminsn_type = 162; -pub const YARVINSN_trace_sendforward: ruby_vminsn_type = 163; -pub const YARVINSN_trace_opt_send_without_block: ruby_vminsn_type = 164; -pub const YARVINSN_trace_objtostring: ruby_vminsn_type = 165; -pub const YARVINSN_trace_opt_str_freeze: ruby_vminsn_type = 166; -pub const YARVINSN_trace_opt_nil_p: ruby_vminsn_type = 167; -pub const YARVINSN_trace_opt_str_uminus: ruby_vminsn_type = 168; -pub const YARVINSN_trace_opt_newarray_send: ruby_vminsn_type = 169; -pub const YARVINSN_trace_invokesuper: ruby_vminsn_type = 170; -pub const YARVINSN_trace_invokesuperforward: ruby_vminsn_type = 171; -pub const YARVINSN_trace_invokeblock: ruby_vminsn_type = 172; -pub const YARVINSN_trace_leave: ruby_vminsn_type = 173; -pub const YARVINSN_trace_throw: ruby_vminsn_type = 174; -pub const YARVINSN_trace_jump: ruby_vminsn_type = 175; -pub const YARVINSN_trace_branchif: ruby_vminsn_type = 176; -pub const YARVINSN_trace_branchunless: ruby_vminsn_type = 177; -pub const YARVINSN_trace_branchnil: ruby_vminsn_type = 178; -pub const YARVINSN_trace_once: ruby_vminsn_type = 179; -pub const YARVINSN_trace_opt_case_dispatch: ruby_vminsn_type = 180; -pub const YARVINSN_trace_opt_plus: ruby_vminsn_type = 181; -pub const YARVINSN_trace_opt_minus: ruby_vminsn_type = 182; -pub const YARVINSN_trace_opt_mult: ruby_vminsn_type = 183; -pub const YARVINSN_trace_opt_div: ruby_vminsn_type = 184; -pub const YARVINSN_trace_opt_mod: ruby_vminsn_type = 185; -pub const YARVINSN_trace_opt_eq: ruby_vminsn_type = 186; -pub const YARVINSN_trace_opt_neq: ruby_vminsn_type = 187; -pub const YARVINSN_trace_opt_lt: ruby_vminsn_type = 188; -pub const YARVINSN_trace_opt_le: ruby_vminsn_type = 189; -pub const YARVINSN_trace_opt_gt: ruby_vminsn_type = 190; -pub const YARVINSN_trace_opt_ge: ruby_vminsn_type = 191; -pub const YARVINSN_trace_opt_ltlt: ruby_vminsn_type = 192; -pub const YARVINSN_trace_opt_and: ruby_vminsn_type = 193; -pub const YARVINSN_trace_opt_or: ruby_vminsn_type = 194; -pub const YARVINSN_trace_opt_aref: ruby_vminsn_type = 195; -pub const YARVINSN_trace_opt_aset: ruby_vminsn_type = 196; -pub const YARVINSN_trace_opt_aset_with: ruby_vminsn_type = 197; -pub const YARVINSN_trace_opt_aref_with: ruby_vminsn_type = 198; -pub const YARVINSN_trace_opt_length: ruby_vminsn_type = 199; -pub const YARVINSN_trace_opt_size: ruby_vminsn_type = 200; -pub const YARVINSN_trace_opt_empty_p: ruby_vminsn_type = 201; -pub const YARVINSN_trace_opt_succ: ruby_vminsn_type = 202; -pub const YARVINSN_trace_opt_not: ruby_vminsn_type = 203; -pub const YARVINSN_trace_opt_regexpmatch2: ruby_vminsn_type = 204; -pub const YARVINSN_trace_invokebuiltin: ruby_vminsn_type = 205; -pub const YARVINSN_trace_opt_invokebuiltin_delegate: ruby_vminsn_type = 206; -pub const YARVINSN_trace_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 207; -pub const YARVINSN_trace_getlocal_WC_0: ruby_vminsn_type = 208; -pub const YARVINSN_trace_getlocal_WC_1: ruby_vminsn_type = 209; -pub const YARVINSN_trace_setlocal_WC_0: ruby_vminsn_type = 210; -pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 211; -pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 212; -pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 213; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 214; +pub const YARVINSN_opt_ary_freeze: ruby_vminsn_type = 59; +pub const YARVINSN_opt_str_freeze: ruby_vminsn_type = 60; +pub const YARVINSN_opt_nil_p: ruby_vminsn_type = 61; +pub const YARVINSN_opt_str_uminus: ruby_vminsn_type = 62; +pub const YARVINSN_opt_newarray_send: ruby_vminsn_type = 63; +pub const YARVINSN_invokesuper: ruby_vminsn_type = 64; +pub const YARVINSN_invokesuperforward: ruby_vminsn_type = 65; +pub const YARVINSN_invokeblock: ruby_vminsn_type = 66; +pub const YARVINSN_leave: ruby_vminsn_type = 67; +pub const YARVINSN_throw: ruby_vminsn_type = 68; +pub const YARVINSN_jump: ruby_vminsn_type = 69; +pub const YARVINSN_branchif: ruby_vminsn_type = 70; +pub const YARVINSN_branchunless: ruby_vminsn_type = 71; +pub const YARVINSN_branchnil: ruby_vminsn_type = 72; +pub const YARVINSN_once: ruby_vminsn_type = 73; +pub const YARVINSN_opt_case_dispatch: ruby_vminsn_type = 74; +pub const YARVINSN_opt_plus: ruby_vminsn_type = 75; +pub const YARVINSN_opt_minus: ruby_vminsn_type = 76; +pub const YARVINSN_opt_mult: ruby_vminsn_type = 77; +pub const YARVINSN_opt_div: ruby_vminsn_type = 78; +pub const YARVINSN_opt_mod: ruby_vminsn_type = 79; +pub const YARVINSN_opt_eq: ruby_vminsn_type = 80; +pub const YARVINSN_opt_neq: ruby_vminsn_type = 81; +pub const YARVINSN_opt_lt: ruby_vminsn_type = 82; +pub const YARVINSN_opt_le: ruby_vminsn_type = 83; +pub const YARVINSN_opt_gt: ruby_vminsn_type = 84; +pub const YARVINSN_opt_ge: ruby_vminsn_type = 85; +pub const YARVINSN_opt_ltlt: ruby_vminsn_type = 86; +pub const YARVINSN_opt_and: ruby_vminsn_type = 87; +pub const YARVINSN_opt_or: ruby_vminsn_type = 88; +pub const YARVINSN_opt_aref: ruby_vminsn_type = 89; +pub const YARVINSN_opt_aset: ruby_vminsn_type = 90; +pub const YARVINSN_opt_aset_with: ruby_vminsn_type = 91; +pub const YARVINSN_opt_aref_with: ruby_vminsn_type = 92; +pub const YARVINSN_opt_length: ruby_vminsn_type = 93; +pub const YARVINSN_opt_size: ruby_vminsn_type = 94; +pub const YARVINSN_opt_empty_p: ruby_vminsn_type = 95; +pub const YARVINSN_opt_succ: ruby_vminsn_type = 96; +pub const YARVINSN_opt_not: ruby_vminsn_type = 97; +pub const YARVINSN_opt_regexpmatch2: ruby_vminsn_type = 98; +pub const YARVINSN_invokebuiltin: ruby_vminsn_type = 99; +pub const YARVINSN_opt_invokebuiltin_delegate: ruby_vminsn_type = 100; +pub const YARVINSN_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 101; +pub const YARVINSN_getlocal_WC_0: ruby_vminsn_type = 102; +pub const YARVINSN_getlocal_WC_1: ruby_vminsn_type = 103; +pub const YARVINSN_setlocal_WC_0: ruby_vminsn_type = 104; +pub const YARVINSN_setlocal_WC_1: ruby_vminsn_type = 105; +pub const YARVINSN_putobject_INT2FIX_0_: ruby_vminsn_type = 106; +pub const YARVINSN_putobject_INT2FIX_1_: ruby_vminsn_type = 107; +pub const YARVINSN_trace_nop: ruby_vminsn_type = 108; +pub const YARVINSN_trace_getlocal: ruby_vminsn_type = 109; +pub const YARVINSN_trace_setlocal: ruby_vminsn_type = 110; +pub const YARVINSN_trace_getblockparam: ruby_vminsn_type = 111; +pub const YARVINSN_trace_setblockparam: ruby_vminsn_type = 112; +pub const YARVINSN_trace_getblockparamproxy: ruby_vminsn_type = 113; +pub const YARVINSN_trace_getspecial: ruby_vminsn_type = 114; +pub const YARVINSN_trace_setspecial: ruby_vminsn_type = 115; +pub const YARVINSN_trace_getinstancevariable: ruby_vminsn_type = 116; +pub const YARVINSN_trace_setinstancevariable: ruby_vminsn_type = 117; +pub const YARVINSN_trace_getclassvariable: ruby_vminsn_type = 118; +pub const YARVINSN_trace_setclassvariable: ruby_vminsn_type = 119; +pub const YARVINSN_trace_opt_getconstant_path: ruby_vminsn_type = 120; +pub const YARVINSN_trace_getconstant: ruby_vminsn_type = 121; +pub const YARVINSN_trace_setconstant: ruby_vminsn_type = 122; +pub const YARVINSN_trace_getglobal: ruby_vminsn_type = 123; +pub const YARVINSN_trace_setglobal: ruby_vminsn_type = 124; +pub const YARVINSN_trace_putnil: ruby_vminsn_type = 125; +pub const YARVINSN_trace_putself: ruby_vminsn_type = 126; +pub const YARVINSN_trace_putobject: ruby_vminsn_type = 127; +pub const YARVINSN_trace_putspecialobject: ruby_vminsn_type = 128; +pub const YARVINSN_trace_putstring: ruby_vminsn_type = 129; +pub const YARVINSN_trace_putchilledstring: ruby_vminsn_type = 130; +pub const YARVINSN_trace_concatstrings: ruby_vminsn_type = 131; +pub const YARVINSN_trace_anytostring: ruby_vminsn_type = 132; +pub const YARVINSN_trace_toregexp: ruby_vminsn_type = 133; +pub const YARVINSN_trace_intern: ruby_vminsn_type = 134; +pub const YARVINSN_trace_newarray: ruby_vminsn_type = 135; +pub const YARVINSN_trace_pushtoarraykwsplat: ruby_vminsn_type = 136; +pub const YARVINSN_trace_duparray: ruby_vminsn_type = 137; +pub const YARVINSN_trace_duphash: ruby_vminsn_type = 138; +pub const YARVINSN_trace_expandarray: ruby_vminsn_type = 139; +pub const YARVINSN_trace_concatarray: ruby_vminsn_type = 140; +pub const YARVINSN_trace_concattoarray: ruby_vminsn_type = 141; +pub const YARVINSN_trace_pushtoarray: ruby_vminsn_type = 142; +pub const YARVINSN_trace_splatarray: ruby_vminsn_type = 143; +pub const YARVINSN_trace_splatkw: ruby_vminsn_type = 144; +pub const YARVINSN_trace_newhash: ruby_vminsn_type = 145; +pub const YARVINSN_trace_newrange: ruby_vminsn_type = 146; +pub const YARVINSN_trace_pop: ruby_vminsn_type = 147; +pub const YARVINSN_trace_dup: ruby_vminsn_type = 148; +pub const YARVINSN_trace_dupn: ruby_vminsn_type = 149; +pub const YARVINSN_trace_swap: ruby_vminsn_type = 150; +pub const YARVINSN_trace_opt_reverse: ruby_vminsn_type = 151; +pub const YARVINSN_trace_topn: ruby_vminsn_type = 152; +pub const YARVINSN_trace_setn: ruby_vminsn_type = 153; +pub const YARVINSN_trace_adjuststack: ruby_vminsn_type = 154; +pub const YARVINSN_trace_defined: ruby_vminsn_type = 155; +pub const YARVINSN_trace_definedivar: ruby_vminsn_type = 156; +pub const YARVINSN_trace_checkmatch: ruby_vminsn_type = 157; +pub const YARVINSN_trace_checkkeyword: ruby_vminsn_type = 158; +pub const YARVINSN_trace_checktype: ruby_vminsn_type = 159; +pub const YARVINSN_trace_defineclass: ruby_vminsn_type = 160; +pub const YARVINSN_trace_definemethod: ruby_vminsn_type = 161; +pub const YARVINSN_trace_definesmethod: ruby_vminsn_type = 162; +pub const YARVINSN_trace_send: ruby_vminsn_type = 163; +pub const YARVINSN_trace_sendforward: ruby_vminsn_type = 164; +pub const YARVINSN_trace_opt_send_without_block: ruby_vminsn_type = 165; +pub const YARVINSN_trace_objtostring: ruby_vminsn_type = 166; +pub const YARVINSN_trace_opt_ary_freeze: ruby_vminsn_type = 167; +pub const YARVINSN_trace_opt_str_freeze: ruby_vminsn_type = 168; +pub const YARVINSN_trace_opt_nil_p: ruby_vminsn_type = 169; +pub const YARVINSN_trace_opt_str_uminus: ruby_vminsn_type = 170; +pub const YARVINSN_trace_opt_newarray_send: ruby_vminsn_type = 171; +pub const YARVINSN_trace_invokesuper: ruby_vminsn_type = 172; +pub const YARVINSN_trace_invokesuperforward: ruby_vminsn_type = 173; +pub const YARVINSN_trace_invokeblock: ruby_vminsn_type = 174; +pub const YARVINSN_trace_leave: ruby_vminsn_type = 175; +pub const YARVINSN_trace_throw: ruby_vminsn_type = 176; +pub const YARVINSN_trace_jump: ruby_vminsn_type = 177; +pub const YARVINSN_trace_branchif: ruby_vminsn_type = 178; +pub const YARVINSN_trace_branchunless: ruby_vminsn_type = 179; +pub const YARVINSN_trace_branchnil: ruby_vminsn_type = 180; +pub const YARVINSN_trace_once: ruby_vminsn_type = 181; +pub const YARVINSN_trace_opt_case_dispatch: ruby_vminsn_type = 182; +pub const YARVINSN_trace_opt_plus: ruby_vminsn_type = 183; +pub const YARVINSN_trace_opt_minus: ruby_vminsn_type = 184; +pub const YARVINSN_trace_opt_mult: ruby_vminsn_type = 185; +pub const YARVINSN_trace_opt_div: ruby_vminsn_type = 186; +pub const YARVINSN_trace_opt_mod: ruby_vminsn_type = 187; +pub const YARVINSN_trace_opt_eq: ruby_vminsn_type = 188; +pub const YARVINSN_trace_opt_neq: ruby_vminsn_type = 189; +pub const YARVINSN_trace_opt_lt: ruby_vminsn_type = 190; +pub const YARVINSN_trace_opt_le: ruby_vminsn_type = 191; +pub const YARVINSN_trace_opt_gt: ruby_vminsn_type = 192; +pub const YARVINSN_trace_opt_ge: ruby_vminsn_type = 193; +pub const YARVINSN_trace_opt_ltlt: ruby_vminsn_type = 194; +pub const YARVINSN_trace_opt_and: ruby_vminsn_type = 195; +pub const YARVINSN_trace_opt_or: ruby_vminsn_type = 196; +pub const YARVINSN_trace_opt_aref: ruby_vminsn_type = 197; +pub const YARVINSN_trace_opt_aset: ruby_vminsn_type = 198; +pub const YARVINSN_trace_opt_aset_with: ruby_vminsn_type = 199; +pub const YARVINSN_trace_opt_aref_with: ruby_vminsn_type = 200; +pub const YARVINSN_trace_opt_length: ruby_vminsn_type = 201; +pub const YARVINSN_trace_opt_size: ruby_vminsn_type = 202; +pub const YARVINSN_trace_opt_empty_p: ruby_vminsn_type = 203; +pub const YARVINSN_trace_opt_succ: ruby_vminsn_type = 204; +pub const YARVINSN_trace_opt_not: ruby_vminsn_type = 205; +pub const YARVINSN_trace_opt_regexpmatch2: ruby_vminsn_type = 206; +pub const YARVINSN_trace_invokebuiltin: ruby_vminsn_type = 207; +pub const YARVINSN_trace_opt_invokebuiltin_delegate: ruby_vminsn_type = 208; +pub const YARVINSN_trace_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 209; +pub const YARVINSN_trace_getlocal_WC_0: ruby_vminsn_type = 210; +pub const YARVINSN_trace_getlocal_WC_1: ruby_vminsn_type = 211; +pub const YARVINSN_trace_setlocal_WC_0: ruby_vminsn_type = 212; +pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 213; +pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 214; +pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 215; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 216; pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), From bf9879791af11a20e50921551220c08d1c7f7f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 5 Aug 2024 12:31:24 +0200 Subject: [PATCH 07/14] Optimized instruction for Hash#freeze If a Hash which is empty or only using literals is frozen, we detect this as a peephole optimization and change the instructions to be `opt_hash_freeze`. [Feature #20684] Co-authored-by: Jean Boussier --- compile.c | 49 +++++ hash.c | 6 + insns.def | 15 ++ internal/hash.h | 1 + lib/ruby_vm/rjit/insn_compiler.rb | 19 ++ test/ruby/test_optimization.rb | 46 +++++ vm.c | 2 +- vm_insnhelper.c | 11 ++ yjit/src/codegen.rs | 18 ++ yjit/src/cruby_bindings.inc.rs | 316 +++++++++++++++--------------- 10 files changed, 325 insertions(+), 158 deletions(-) diff --git a/compile.c b/compile.c index 32bcbf69afd20f..3be7808a8d311e 100644 --- a/compile.c +++ b/compile.c @@ -3429,6 +3429,32 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal } } + /* + * duphash {...} + * send , nil + * => + * opt_hash_freeze {...}, + */ + if (IS_INSN_ID(iobj, duphash)) { + LINK_ELEMENT *next = iobj->link.next; + if (IS_INSN(next) && (IS_INSN_ID(next, send))) { + const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT(next, 0); + const rb_iseq_t *blockiseq = (rb_iseq_t *)OPERAND_AT(next, 1); + + if (vm_ci_simple(ci) && vm_ci_argc(ci) == 0 && blockiseq == NULL && vm_ci_mid(ci) == idFreeze) { + VALUE hash = iobj->operands[0]; + rb_obj_reveal(hash, rb_cHash); + + iobj->insn_id = BIN(opt_hash_freeze); + iobj->operand_size = 2; + iobj->operands = compile_data_calloc2(iseq, iobj->operand_size, sizeof(VALUE)); + iobj->operands[0] = hash; + iobj->operands[1] = (VALUE)ci; + ELEM_REMOVE(next); + } + } + } + /* * newarray 0 * send , nil @@ -3452,6 +3478,29 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal } } + /* + * newhash 0 + * send , nil + * => + * opt_hash_freeze {}, + */ + if (IS_INSN_ID(iobj, newhash) && iobj->operands[0] == INT2FIX(0)) { + LINK_ELEMENT *next = iobj->link.next; + if (IS_INSN(next) && (IS_INSN_ID(next, send))) { + const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT(next, 0); + const rb_iseq_t *blockiseq = (rb_iseq_t *)OPERAND_AT(next, 1); + + if (vm_ci_simple(ci) && vm_ci_argc(ci) == 0 && blockiseq == NULL && vm_ci_mid(ci) == idFreeze) { + iobj->insn_id = BIN(opt_hash_freeze); + iobj->operand_size = 2; + iobj->operands = compile_data_calloc2(iseq, iobj->operand_size, sizeof(VALUE)); + iobj->operands[0] = rb_cHash_empty_frozen; + iobj->operands[1] = (VALUE)ci; + ELEM_REMOVE(next); + } + } + } + if (IS_INSN_ID(iobj, branchif) || IS_INSN_ID(iobj, branchnil) || IS_INSN_ID(iobj, branchunless)) { diff --git a/hash.c b/hash.c index c6b65c095c29eb..14086bcb6454e8 100644 --- a/hash.c +++ b/hash.c @@ -103,6 +103,7 @@ static VALUE rb_hash_s_try_convert(VALUE, VALUE); * 2. Insert WBs */ +/* :nodoc: */ VALUE rb_hash_freeze(VALUE hash) { @@ -110,6 +111,7 @@ rb_hash_freeze(VALUE hash) } VALUE rb_cHash; +VALUE rb_cHash_empty_frozen; static VALUE envtbl; static ID id_hash, id_flatten_bang; @@ -7118,6 +7120,7 @@ Init_Hash(void) rb_define_singleton_method(rb_cHash, "try_convert", rb_hash_s_try_convert, 1); rb_define_method(rb_cHash, "initialize_copy", rb_hash_replace, 1); rb_define_method(rb_cHash, "rehash", rb_hash_rehash, 0); + rb_define_method(rb_cHash, "freeze", rb_hash_freeze, 0); rb_define_method(rb_cHash, "to_hash", rb_hash_to_hash, 0); rb_define_method(rb_cHash, "to_h", rb_hash_to_h, 0); @@ -7204,6 +7207,9 @@ Init_Hash(void) rb_define_singleton_method(rb_cHash, "ruby2_keywords_hash?", rb_hash_s_ruby2_keywords_hash_p, 1); rb_define_singleton_method(rb_cHash, "ruby2_keywords_hash", rb_hash_s_ruby2_keywords_hash, 1); + rb_cHash_empty_frozen = rb_hash_freeze(rb_hash_new()); + rb_vm_register_global_object(rb_cHash_empty_frozen); + /* Document-class: ENV * * +ENV+ is a hash-like accessor for environment variables. diff --git a/insns.def b/insns.def index 8a763d6187988a..99ea95f5842eb4 100644 --- a/insns.def +++ b/insns.def @@ -934,6 +934,21 @@ opt_ary_freeze } } +DEFINE_INSN +opt_hash_freeze +(VALUE hash, CALL_DATA cd) +() +(VALUE val) +{ + val = vm_opt_hash_freeze(hash, BOP_FREEZE, idFreeze); + + if (UNDEF_P(val)) { + RUBY_DTRACE_CREATE_HOOK(HASH, RHASH_SIZE(hash) << 1); + PUSH(rb_hash_resurrect(hash)); + CALL_SIMPLE_METHOD(); + } +} + DEFINE_INSN opt_str_freeze (VALUE str, CALL_DATA cd) diff --git a/internal/hash.h b/internal/hash.h index fe859cb716b95b..d66b5b2d04401f 100644 --- a/internal/hash.h +++ b/internal/hash.h @@ -88,6 +88,7 @@ int rb_hash_stlike_foreach_with_replace(VALUE hash, st_foreach_check_callback_fu int rb_hash_stlike_update(VALUE hash, st_data_t key, st_update_callback_func *func, st_data_t arg); VALUE rb_ident_hash_new_with_size(st_index_t size); void rb_hash_free(VALUE hash); +RUBY_EXTERN VALUE rb_cHash_empty_frozen; static inline unsigned RHASH_AR_TABLE_SIZE_RAW(VALUE h); static inline VALUE RHASH_IFNONE(VALUE h); diff --git a/lib/ruby_vm/rjit/insn_compiler.rb b/lib/ruby_vm/rjit/insn_compiler.rb index c6b886e192c45c..a33ba9f4687bff 100644 --- a/lib/ruby_vm/rjit/insn_compiler.rb +++ b/lib/ruby_vm/rjit/insn_compiler.rb @@ -91,6 +91,7 @@ def compile(jit, ctx, asm, insn) when :objtostring then objtostring(jit, ctx, asm) when :opt_str_freeze then opt_str_freeze(jit, ctx, asm) when :opt_ary_freeze then opt_ary_freeze(jit, ctx, asm) + when :opt_hash_freeze then opt_hash_freeze(jit, ctx, asm) when :opt_nil_p then opt_nil_p(jit, ctx, asm) # opt_str_uminus when :opt_newarray_send then opt_newarray_send(jit, ctx, asm) @@ -1509,6 +1510,24 @@ def opt_ary_freeze(jit, ctx, asm) KeepCompiling end + # @param jit [RubyVM::RJIT::JITState] + # @param ctx [RubyVM::RJIT::Context] + # @param asm [RubyVM::RJIT::Assembler] + def opt_hash_freeze(jit, ctx, asm) + unless Invariants.assume_bop_not_redefined(jit, C::HASH_REDEFINED_OP_FLAG, C::BOP_FREEZE) + return CantCompile; + end + + hash = jit.operand(0, ruby: true) + + # Push the return value onto the stack + stack_ret = ctx.stack_push(Type::CHash) + asm.mov(:rax, to_value(hash)) + asm.mov(stack_ret, :rax) + + KeepCompiling + end + # @param jit [RubyVM::RJIT::JITState] # @param ctx [RubyVM::RJIT::Context] # @param asm [RubyVM::RJIT::Assembler] diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index 671f7d7ed5810b..982da661ecd0f9 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -795,6 +795,52 @@ def freeze RUBY end + def test_peephole_hash_freeze + code = "#{<<~'begin;'}\n#{<<~'end;'}" + begin; + {a:1}.freeze + end; + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_hash_freeze/, insn) + assert_no_match(/duphash/, insn) + assert_no_match(/send/, insn) + assert_predicate([1].freeze, :frozen?) + assert_in_out_err([], <<~RUBY, [":ok"]) + class Hash + prepend Module.new { + def freeze + :ok + end + } + end + p({a:1}.freeze) + RUBY + end + + def test_peephole_hash_freeze_empty + code = "#{<<~'begin;'}\n#{<<~'end;'}" + begin; + {}.freeze + end; + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_hash_freeze/, insn) + assert_no_match(/duphash/, insn) + assert_no_match(/send/, insn) + assert_predicate([].freeze, :frozen?) + assert_in_out_err([], <<~RUBY, [":ok"]) + class Hash + prepend Module.new { + def freeze + :ok + end + } + end + p({}.freeze) + RUBY + end + def test_branch_condition_backquote bug = '[ruby-core:80740] [Bug #13444] redefined backquote should be called' class << self diff --git a/vm.c b/vm.c index 0f0c3c91b19ccf..6b75a7ad5a76b2 100644 --- a/vm.c +++ b/vm.c @@ -2232,7 +2232,7 @@ vm_init_redefined_flag(void) OP(EmptyP, EMPTY_P), (C(Array), C(String), C(Hash)); OP(Succ, SUCC), (C(Integer), C(String)); OP(EqTilde, MATCH), (C(Regexp), C(String)); - OP(Freeze, FREEZE), (C(String), C(Array)); + OP(Freeze, FREEZE), (C(String), C(Array), C(Hash)); OP(UMinus, UMINUS), (C(String)); OP(Max, MAX), (C(Array)); OP(Min, MIN), (C(Array)); diff --git a/vm_insnhelper.c b/vm_insnhelper.c index bc133f4013a9e8..543f31a3b1f56e 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -6120,6 +6120,17 @@ vm_opt_ary_freeze(VALUE ary, int bop, ID id) } } +static VALUE +vm_opt_hash_freeze(VALUE hash, int bop, ID id) +{ + if (BASIC_OP_UNREDEFINED_P(bop, HASH_REDEFINED_OP_FLAG)) { + return hash; + } + else { + return Qundef; + } +} + static VALUE vm_opt_str_freeze(VALUE str, int bop, ID id) { diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 08aaa60731319e..a105ab97916b97 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -4204,6 +4204,23 @@ fn gen_opt_ary_freeze( Some(KeepCompiling) } +fn gen_opt_hash_freeze( + jit: &mut JITState, + asm: &mut Assembler, +) -> Option { + if !assume_bop_not_redefined(jit, asm, HASH_REDEFINED_OP_FLAG, BOP_FREEZE) { + return None; + } + + let str = jit.get_arg(0); + + // Push the return value onto the stack + let stack_ret = asm.stack_push(Type::CHash); + asm.mov(stack_ret, str.into()); + + Some(KeepCompiling) +} + fn gen_opt_str_uminus( jit: &mut JITState, asm: &mut Assembler, @@ -10246,6 +10263,7 @@ fn get_gen_fn(opcode: VALUE) -> Option { YARVINSN_opt_ge => Some(gen_opt_ge), YARVINSN_opt_mod => Some(gen_opt_mod), YARVINSN_opt_ary_freeze => Some(gen_opt_ary_freeze), + YARVINSN_opt_hash_freeze => Some(gen_opt_hash_freeze), YARVINSN_opt_str_freeze => Some(gen_opt_str_freeze), YARVINSN_opt_str_uminus => Some(gen_opt_str_uminus), YARVINSN_opt_newarray_send => Some(gen_opt_newarray_send), diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 7492a28f22ed09..03cb053fd323d9 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -784,163 +784,165 @@ pub const YARVINSN_sendforward: ruby_vminsn_type = 56; pub const YARVINSN_opt_send_without_block: ruby_vminsn_type = 57; pub const YARVINSN_objtostring: ruby_vminsn_type = 58; pub const YARVINSN_opt_ary_freeze: ruby_vminsn_type = 59; -pub const YARVINSN_opt_str_freeze: ruby_vminsn_type = 60; -pub const YARVINSN_opt_nil_p: ruby_vminsn_type = 61; -pub const YARVINSN_opt_str_uminus: ruby_vminsn_type = 62; -pub const YARVINSN_opt_newarray_send: ruby_vminsn_type = 63; -pub const YARVINSN_invokesuper: ruby_vminsn_type = 64; -pub const YARVINSN_invokesuperforward: ruby_vminsn_type = 65; -pub const YARVINSN_invokeblock: ruby_vminsn_type = 66; -pub const YARVINSN_leave: ruby_vminsn_type = 67; -pub const YARVINSN_throw: ruby_vminsn_type = 68; -pub const YARVINSN_jump: ruby_vminsn_type = 69; -pub const YARVINSN_branchif: ruby_vminsn_type = 70; -pub const YARVINSN_branchunless: ruby_vminsn_type = 71; -pub const YARVINSN_branchnil: ruby_vminsn_type = 72; -pub const YARVINSN_once: ruby_vminsn_type = 73; -pub const YARVINSN_opt_case_dispatch: ruby_vminsn_type = 74; -pub const YARVINSN_opt_plus: ruby_vminsn_type = 75; -pub const YARVINSN_opt_minus: ruby_vminsn_type = 76; -pub const YARVINSN_opt_mult: ruby_vminsn_type = 77; -pub const YARVINSN_opt_div: ruby_vminsn_type = 78; -pub const YARVINSN_opt_mod: ruby_vminsn_type = 79; -pub const YARVINSN_opt_eq: ruby_vminsn_type = 80; -pub const YARVINSN_opt_neq: ruby_vminsn_type = 81; -pub const YARVINSN_opt_lt: ruby_vminsn_type = 82; -pub const YARVINSN_opt_le: ruby_vminsn_type = 83; -pub const YARVINSN_opt_gt: ruby_vminsn_type = 84; -pub const YARVINSN_opt_ge: ruby_vminsn_type = 85; -pub const YARVINSN_opt_ltlt: ruby_vminsn_type = 86; -pub const YARVINSN_opt_and: ruby_vminsn_type = 87; -pub const YARVINSN_opt_or: ruby_vminsn_type = 88; -pub const YARVINSN_opt_aref: ruby_vminsn_type = 89; -pub const YARVINSN_opt_aset: ruby_vminsn_type = 90; -pub const YARVINSN_opt_aset_with: ruby_vminsn_type = 91; -pub const YARVINSN_opt_aref_with: ruby_vminsn_type = 92; -pub const YARVINSN_opt_length: ruby_vminsn_type = 93; -pub const YARVINSN_opt_size: ruby_vminsn_type = 94; -pub const YARVINSN_opt_empty_p: ruby_vminsn_type = 95; -pub const YARVINSN_opt_succ: ruby_vminsn_type = 96; -pub const YARVINSN_opt_not: ruby_vminsn_type = 97; -pub const YARVINSN_opt_regexpmatch2: ruby_vminsn_type = 98; -pub const YARVINSN_invokebuiltin: ruby_vminsn_type = 99; -pub const YARVINSN_opt_invokebuiltin_delegate: ruby_vminsn_type = 100; -pub const YARVINSN_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 101; -pub const YARVINSN_getlocal_WC_0: ruby_vminsn_type = 102; -pub const YARVINSN_getlocal_WC_1: ruby_vminsn_type = 103; -pub const YARVINSN_setlocal_WC_0: ruby_vminsn_type = 104; -pub const YARVINSN_setlocal_WC_1: ruby_vminsn_type = 105; -pub const YARVINSN_putobject_INT2FIX_0_: ruby_vminsn_type = 106; -pub const YARVINSN_putobject_INT2FIX_1_: ruby_vminsn_type = 107; -pub const YARVINSN_trace_nop: ruby_vminsn_type = 108; -pub const YARVINSN_trace_getlocal: ruby_vminsn_type = 109; -pub const YARVINSN_trace_setlocal: ruby_vminsn_type = 110; -pub const YARVINSN_trace_getblockparam: ruby_vminsn_type = 111; -pub const YARVINSN_trace_setblockparam: ruby_vminsn_type = 112; -pub const YARVINSN_trace_getblockparamproxy: ruby_vminsn_type = 113; -pub const YARVINSN_trace_getspecial: ruby_vminsn_type = 114; -pub const YARVINSN_trace_setspecial: ruby_vminsn_type = 115; -pub const YARVINSN_trace_getinstancevariable: ruby_vminsn_type = 116; -pub const YARVINSN_trace_setinstancevariable: ruby_vminsn_type = 117; -pub const YARVINSN_trace_getclassvariable: ruby_vminsn_type = 118; -pub const YARVINSN_trace_setclassvariable: ruby_vminsn_type = 119; -pub const YARVINSN_trace_opt_getconstant_path: ruby_vminsn_type = 120; -pub const YARVINSN_trace_getconstant: ruby_vminsn_type = 121; -pub const YARVINSN_trace_setconstant: ruby_vminsn_type = 122; -pub const YARVINSN_trace_getglobal: ruby_vminsn_type = 123; -pub const YARVINSN_trace_setglobal: ruby_vminsn_type = 124; -pub const YARVINSN_trace_putnil: ruby_vminsn_type = 125; -pub const YARVINSN_trace_putself: ruby_vminsn_type = 126; -pub const YARVINSN_trace_putobject: ruby_vminsn_type = 127; -pub const YARVINSN_trace_putspecialobject: ruby_vminsn_type = 128; -pub const YARVINSN_trace_putstring: ruby_vminsn_type = 129; -pub const YARVINSN_trace_putchilledstring: ruby_vminsn_type = 130; -pub const YARVINSN_trace_concatstrings: ruby_vminsn_type = 131; -pub const YARVINSN_trace_anytostring: ruby_vminsn_type = 132; -pub const YARVINSN_trace_toregexp: ruby_vminsn_type = 133; -pub const YARVINSN_trace_intern: ruby_vminsn_type = 134; -pub const YARVINSN_trace_newarray: ruby_vminsn_type = 135; -pub const YARVINSN_trace_pushtoarraykwsplat: ruby_vminsn_type = 136; -pub const YARVINSN_trace_duparray: ruby_vminsn_type = 137; -pub const YARVINSN_trace_duphash: ruby_vminsn_type = 138; -pub const YARVINSN_trace_expandarray: ruby_vminsn_type = 139; -pub const YARVINSN_trace_concatarray: ruby_vminsn_type = 140; -pub const YARVINSN_trace_concattoarray: ruby_vminsn_type = 141; -pub const YARVINSN_trace_pushtoarray: ruby_vminsn_type = 142; -pub const YARVINSN_trace_splatarray: ruby_vminsn_type = 143; -pub const YARVINSN_trace_splatkw: ruby_vminsn_type = 144; -pub const YARVINSN_trace_newhash: ruby_vminsn_type = 145; -pub const YARVINSN_trace_newrange: ruby_vminsn_type = 146; -pub const YARVINSN_trace_pop: ruby_vminsn_type = 147; -pub const YARVINSN_trace_dup: ruby_vminsn_type = 148; -pub const YARVINSN_trace_dupn: ruby_vminsn_type = 149; -pub const YARVINSN_trace_swap: ruby_vminsn_type = 150; -pub const YARVINSN_trace_opt_reverse: ruby_vminsn_type = 151; -pub const YARVINSN_trace_topn: ruby_vminsn_type = 152; -pub const YARVINSN_trace_setn: ruby_vminsn_type = 153; -pub const YARVINSN_trace_adjuststack: ruby_vminsn_type = 154; -pub const YARVINSN_trace_defined: ruby_vminsn_type = 155; -pub const YARVINSN_trace_definedivar: ruby_vminsn_type = 156; -pub const YARVINSN_trace_checkmatch: ruby_vminsn_type = 157; -pub const YARVINSN_trace_checkkeyword: ruby_vminsn_type = 158; -pub const YARVINSN_trace_checktype: ruby_vminsn_type = 159; -pub const YARVINSN_trace_defineclass: ruby_vminsn_type = 160; -pub const YARVINSN_trace_definemethod: ruby_vminsn_type = 161; -pub const YARVINSN_trace_definesmethod: ruby_vminsn_type = 162; -pub const YARVINSN_trace_send: ruby_vminsn_type = 163; -pub const YARVINSN_trace_sendforward: ruby_vminsn_type = 164; -pub const YARVINSN_trace_opt_send_without_block: ruby_vminsn_type = 165; -pub const YARVINSN_trace_objtostring: ruby_vminsn_type = 166; -pub const YARVINSN_trace_opt_ary_freeze: ruby_vminsn_type = 167; -pub const YARVINSN_trace_opt_str_freeze: ruby_vminsn_type = 168; -pub const YARVINSN_trace_opt_nil_p: ruby_vminsn_type = 169; -pub const YARVINSN_trace_opt_str_uminus: ruby_vminsn_type = 170; -pub const YARVINSN_trace_opt_newarray_send: ruby_vminsn_type = 171; -pub const YARVINSN_trace_invokesuper: ruby_vminsn_type = 172; -pub const YARVINSN_trace_invokesuperforward: ruby_vminsn_type = 173; -pub const YARVINSN_trace_invokeblock: ruby_vminsn_type = 174; -pub const YARVINSN_trace_leave: ruby_vminsn_type = 175; -pub const YARVINSN_trace_throw: ruby_vminsn_type = 176; -pub const YARVINSN_trace_jump: ruby_vminsn_type = 177; -pub const YARVINSN_trace_branchif: ruby_vminsn_type = 178; -pub const YARVINSN_trace_branchunless: ruby_vminsn_type = 179; -pub const YARVINSN_trace_branchnil: ruby_vminsn_type = 180; -pub const YARVINSN_trace_once: ruby_vminsn_type = 181; -pub const YARVINSN_trace_opt_case_dispatch: ruby_vminsn_type = 182; -pub const YARVINSN_trace_opt_plus: ruby_vminsn_type = 183; -pub const YARVINSN_trace_opt_minus: ruby_vminsn_type = 184; -pub const YARVINSN_trace_opt_mult: ruby_vminsn_type = 185; -pub const YARVINSN_trace_opt_div: ruby_vminsn_type = 186; -pub const YARVINSN_trace_opt_mod: ruby_vminsn_type = 187; -pub const YARVINSN_trace_opt_eq: ruby_vminsn_type = 188; -pub const YARVINSN_trace_opt_neq: ruby_vminsn_type = 189; -pub const YARVINSN_trace_opt_lt: ruby_vminsn_type = 190; -pub const YARVINSN_trace_opt_le: ruby_vminsn_type = 191; -pub const YARVINSN_trace_opt_gt: ruby_vminsn_type = 192; -pub const YARVINSN_trace_opt_ge: ruby_vminsn_type = 193; -pub const YARVINSN_trace_opt_ltlt: ruby_vminsn_type = 194; -pub const YARVINSN_trace_opt_and: ruby_vminsn_type = 195; -pub const YARVINSN_trace_opt_or: ruby_vminsn_type = 196; -pub const YARVINSN_trace_opt_aref: ruby_vminsn_type = 197; -pub const YARVINSN_trace_opt_aset: ruby_vminsn_type = 198; -pub const YARVINSN_trace_opt_aset_with: ruby_vminsn_type = 199; -pub const YARVINSN_trace_opt_aref_with: ruby_vminsn_type = 200; -pub const YARVINSN_trace_opt_length: ruby_vminsn_type = 201; -pub const YARVINSN_trace_opt_size: ruby_vminsn_type = 202; -pub const YARVINSN_trace_opt_empty_p: ruby_vminsn_type = 203; -pub const YARVINSN_trace_opt_succ: ruby_vminsn_type = 204; -pub const YARVINSN_trace_opt_not: ruby_vminsn_type = 205; -pub const YARVINSN_trace_opt_regexpmatch2: ruby_vminsn_type = 206; -pub const YARVINSN_trace_invokebuiltin: ruby_vminsn_type = 207; -pub const YARVINSN_trace_opt_invokebuiltin_delegate: ruby_vminsn_type = 208; -pub const YARVINSN_trace_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 209; -pub const YARVINSN_trace_getlocal_WC_0: ruby_vminsn_type = 210; -pub const YARVINSN_trace_getlocal_WC_1: ruby_vminsn_type = 211; -pub const YARVINSN_trace_setlocal_WC_0: ruby_vminsn_type = 212; -pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 213; -pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 214; -pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 215; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 216; +pub const YARVINSN_opt_hash_freeze: ruby_vminsn_type = 60; +pub const YARVINSN_opt_str_freeze: ruby_vminsn_type = 61; +pub const YARVINSN_opt_nil_p: ruby_vminsn_type = 62; +pub const YARVINSN_opt_str_uminus: ruby_vminsn_type = 63; +pub const YARVINSN_opt_newarray_send: ruby_vminsn_type = 64; +pub const YARVINSN_invokesuper: ruby_vminsn_type = 65; +pub const YARVINSN_invokesuperforward: ruby_vminsn_type = 66; +pub const YARVINSN_invokeblock: ruby_vminsn_type = 67; +pub const YARVINSN_leave: ruby_vminsn_type = 68; +pub const YARVINSN_throw: ruby_vminsn_type = 69; +pub const YARVINSN_jump: ruby_vminsn_type = 70; +pub const YARVINSN_branchif: ruby_vminsn_type = 71; +pub const YARVINSN_branchunless: ruby_vminsn_type = 72; +pub const YARVINSN_branchnil: ruby_vminsn_type = 73; +pub const YARVINSN_once: ruby_vminsn_type = 74; +pub const YARVINSN_opt_case_dispatch: ruby_vminsn_type = 75; +pub const YARVINSN_opt_plus: ruby_vminsn_type = 76; +pub const YARVINSN_opt_minus: ruby_vminsn_type = 77; +pub const YARVINSN_opt_mult: ruby_vminsn_type = 78; +pub const YARVINSN_opt_div: ruby_vminsn_type = 79; +pub const YARVINSN_opt_mod: ruby_vminsn_type = 80; +pub const YARVINSN_opt_eq: ruby_vminsn_type = 81; +pub const YARVINSN_opt_neq: ruby_vminsn_type = 82; +pub const YARVINSN_opt_lt: ruby_vminsn_type = 83; +pub const YARVINSN_opt_le: ruby_vminsn_type = 84; +pub const YARVINSN_opt_gt: ruby_vminsn_type = 85; +pub const YARVINSN_opt_ge: ruby_vminsn_type = 86; +pub const YARVINSN_opt_ltlt: ruby_vminsn_type = 87; +pub const YARVINSN_opt_and: ruby_vminsn_type = 88; +pub const YARVINSN_opt_or: ruby_vminsn_type = 89; +pub const YARVINSN_opt_aref: ruby_vminsn_type = 90; +pub const YARVINSN_opt_aset: ruby_vminsn_type = 91; +pub const YARVINSN_opt_aset_with: ruby_vminsn_type = 92; +pub const YARVINSN_opt_aref_with: ruby_vminsn_type = 93; +pub const YARVINSN_opt_length: ruby_vminsn_type = 94; +pub const YARVINSN_opt_size: ruby_vminsn_type = 95; +pub const YARVINSN_opt_empty_p: ruby_vminsn_type = 96; +pub const YARVINSN_opt_succ: ruby_vminsn_type = 97; +pub const YARVINSN_opt_not: ruby_vminsn_type = 98; +pub const YARVINSN_opt_regexpmatch2: ruby_vminsn_type = 99; +pub const YARVINSN_invokebuiltin: ruby_vminsn_type = 100; +pub const YARVINSN_opt_invokebuiltin_delegate: ruby_vminsn_type = 101; +pub const YARVINSN_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 102; +pub const YARVINSN_getlocal_WC_0: ruby_vminsn_type = 103; +pub const YARVINSN_getlocal_WC_1: ruby_vminsn_type = 104; +pub const YARVINSN_setlocal_WC_0: ruby_vminsn_type = 105; +pub const YARVINSN_setlocal_WC_1: ruby_vminsn_type = 106; +pub const YARVINSN_putobject_INT2FIX_0_: ruby_vminsn_type = 107; +pub const YARVINSN_putobject_INT2FIX_1_: ruby_vminsn_type = 108; +pub const YARVINSN_trace_nop: ruby_vminsn_type = 109; +pub const YARVINSN_trace_getlocal: ruby_vminsn_type = 110; +pub const YARVINSN_trace_setlocal: ruby_vminsn_type = 111; +pub const YARVINSN_trace_getblockparam: ruby_vminsn_type = 112; +pub const YARVINSN_trace_setblockparam: ruby_vminsn_type = 113; +pub const YARVINSN_trace_getblockparamproxy: ruby_vminsn_type = 114; +pub const YARVINSN_trace_getspecial: ruby_vminsn_type = 115; +pub const YARVINSN_trace_setspecial: ruby_vminsn_type = 116; +pub const YARVINSN_trace_getinstancevariable: ruby_vminsn_type = 117; +pub const YARVINSN_trace_setinstancevariable: ruby_vminsn_type = 118; +pub const YARVINSN_trace_getclassvariable: ruby_vminsn_type = 119; +pub const YARVINSN_trace_setclassvariable: ruby_vminsn_type = 120; +pub const YARVINSN_trace_opt_getconstant_path: ruby_vminsn_type = 121; +pub const YARVINSN_trace_getconstant: ruby_vminsn_type = 122; +pub const YARVINSN_trace_setconstant: ruby_vminsn_type = 123; +pub const YARVINSN_trace_getglobal: ruby_vminsn_type = 124; +pub const YARVINSN_trace_setglobal: ruby_vminsn_type = 125; +pub const YARVINSN_trace_putnil: ruby_vminsn_type = 126; +pub const YARVINSN_trace_putself: ruby_vminsn_type = 127; +pub const YARVINSN_trace_putobject: ruby_vminsn_type = 128; +pub const YARVINSN_trace_putspecialobject: ruby_vminsn_type = 129; +pub const YARVINSN_trace_putstring: ruby_vminsn_type = 130; +pub const YARVINSN_trace_putchilledstring: ruby_vminsn_type = 131; +pub const YARVINSN_trace_concatstrings: ruby_vminsn_type = 132; +pub const YARVINSN_trace_anytostring: ruby_vminsn_type = 133; +pub const YARVINSN_trace_toregexp: ruby_vminsn_type = 134; +pub const YARVINSN_trace_intern: ruby_vminsn_type = 135; +pub const YARVINSN_trace_newarray: ruby_vminsn_type = 136; +pub const YARVINSN_trace_pushtoarraykwsplat: ruby_vminsn_type = 137; +pub const YARVINSN_trace_duparray: ruby_vminsn_type = 138; +pub const YARVINSN_trace_duphash: ruby_vminsn_type = 139; +pub const YARVINSN_trace_expandarray: ruby_vminsn_type = 140; +pub const YARVINSN_trace_concatarray: ruby_vminsn_type = 141; +pub const YARVINSN_trace_concattoarray: ruby_vminsn_type = 142; +pub const YARVINSN_trace_pushtoarray: ruby_vminsn_type = 143; +pub const YARVINSN_trace_splatarray: ruby_vminsn_type = 144; +pub const YARVINSN_trace_splatkw: ruby_vminsn_type = 145; +pub const YARVINSN_trace_newhash: ruby_vminsn_type = 146; +pub const YARVINSN_trace_newrange: ruby_vminsn_type = 147; +pub const YARVINSN_trace_pop: ruby_vminsn_type = 148; +pub const YARVINSN_trace_dup: ruby_vminsn_type = 149; +pub const YARVINSN_trace_dupn: ruby_vminsn_type = 150; +pub const YARVINSN_trace_swap: ruby_vminsn_type = 151; +pub const YARVINSN_trace_opt_reverse: ruby_vminsn_type = 152; +pub const YARVINSN_trace_topn: ruby_vminsn_type = 153; +pub const YARVINSN_trace_setn: ruby_vminsn_type = 154; +pub const YARVINSN_trace_adjuststack: ruby_vminsn_type = 155; +pub const YARVINSN_trace_defined: ruby_vminsn_type = 156; +pub const YARVINSN_trace_definedivar: ruby_vminsn_type = 157; +pub const YARVINSN_trace_checkmatch: ruby_vminsn_type = 158; +pub const YARVINSN_trace_checkkeyword: ruby_vminsn_type = 159; +pub const YARVINSN_trace_checktype: ruby_vminsn_type = 160; +pub const YARVINSN_trace_defineclass: ruby_vminsn_type = 161; +pub const YARVINSN_trace_definemethod: ruby_vminsn_type = 162; +pub const YARVINSN_trace_definesmethod: ruby_vminsn_type = 163; +pub const YARVINSN_trace_send: ruby_vminsn_type = 164; +pub const YARVINSN_trace_sendforward: ruby_vminsn_type = 165; +pub const YARVINSN_trace_opt_send_without_block: ruby_vminsn_type = 166; +pub const YARVINSN_trace_objtostring: ruby_vminsn_type = 167; +pub const YARVINSN_trace_opt_ary_freeze: ruby_vminsn_type = 168; +pub const YARVINSN_trace_opt_hash_freeze: ruby_vminsn_type = 169; +pub const YARVINSN_trace_opt_str_freeze: ruby_vminsn_type = 170; +pub const YARVINSN_trace_opt_nil_p: ruby_vminsn_type = 171; +pub const YARVINSN_trace_opt_str_uminus: ruby_vminsn_type = 172; +pub const YARVINSN_trace_opt_newarray_send: ruby_vminsn_type = 173; +pub const YARVINSN_trace_invokesuper: ruby_vminsn_type = 174; +pub const YARVINSN_trace_invokesuperforward: ruby_vminsn_type = 175; +pub const YARVINSN_trace_invokeblock: ruby_vminsn_type = 176; +pub const YARVINSN_trace_leave: ruby_vminsn_type = 177; +pub const YARVINSN_trace_throw: ruby_vminsn_type = 178; +pub const YARVINSN_trace_jump: ruby_vminsn_type = 179; +pub const YARVINSN_trace_branchif: ruby_vminsn_type = 180; +pub const YARVINSN_trace_branchunless: ruby_vminsn_type = 181; +pub const YARVINSN_trace_branchnil: ruby_vminsn_type = 182; +pub const YARVINSN_trace_once: ruby_vminsn_type = 183; +pub const YARVINSN_trace_opt_case_dispatch: ruby_vminsn_type = 184; +pub const YARVINSN_trace_opt_plus: ruby_vminsn_type = 185; +pub const YARVINSN_trace_opt_minus: ruby_vminsn_type = 186; +pub const YARVINSN_trace_opt_mult: ruby_vminsn_type = 187; +pub const YARVINSN_trace_opt_div: ruby_vminsn_type = 188; +pub const YARVINSN_trace_opt_mod: ruby_vminsn_type = 189; +pub const YARVINSN_trace_opt_eq: ruby_vminsn_type = 190; +pub const YARVINSN_trace_opt_neq: ruby_vminsn_type = 191; +pub const YARVINSN_trace_opt_lt: ruby_vminsn_type = 192; +pub const YARVINSN_trace_opt_le: ruby_vminsn_type = 193; +pub const YARVINSN_trace_opt_gt: ruby_vminsn_type = 194; +pub const YARVINSN_trace_opt_ge: ruby_vminsn_type = 195; +pub const YARVINSN_trace_opt_ltlt: ruby_vminsn_type = 196; +pub const YARVINSN_trace_opt_and: ruby_vminsn_type = 197; +pub const YARVINSN_trace_opt_or: ruby_vminsn_type = 198; +pub const YARVINSN_trace_opt_aref: ruby_vminsn_type = 199; +pub const YARVINSN_trace_opt_aset: ruby_vminsn_type = 200; +pub const YARVINSN_trace_opt_aset_with: ruby_vminsn_type = 201; +pub const YARVINSN_trace_opt_aref_with: ruby_vminsn_type = 202; +pub const YARVINSN_trace_opt_length: ruby_vminsn_type = 203; +pub const YARVINSN_trace_opt_size: ruby_vminsn_type = 204; +pub const YARVINSN_trace_opt_empty_p: ruby_vminsn_type = 205; +pub const YARVINSN_trace_opt_succ: ruby_vminsn_type = 206; +pub const YARVINSN_trace_opt_not: ruby_vminsn_type = 207; +pub const YARVINSN_trace_opt_regexpmatch2: ruby_vminsn_type = 208; +pub const YARVINSN_trace_invokebuiltin: ruby_vminsn_type = 209; +pub const YARVINSN_trace_opt_invokebuiltin_delegate: ruby_vminsn_type = 210; +pub const YARVINSN_trace_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 211; +pub const YARVINSN_trace_getlocal_WC_0: ruby_vminsn_type = 212; +pub const YARVINSN_trace_getlocal_WC_1: ruby_vminsn_type = 213; +pub const YARVINSN_trace_setlocal_WC_0: ruby_vminsn_type = 214; +pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 215; +pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 216; +pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 217; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 218; pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), From be6d23646f3d187c1c68cfdd9fb8eb85481342ed Mon Sep 17 00:00:00 2001 From: Mari Imaizumi Date: Thu, 5 Sep 2024 21:22:30 +0900 Subject: [PATCH 08/14] [ruby/reline] Prevent a warning for `warning: literal string will be frozen in the future` (https://github.com/ruby/reline/pull/744) https://github.com/ruby/reline/commit/69c95c8b6a --- test/reline/helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/reline/helper.rb b/test/reline/helper.rb index 9a346a17a1c91f..3d7dc7d812cde6 100644 --- a/test/reline/helper.rb +++ b/test/reline/helper.rb @@ -96,7 +96,7 @@ class Reline::TestCase < Test::Unit::TestCase end }.join rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError - input.unicode_normalize!(:nfc) + input = input.unicode_normalize(:nfc) if normalized options[:undef] = :replace options[:replace] = '?' From 721a2ceecfe3e3f943b7da50efe8f134efb97fea Mon Sep 17 00:00:00 2001 From: Mari Imaizumi Date: Thu, 5 Sep 2024 21:47:59 +0900 Subject: [PATCH 09/14] [ruby/reline] Bump version to 0.5.10 (https://github.com/ruby/reline/pull/745) https://github.com/ruby/reline/commit/0ebd54f675 --- lib/reline/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reline/version.rb b/lib/reline/version.rb index 37916d11b99e04..b75d874adb7744 100644 --- a/lib/reline/version.rb +++ b/lib/reline/version.rb @@ -1,3 +1,3 @@ module Reline - VERSION = '0.5.9' + VERSION = '0.5.10' end From ffebc1c7c7c4d95a0b271c17d8349674708f2ab5 Mon Sep 17 00:00:00 2001 From: git Date: Thu, 5 Sep 2024 12:49:01 +0000 Subject: [PATCH 10/14] Update default gems list at 721a2ceecfe3e3f943b7da50efe8f1 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 9af65809ee7f78..b91fceb9754587 100644 --- a/NEWS.md +++ b/NEWS.md @@ -80,7 +80,7 @@ The following default gems are updated. * optparse 0.5.0 * prism 1.0.0 * rdoc 6.7.0 -* reline 0.5.9 +* reline 0.5.10 * resolv 0.4.0 * stringio 3.1.2.dev * strscan 3.1.1.dev From d4de8aef374310d318b27c910f2fb562da170641 Mon Sep 17 00:00:00 2001 From: mterada1228 <49284339+mterada1228@users.noreply.github.com> Date: Fri, 6 Sep 2024 01:53:26 +0900 Subject: [PATCH 11/14] `rake install` command is failed (#1170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * `rake install` command is failed \### Problems Several file paths were changed by following PR. - https://github.com/ruby/rdoc/commit/4211292ffe80dd4737db2450d72df404a9d55051 - https://github.com/ruby/rdoc/commit/d7bca12c13b8b3f1632d698d497e67d4ea8a88bc Because rdoc.gemspec doesn't take in this changes, the `rake install` command is permanently failed. \### Test \#### before ```console ❯ bundle exec rake install Running RuboCop... Inspecting 4 files .... 4 files inspected, no offenses detected Tip: Based on detected gems, the following RuboCop extension libraries might be helpful: * rubocop-rake (https://rubygems.org/gems/rubocop-rake) You can opt out of this message by adding the following to your config (see https://docs.rubocop.org/rubocop/extensions.html#extension-suggestions for more options): AllCops: SuggestExtensions: false rake aborted! Running `gem build -V /Users/mterada/dev/redDataTools/remove_dependency/rdoc/rdoc.gemspec` failed with the following output: WARNING: See https://guides.rubygems.org/specification-reference/ for help ERROR: While executing gem ... (Gem::InvalidSpecificationException) ["RI.rdoc", "lib/rdoc/alias.rb", "lib/rdoc/anon_class.rb", "lib/rdoc/any_method.rb", "lib/rdoc/attr.rb", "lib/rdoc/class_module.rb", "lib/rdoc/constant.rb", "lib/rdoc/context.rb", "lib/rdoc/context/section.rb", "lib/rdoc/extend.rb", "lib/rdoc/ghost_method.rb", "lib/rdoc/include.rb", "lib/rdoc/meta_method.rb", "lib/rdoc/method_attr.rb", "lib/rdoc/mixin.rb", "lib/rdoc/normal_class.rb", "lib/rdoc/normal_module.rb", "lib/rdoc/require.rb", "lib/rdoc/single_class.rb", "lib/rdoc/top_level.rb"] are not files /Users/mterada/.rbenv/versions/3.1.0/bin/bundle:25:in `load' /Users/mterada/.rbenv/versions/3.1.0/bin/bundle:25:in `
' Tasks: TOP => install => build (See full trace by running task with --trace) ``` \#### after ```console ❯ bundle exec rake install Running RuboCop... Inspecting 4 files .... 4 files inspected, no offenses detected Tip: Based on detected gems, the following RuboCop extension libraries might be helpful: * rubocop-rake (https://rubygems.org/gems/rubocop-rake) You can opt out of this message by adding the following to your config (see https://docs.rubocop.org/rubocop/extensions.html#extension-suggestions for more options): AllCops: SuggestExtensions: false rdoc 6.7.0 built to pkg/rdoc-6.7.0.gem. rdoc (6.7.0) installed. ``` * Add a `bundle exec rake install` step to github workflow * make intentionally CI failed * Revert "make intentionally CI failed" This reverts commit 9fc5dd9423a024594ad26d86a8a6af829e7017f8. --- lib/rdoc/rdoc.gemspec | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/rdoc/rdoc.gemspec b/lib/rdoc/rdoc.gemspec index 93a281c8aea62d..26f9ba1a871986 100644 --- a/lib/rdoc/rdoc.gemspec +++ b/lib/rdoc/rdoc.gemspec @@ -46,27 +46,27 @@ RDoc includes the +rdoc+ and +ri+ tools for generating and displaying documentat "LEGAL.rdoc", "LICENSE.rdoc", "README.rdoc", - "RI.rdoc", + "RI.md", "TODO.rdoc", "exe/rdoc", "exe/ri", "lib/rdoc.rb", - "lib/rdoc/alias.rb", - "lib/rdoc/anon_class.rb", - "lib/rdoc/any_method.rb", - "lib/rdoc/attr.rb", - "lib/rdoc/class_module.rb", + "lib/rdoc/code_object/alias.rb", + "lib/rdoc/code_object/anon_class.rb", + "lib/rdoc/code_object/any_method.rb", + "lib/rdoc/code_object/attr.rb", + "lib/rdoc/code_object/class_module.rb", "lib/rdoc/code_object.rb", "lib/rdoc/code_objects.rb", "lib/rdoc/comment.rb", - "lib/rdoc/constant.rb", - "lib/rdoc/context.rb", - "lib/rdoc/context/section.rb", + "lib/rdoc/code_object/constant.rb", + "lib/rdoc/code_object/context.rb", + "lib/rdoc/code_object/context/section.rb", "lib/rdoc/cross_reference.rb", "lib/rdoc/encoding.rb", "lib/rdoc/erb_partial.rb", "lib/rdoc/erbio.rb", - "lib/rdoc/extend.rb", + "lib/rdoc/code_object/extend.rb", "lib/rdoc/generator.rb", "lib/rdoc/generator/darkfish.rb", "lib/rdoc/generator/json_index.rb", @@ -136,11 +136,11 @@ RDoc includes the +rdoc+ and +ri+ tools for generating and displaying documentat "lib/rdoc/generator/template/json_index/.document", "lib/rdoc/generator/template/json_index/js/navigation.js", "lib/rdoc/generator/template/json_index/js/searcher.js", - "lib/rdoc/ghost_method.rb", + "lib/rdoc/code_object/ghost_method.rb", "lib/rdoc/i18n.rb", "lib/rdoc/i18n/locale.rb", "lib/rdoc/i18n/text.rb", - "lib/rdoc/include.rb", + "lib/rdoc/code_object/include.rb", "lib/rdoc/known_classes.rb", "lib/rdoc/markdown.kpeg", "lib/rdoc/markdown/entities.rb", @@ -180,11 +180,11 @@ RDoc includes the +rdoc+ and +ri+ tools for generating and displaying documentat "lib/rdoc/markup/to_test.rb", "lib/rdoc/markup/to_tt_only.rb", "lib/rdoc/markup/verbatim.rb", - "lib/rdoc/meta_method.rb", - "lib/rdoc/method_attr.rb", - "lib/rdoc/mixin.rb", - "lib/rdoc/normal_class.rb", - "lib/rdoc/normal_module.rb", + "lib/rdoc/code_object/meta_method.rb", + "lib/rdoc/code_object/method_attr.rb", + "lib/rdoc/code_object/mixin.rb", + "lib/rdoc/code_object/normal_class.rb", + "lib/rdoc/code_object/normal_module.rb", "lib/rdoc/options.rb", "lib/rdoc/parser.rb", "lib/rdoc/parser/c.rb", @@ -201,7 +201,7 @@ RDoc includes the +rdoc+ and +ri+ tools for generating and displaying documentat "lib/rdoc/rd/inline.rb", "lib/rdoc/rd/inline_parser.ry", "lib/rdoc/rdoc.rb", - "lib/rdoc/require.rb", + "lib/rdoc/code_object/require.rb", "lib/rdoc/ri.rb", "lib/rdoc/ri/driver.rb", "lib/rdoc/ri/formatter.rb", @@ -210,7 +210,7 @@ RDoc includes the +rdoc+ and +ri+ tools for generating and displaying documentat "lib/rdoc/ri/task.rb", "lib/rdoc/rubygems_hook.rb", "lib/rdoc/servlet.rb", - "lib/rdoc/single_class.rb", + "lib/rdoc/code_object/single_class.rb", "lib/rdoc/stats.rb", "lib/rdoc/stats/normal.rb", "lib/rdoc/stats/quiet.rb", @@ -220,7 +220,7 @@ RDoc includes the +rdoc+ and +ri+ tools for generating and displaying documentat "lib/rdoc/text.rb", "lib/rdoc/token_stream.rb", "lib/rdoc/tom_doc.rb", - "lib/rdoc/top_level.rb", + "lib/rdoc/code_object/top_level.rb", "lib/rdoc/version.rb", "man/ri.1", ] From 57e3fc32ea83e55d40f4cc6c3e437d485c506d34 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 30 Aug 2024 13:04:11 +0200 Subject: [PATCH 12/14] Move Time#xmlschema in core and optimize it [Feature #20707] Converting Time into RFC3339 / ISO8601 representation is an significant hotspot for applications that serialize data in JSON, XML or other formats. By moving it into core we can optimize it much further than what `strftime` will allow. ``` compare-ruby: ruby 3.4.0dev (2024-08-29T13:11:40Z master 6b08a50a62) +YJIT [arm64-darwin23] built-ruby: ruby 3.4.0dev (2024-08-30T13:17:32Z native-xmlschema 34041ff71f) +YJIT [arm64-darwin23] warming up...... | |compare-ruby|built-ruby| |:-----------------------|-----------:|---------:| |time.xmlschema | 1.087M| 5.190M| | | -| 4.78x| |utc_time.xmlschema | 1.464M| 6.848M| | | -| 4.68x| |time.xmlschema(6) | 859.960k| 4.646M| | | -| 5.40x| |utc_time.xmlschema(6) | 1.080M| 5.917M| | | -| 5.48x| |time.xmlschema(9) | 893.909k| 4.668M| | | -| 5.22x| |utc_time.xmlschema(9) | 1.056M| 5.707M| | | -| 5.40x| ``` --- benchmark/time_xmlschema.yml | 23 ++++++ spec/ruby/core/time/iso8601_spec.rb | 6 ++ spec/ruby/core/time/shared/xmlschema.rb | 31 ++++++++ spec/ruby/core/time/xmlschema_spec.rb | 6 ++ spec/ruby/library/time/iso8601_spec.rb | 2 +- spec/ruby/library/time/shared/xmlschema.rb | 2 +- spec/ruby/library/time/xmlschema_spec.rb | 2 +- test/ruby/test_time.rb | 56 +++++++++++++++ time.c | 82 ++++++++++++++++++++++ 9 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 benchmark/time_xmlschema.yml create mode 100644 spec/ruby/core/time/iso8601_spec.rb create mode 100644 spec/ruby/core/time/shared/xmlschema.rb create mode 100644 spec/ruby/core/time/xmlschema_spec.rb diff --git a/benchmark/time_xmlschema.yml b/benchmark/time_xmlschema.yml new file mode 100644 index 00000000000000..f746d9c6c8d1da --- /dev/null +++ b/benchmark/time_xmlschema.yml @@ -0,0 +1,23 @@ +prelude: | + # frozen_string_literal + unless Time.method_defined?(:xmlschema) + class Time + def xmlschema(fraction_digits=0) + fraction_digits = fraction_digits.to_i + s = strftime("%FT%T") + if fraction_digits > 0 + s << strftime(".%#{fraction_digits}N") + end + s << (utc? ? 'Z' : strftime("%:z")) + end + end + end + time = Time.now + utc_time = Time.now.utc +benchmark: + - time.xmlschema + - utc_time.xmlschema + - time.xmlschema(6) + - utc_time.xmlschema(6) + - time.xmlschema(9) + - utc_time.xmlschema(9) diff --git a/spec/ruby/core/time/iso8601_spec.rb b/spec/ruby/core/time/iso8601_spec.rb new file mode 100644 index 00000000000000..ad60c3bb322100 --- /dev/null +++ b/spec/ruby/core/time/iso8601_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/xmlschema' + +describe "Time#iso8601" do + it_behaves_like :time_xmlschema, :iso8601 +end diff --git a/spec/ruby/core/time/shared/xmlschema.rb b/spec/ruby/core/time/shared/xmlschema.rb new file mode 100644 index 00000000000000..d68c18df364b86 --- /dev/null +++ b/spec/ruby/core/time/shared/xmlschema.rb @@ -0,0 +1,31 @@ +describe :time_xmlschema, shared: true do + ruby_version_is "3.4" do + it "generates ISO-8601 strings in Z for UTC times" do + t = Time.utc(1985, 4, 12, 23, 20, 50, 521245) + t.send(@method).should == "1985-04-12T23:20:50Z" + t.send(@method, 2).should == "1985-04-12T23:20:50.52Z" + t.send(@method, 9).should == "1985-04-12T23:20:50.521245000Z" + end + + it "generates ISO-8601 string with timeone offset for non-UTC times" do + t = Time.new(1985, 4, 12, 23, 20, 50, "+02:00") + t.send(@method).should == "1985-04-12T23:20:50+02:00" + t.send(@method, 2).should == "1985-04-12T23:20:50.00+02:00" + end + + it "year is always at least 4 digits" do + t = Time.utc(12, 4, 12) + t.send(@method).should == "0012-04-12T00:00:00Z" + end + + it "year can be more than 4 digits" do + t = Time.utc(40_000, 4, 12) + t.send(@method).should == "40000-04-12T00:00:00Z" + end + + it "year can be negative" do + t = Time.utc(-2000, 4, 12) + t.send(@method).should == "-2000-04-12T00:00:00Z" + end + end +end diff --git a/spec/ruby/core/time/xmlschema_spec.rb b/spec/ruby/core/time/xmlschema_spec.rb new file mode 100644 index 00000000000000..bdf1dc7923eeaf --- /dev/null +++ b/spec/ruby/core/time/xmlschema_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/xmlschema' + +describe "Time#xmlschema" do + it_behaves_like :time_xmlschema, :xmlschema +end diff --git a/spec/ruby/library/time/iso8601_spec.rb b/spec/ruby/library/time/iso8601_spec.rb index 4a9eb45613867e..9e2607fbd0b814 100644 --- a/spec/ruby/library/time/iso8601_spec.rb +++ b/spec/ruby/library/time/iso8601_spec.rb @@ -3,5 +3,5 @@ require 'time' describe "Time.xmlschema" do - it_behaves_like :time_xmlschema, :iso8601 + it_behaves_like :time_library_xmlschema, :iso8601 end diff --git a/spec/ruby/library/time/shared/xmlschema.rb b/spec/ruby/library/time/shared/xmlschema.rb index 44d33cda7e386a..831d8509a72725 100644 --- a/spec/ruby/library/time/shared/xmlschema.rb +++ b/spec/ruby/library/time/shared/xmlschema.rb @@ -1,4 +1,4 @@ -describe :time_xmlschema, shared: true do +describe :time_library_xmlschema, shared: true do it "parses ISO-8601 strings" do t = Time.utc(1985, 4, 12, 23, 20, 50, 520000) s = "1985-04-12T23:20:50.52Z" diff --git a/spec/ruby/library/time/xmlschema_spec.rb b/spec/ruby/library/time/xmlschema_spec.rb index 427931119971fe..ff3c864a02e85b 100644 --- a/spec/ruby/library/time/xmlschema_spec.rb +++ b/spec/ruby/library/time/xmlschema_spec.rb @@ -3,5 +3,5 @@ require 'time' describe "Time.xmlschema" do - it_behaves_like :time_xmlschema, :xmlschema + it_behaves_like :time_library_xmlschema, :xmlschema end diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb index 2a541bbe8c8f47..cab14eb6941766 100644 --- a/test/ruby/test_time.rb +++ b/test/ruby/test_time.rb @@ -1444,4 +1444,60 @@ def test_deconstruct_keys def test_parse_zero_bigint assert_equal 0, Time.new("2020-10-28T16:48:07.000Z").nsec, '[Bug #19390]' end + + def test_xmlschema_encode + [:xmlschema, :iso8601].each do |method| + bug6100 = '[ruby-core:42997]' + + t = Time.utc(2001, 4, 17, 19, 23, 17, 300000) + assert_equal("2001-04-17T19:23:17Z", t.__send__(method)) + assert_equal("2001-04-17T19:23:17.3Z", t.__send__(method, 1)) + assert_equal("2001-04-17T19:23:17.300000Z", t.__send__(method, 6)) + assert_equal("2001-04-17T19:23:17.3000000Z", t.__send__(method, 7)) + assert_equal("2001-04-17T19:23:17.3Z", t.__send__(method, 1.9), bug6100) + + t = Time.utc(2001, 4, 17, 19, 23, 17, 123456) + assert_equal("2001-04-17T19:23:17.1234560Z", t.__send__(method, 7)) + assert_equal("2001-04-17T19:23:17.123456Z", t.__send__(method, 6)) + assert_equal("2001-04-17T19:23:17.12345Z", t.__send__(method, 5)) + assert_equal("2001-04-17T19:23:17.1Z", t.__send__(method, 1)) + assert_equal("2001-04-17T19:23:17.1Z", t.__send__(method, 1.9), bug6100) + + t = Time.at(2.quo(3)).getlocal("+09:00") + assert_equal("1970-01-01T09:00:00.666+09:00", t.__send__(method, 3)) + assert_equal("1970-01-01T09:00:00.6666666666+09:00", t.__send__(method, 10)) + assert_equal("1970-01-01T09:00:00.66666666666666666666+09:00", t.__send__(method, 20)) + assert_equal("1970-01-01T09:00:00.6+09:00", t.__send__(method, 1.1), bug6100) + assert_equal("1970-01-01T09:00:00.666+09:00", t.__send__(method, 3.2), bug6100) + + t = Time.at(123456789.quo(9999999999)).getlocal("+09:00") + assert_equal("1970-01-01T09:00:00.012+09:00", t.__send__(method, 3)) + assert_equal("1970-01-01T09:00:00.012345678+09:00", t.__send__(method, 9)) + assert_equal("1970-01-01T09:00:00.0123456789+09:00", t.__send__(method, 10)) + assert_equal("1970-01-01T09:00:00.0123456789012345678+09:00", t.__send__(method, 19)) + assert_equal("1970-01-01T09:00:00.01234567890123456789+09:00", t.__send__(method, 20)) + assert_equal("1970-01-01T09:00:00.012+09:00", t.__send__(method, 3.8), bug6100) + + t = Time.utc(1) + assert_equal("0001-01-01T00:00:00Z", t.__send__(method)) + + begin + Time.at(-1) + rescue ArgumentError + # ignore + else + t = Time.utc(1960, 12, 31, 23, 0, 0, 123456) + assert_equal("1960-12-31T23:00:00.123456Z", t.__send__(method, 6)) + end + + assert_equal("10000-01-01T00:00:00Z", Time.utc(10000).__send__(method)) + assert_equal("9999-01-01T00:00:00Z", Time.utc(9999).__send__(method)) + assert_equal("0001-01-01T00:00:00Z", Time.utc(1).__send__(method)) # 1 AD + assert_equal("0000-01-01T00:00:00Z", Time.utc(0).__send__(method)) # 1 BC + assert_equal("-0001-01-01T00:00:00Z", Time.utc(-1).__send__(method)) # 2 BC + assert_equal("-0004-01-01T00:00:00Z", Time.utc(-4).__send__(method)) # 5 BC + assert_equal("-9999-01-01T00:00:00Z", Time.utc(-9999).__send__(method)) + assert_equal("-10000-01-01T00:00:00Z", Time.utc(-10000).__send__(method)) + end + end end diff --git a/time.c b/time.c index 035d32dd3140f1..801df3658edc78 100644 --- a/time.c +++ b/time.c @@ -5215,6 +5215,86 @@ time_strftime(VALUE time, VALUE format) } } +static VALUE +time_xmlschema(int argc, VALUE *argv, VALUE time) +{ + long fraction_digits = 0; + rb_check_arity(argc, 0, 1); + if (argc > 0) { + fraction_digits = NUM2LONG(argv[0]); + if (fraction_digits < 0) { + fraction_digits = 0; + } + } + + struct time_object *tobj; + + GetTimeval(time, tobj); + MAKE_TM(time, tobj); + + long year = -1; + if (FIXNUM_P(tobj->vtm.year)) { + year = FIX2LONG(tobj->vtm.year); + } + if (RB_UNLIKELY(year > 9999 || year < 0 || fraction_digits > 9)) { + // Slow path for uncommon dates. + VALUE format = rb_utf8_str_new_cstr("%FT%T"); + if (fraction_digits > 0) { + rb_str_catf(format, ".%%#%ldN", fraction_digits); + } + rb_str_cat_cstr(format, TZMODE_UTC_P(tobj) ? "Z" : "%:z"); + return rb_funcallv(time, rb_intern("strftime"), 1, &format); + } + + long buf_size = sizeof("YYYY-MM-DDTHH:MM:SS+ZH:ZM") + fraction_digits + (fraction_digits > 0 ? 1 : 0); + + VALUE str = rb_str_buf_new(buf_size); + rb_enc_associate_index(str, rb_utf8_encindex()); + + char *ptr = RSTRING_PTR(str); + char *start = ptr; + int written = snprintf( + ptr, + sizeof("YYYY-MM-DDTHH:MM:SS"), + "%04ld-%02d-%02dT%02d:%02d:%02d", + year, + tobj->vtm.mon, + tobj->vtm.mday, + tobj->vtm.hour, + tobj->vtm.min, + tobj->vtm.sec + ); + RUBY_ASSERT(written == sizeof("YYYY-MM-DDTHH:MM:SS") - 1); + ptr += written; + + if (fraction_digits > 0) { + long nsec = NUM2LONG(mulquov(tobj->vtm.subsecx, INT2FIX(1000000000), INT2FIX(TIME_SCALE))); + long subsec = nsec / (long)pow(10, 9 - fraction_digits); + + *ptr = '.'; + ptr++; + + written = snprintf(ptr, fraction_digits + 1, "%0*ld", (int)fraction_digits, subsec); // Always allow to write \0 + RUBY_ASSERT(written > 0); + ptr += written; + } + + if (TZMODE_UTC_P(tobj)) { + *ptr = 'Z'; + ptr++; + } + else { + long offset = NUM2LONG(rb_time_utc_offset(time)); + int offset_hours = (int)(offset / 3600); + int offset_minutes = (int)((offset % 3600 / 60)); + written = snprintf(ptr, sizeof("+ZH:ZM"), "%+03d:%02d", offset_hours, offset_minutes); + RUBY_ASSERT(written == sizeof("+ZH:ZM") - 1); + ptr += written; + } + rb_str_set_len(str, ptr -start); // We could skip coderange scanning as we know it's full ASCII. + return str; +} + int ruby_marshal_write_long(long x, char *buf); enum {base_dump_size = 8}; @@ -5842,6 +5922,8 @@ Init_Time(void) rb_define_method(rb_cTime, "subsec", time_subsec, 0); rb_define_method(rb_cTime, "strftime", time_strftime, 1); + rb_define_method(rb_cTime, "xmlschema", time_xmlschema, -1); + rb_define_alias(rb_cTime, "iso8601", "xmlschema"); /* methods for marshaling */ rb_define_private_method(rb_cTime, "_dump", time_dump, -1); From cf3b62b54579d5cba4ceee707fee01b198de034a Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 29 Aug 2024 12:18:07 -0400 Subject: [PATCH 13/14] Fix check_tempfile_leak in leakchecker.rb The instance variable @tmpfile was removed in ddcfc9f so check_tempfile_leak did not work. --- tool/lib/leakchecker.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tool/lib/leakchecker.rb b/tool/lib/leakchecker.rb index 7518f49ae3e4e3..69aeb2c254cbfd 100644 --- a/tool/lib/leakchecker.rb +++ b/tool/lib/leakchecker.rb @@ -155,8 +155,8 @@ def find_tempfiles(prev_count=-1) if prev_count == count [prev_count, []] else - tempfiles = ObjectSpace.each_object(Tempfile).find_all {|t| - t.instance_variable_defined?(:@tmpfile) and t.path + tempfiles = ObjectSpace.each_object(Tempfile).reject {|t| + t.instance_variables.empty? || t.closed? } [count, tempfiles] end From f250296efaa7ea3e929eeaecfecd29e5cfe13de3 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 5 Sep 2024 12:39:57 -0700 Subject: [PATCH 14/14] YJIT: Speed up block_assumptions_free (#11556) --- yjit/src/invariants.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/yjit/src/invariants.rs b/yjit/src/invariants.rs index 98516aa400c811..d468cfebd9b9fa 100644 --- a/yjit/src/invariants.rs +++ b/yjit/src/invariants.rs @@ -30,7 +30,6 @@ pub struct Invariants { /// quick access to all of the blocks that are making this assumption when /// the operator is redefined. basic_operator_blocks: HashMap<(RedefinitionFlag, ruby_basic_operators), HashSet>, - /// A map from a block to a set of classes and their associated basic /// operators that the block is assuming are not redefined. This is used for /// quick access to all of the assumptions that a block is making when it @@ -48,7 +47,6 @@ pub struct Invariants { /// a constant `A::B` is redefined, then all blocks that are assuming that /// `A` and `B` have not be redefined must be invalidated. constant_state_blocks: HashMap>, - /// A map from a block to a set of IDs that it is assuming have not been /// redefined. block_constant_states: HashMap>, @@ -57,6 +55,9 @@ pub struct Invariants { /// will have no singleton class. When the set is empty, it means that /// there has been a singleton class for the class after boot, so you cannot /// assume no singleton class going forward. + /// For now, the key can be only Array, Hash, or String. Consider making + /// an inverted HashMap if we start using this for user-defined classes + /// to maintain the performance of block_assumptions_free(). no_singleton_classes: HashMap>, /// A map from an ISEQ to a set of blocks that assume base pointer is equal @@ -462,11 +463,15 @@ pub fn block_assumptions_free(blockref: BlockRef) { } // Remove tracking for blocks assuming no singleton class + // NOTE: no_singleton_class has up to 3 keys (Array, Hash, or String) for now. + // This is effectively an O(1) access unless we start using it for more classes. for (_, blocks) in invariants.no_singleton_classes.iter_mut() { blocks.remove(&blockref); } + // Remove tracking for blocks assuming EP doesn't escape - for (_, blocks) in invariants.no_ep_escape_iseqs.iter_mut() { + let iseq = unsafe { blockref.as_ref() }.get_blockid().iseq; + if let Some(blocks) = invariants.no_ep_escape_iseqs.get_mut(&iseq) { blocks.remove(&blockref); } }