diff --git a/NEWS.md b/NEWS.md index 033fc2ea76da1b..784b92b717d1c5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -160,6 +160,12 @@ Note: We're only listing outstanding class updates. Ruby-related constants. This module was reserved in Ruby 3.4 and is now officially defined. [[Feature #20884]] +* Ruby::Box + + * A new (experimental) feature to provide separation about definitions. + For the detail of "Ruby Box", see [doc/language/box.md](doc/language/box.md). + [[Feature #21311]] [[Misc #21385]] + * Set * `Set` is now a core class, instead of an autoloaded stdlib class. @@ -232,8 +238,8 @@ The following default gem is added. The following default gems are updated. -* RubyGems 4.0.0 -* bundler 4.0.0 +* RubyGems 4.0.1 +* bundler 4.0.1 * date 3.5.0 * digest 3.2.1 * english 0.8.1 @@ -427,8 +433,10 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21262]: https://bugs.ruby-lang.org/issues/21262 [Feature #21275]: https://bugs.ruby-lang.org/issues/21275 [Feature #21287]: https://bugs.ruby-lang.org/issues/21287 +[Feature #21311]: https://bugs.ruby-lang.org/issues/21311 [Feature #21347]: https://bugs.ruby-lang.org/issues/21347 [Feature #21360]: https://bugs.ruby-lang.org/issues/21360 +[Misc #21385]: https://bugs.ruby-lang.org/issues/21385 [Feature #21389]: https://bugs.ruby-lang.org/issues/21389 [Feature #21390]: https://bugs.ruby-lang.org/issues/21390 [Feature #21459]: https://bugs.ruby-lang.org/issues/21459 diff --git a/box.c b/box.c index 3fbc4e9fe23a14..e7065c1c298815 100644 --- a/box.c +++ b/box.c @@ -20,6 +20,13 @@ #include +#ifdef HAVE_SYS_SENDFILE_H +# include +#endif +#ifdef HAVE_COPYFILE_H +#include +#endif + VALUE rb_cBox = 0; VALUE rb_cBoxEntry = 0; VALUE rb_mBoxLoader = 0; @@ -518,10 +525,21 @@ sprint_ext_filename(char *str, size_t size, long box_id, const char *prefix, con return snprintf(str, size, "%s%s%sp%"PRI_PIDT_PREFIX"u_%ld_%s", tmp_dir, DIRSEP, prefix, getpid(), box_id, basename); } -#ifdef _WIN32 +enum copy_error_type { + COPY_ERROR_NONE, + COPY_ERROR_SRC_OPEN, + COPY_ERROR_DST_OPEN, + COPY_ERROR_SRC_READ, + COPY_ERROR_DST_WRITE, + COPY_ERROR_SRC_STAT, + COPY_ERROR_DST_CHMOD, + COPY_ERROR_SYSERR +}; + static const char * -copy_ext_file_error(char *message, size_t size) +copy_ext_file_error(char *message, size_t size, int copy_retvalue) { +#ifdef _WIN32 int error = GetLastError(); char *p = message; size_t len = snprintf(message, size, "%d: ", error); @@ -536,120 +554,130 @@ copy_ext_file_error(char *message, size_t size) if (*p == '\n' || *p == '\r') *p = ' '; } - return message; -} #else -static const char * -copy_ext_file_error(char *message, size_t size, int copy_retvalue, const char *src_path, const char *dst_path) -{ switch (copy_retvalue) { - case 1: - snprintf(message, size, "can't open the extension path: %s", src_path); + case COPY_ERROR_SRC_OPEN: + strlcpy(message, "can't open the extension path", size); + break; + case COPY_ERROR_DST_OPEN: + strlcpy(message, "can't open the file to write", size); break; - case 2: - snprintf(message, size, "can't open the file to write: %s", dst_path); + case COPY_ERROR_SRC_READ: + strlcpy(message, "failed to read the extension path", size); break; - case 3: - snprintf(message, size, "failed to read the extension path: %s", src_path); + case COPY_ERROR_DST_WRITE: + strlcpy(message, "failed to write the extension path", size); break; - case 4: - snprintf(message, size, "failed to write the extension path: %s", dst_path); + case COPY_ERROR_SRC_STAT: + strlcpy(message, "failed to stat the extension path to copy permissions", size); break; - case 5: - snprintf(message, size, "failed to stat the extension path to copy permissions: %s", src_path); + case COPY_ERROR_DST_CHMOD: + strlcpy(message, "failed to set permissions to the copied extension path", size); break; - case 6: - snprintf(message, size, "failed to set permissions to the copied extension path: %s", dst_path); + case COPY_ERROR_SYSERR: + strlcpy(message, strerror(errno), size); break; + case COPY_ERROR_NONE: /* shouldn't be called */ default: rb_bug("unknown return value of copy_ext_file: %d", copy_retvalue); } +#endif return message; } + +#ifndef _WIN32 +static enum copy_error_type +copy_stream(int src_fd, int dst_fd) +{ + char buffer[1024]; + ssize_t rsize; + + while ((rsize = read(src_fd, buffer, sizeof(buffer))) != 0) { + if (rsize < 0) return COPY_ERROR_SRC_READ; + for (size_t written = 0; written < (size_t)rsize;) { + ssize_t wsize = write(dst_fd, buffer+written, rsize-written); + if (wsize < 0) return COPY_ERROR_DST_WRITE; + written += (size_t)wsize; + } + } + return COPY_ERROR_NONE; +} #endif -static int +static enum copy_error_type copy_ext_file(const char *src_path, const char *dst_path) { #if defined(_WIN32) - int rvalue; - WCHAR *w_src = rb_w32_mbstr_to_wstr(CP_UTF8, src_path, -1, NULL); WCHAR *w_dst = rb_w32_mbstr_to_wstr(CP_UTF8, dst_path, -1, NULL); if (!w_src || !w_dst) { + free(w_src); + free(w_dst); rb_memerror(); } - rvalue = CopyFileW(w_src, w_dst, FALSE) ? 0 : 1; + enum copy_error_type rvalue = CopyFileW(w_src, w_dst, TRUE) ? + COPY_ERROR_NONE : COPY_ERROR_SYSERR; free(w_src); free(w_dst); return rvalue; #else - FILE *src, *dst; - char buffer[1024]; - size_t read = 0, wrote, written = 0; - size_t maxread = sizeof(buffer); - int eof = 0; - int clean_read = 1; - int retvalue = 0; - - src = fopen(src_path, "rb"); - if (!src) { - return 1; +# ifdef O_BINARY + const int bin = O_BINARY; +# else + const int bin = 0; +# endif + const int src_fd = open(src_path, O_RDONLY|bin); + if (src_fd < 0) return COPY_ERROR_SRC_OPEN; + + struct stat src_st; + if (fstat(src_fd, &src_st)) { + close(src_fd); + return COPY_ERROR_SRC_STAT; } - dst = fopen(dst_path, "wb"); - if (!dst) { - return 2; + + const int dst_fd = open(dst_path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|bin, S_IRWXU); + if (dst_fd < 0) { + close(src_fd); + return COPY_ERROR_DST_OPEN; } - while (!eof) { - if (clean_read) { - read = fread(buffer, 1, sizeof(buffer), src); - written = 0; - } - if (read > 0) { - wrote = fwrite(buffer+written, 1, read-written, dst); - if (wrote < read-written) { - if (ferror(dst)) { - retvalue = 4; - break; - } - else { // partial write - clean_read = 0; - written += wrote; - } - } - else { // Wrote the entire buffer to dst, next read is clean one - clean_read = 1; - } - } - if (read < maxread) { - if (clean_read && feof(src)) { - // If it's not clean, buffer should have bytes not written yet. - eof = 1; - } - else if (ferror(src)) { - retvalue = 3; - // Writes could be partial/dirty, but this load is failure anyway - break; - } - } + + enum copy_error_type ret = COPY_ERROR_NONE; + + if (fchmod(dst_fd, src_st.st_mode & 0777)) { + ret = COPY_ERROR_DST_CHMOD; + goto done; } - fclose(src); - fclose(dst); -#if defined(__CYGWIN__) - // On Cygwin, CopyFile-like operations may strip executable bits. - // Explicitly match destination file permissions to source. - if (retvalue == 0) { - struct stat st; - if (stat(src_path, &st) != 0) { - retvalue = 5; - } - else if (chmod(dst_path, st.st_mode & 0777) != 0) { - retvalue = 6; - } + + const size_t count_max = (SIZE_MAX >> 1) + 1; + (void)count_max; + +# ifdef HAVE_COPY_FILE_RANGE + for (;;) { + ssize_t written = copy_file_range(src_fd, NULL, dst_fd, NULL, count_max, 0); + if (written == 0) goto done; + if (written < 0) break; } -#endif - return retvalue; +# endif +# ifdef HAVE_FCOPYFILE + if (fcopyfile(src_fd, dst_fd, NULL, COPYFILE_DATA) == 0) { + goto done; + } +# endif +# ifdef USE_SENDFILE + for (;;) { + ssize_t written = sendfile(src_fd, dst_fd, NULL count_max); + if (written == 0) goto done; + if (written < 0) break; + } +# endif + ret = copy_stream(src_fd, dst_fd); + + done: + close(src_fd); + if (dst_fd >= 0) close(dst_fd); + if (ret != COPY_ERROR_NONE) unlink(dst_path); + return ret; #endif } @@ -700,7 +728,7 @@ VALUE rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path) { char ext_path[MAXPATHLEN], fname2[MAXPATHLEN], basename[MAXPATHLEN]; - int copy_error, wrote; + int wrote; const char *src_path = RSTRING_PTR(path), *fname_ptr = RSTRING_PTR(fname); rb_box_t *box = rb_get_box_t(box_value); @@ -711,15 +739,11 @@ rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path) if (wrote >= (int)sizeof(ext_path)) { rb_bug("Extension file path in the box was too long"); } - copy_error = copy_ext_file(src_path, ext_path); + enum copy_error_type copy_error = copy_ext_file(src_path, ext_path); if (copy_error) { char message[1024]; -#if defined(_WIN32) - copy_ext_file_error(message, sizeof(message)); -#else - copy_ext_file_error(message, sizeof(message), copy_error, src_path, ext_path); -#endif - rb_raise(rb_eLoadError, "can't prepare the extension file for Ruby Box (%s from %s): %s", ext_path, src_path, message); + copy_ext_file_error(message, sizeof(message), copy_error); + rb_raise(rb_eLoadError, "can't prepare the extension file for Ruby Box (%s from %"PRIsVALUE"): %s", ext_path, path, message); } // TODO: register the path to be clean-uped return rb_str_new_cstr(ext_path); diff --git a/concurrent_set.c b/concurrent_set.c index e48d9a46e89d3f..3aa61507aaa7d2 100644 --- a/concurrent_set.c +++ b/concurrent_set.c @@ -129,7 +129,7 @@ concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr) new_capacity = old_capacity; } - // May cause GC and therefore deletes, so must hapen first. + // May cause GC and therefore deletes, so must happen first. VALUE new_set_obj = rb_concurrent_set_new(old_set->funcs, new_capacity); struct concurrent_set *new_set = RTYPEDDATA_GET_DATA(new_set_obj); diff --git a/doc/language/box.md b/doc/language/box.md index ed62cbd072dcff..928c98eb3c32bb 100644 --- a/doc/language/box.md +++ b/doc/language/box.md @@ -1,6 +1,6 @@ # Ruby Box - Ruby's in-process separation of Classes and Modules -Ruby Box is designed to provide separated spaces in a Ruby process, to isolate applications and libraries. +Ruby Box is designed to provide separated spaces in a Ruby process, to isolate application codes, libraries and monkey patches. ## Known issues @@ -12,13 +12,11 @@ Ruby Box is designed to provide separated spaces in a Ruby process, to isolate a ## TODOs * Add the loaded namespace on iseq to check if another namespace tries running the iseq (add a field only when VM_CHECK_MODE?) -* Delete per-box extension files (.so) lazily or process exit -* Collect `rb_classext_t` entries for a box when GC collects the box +* Delete per-box extension files (.so) lazily or process exit (on Windows) * Assign its own TOPLEVEL_BINDING in boxes * Fix calling `warn` in boxes to refer `$VERBOSE` and `Warning.warn` in the box -* Make an internal data container `Ruby::Box::Entry` invisible +* Make an internal data container class `Ruby::Box::Entry` invisible * More test cases about `$LOAD_PATH` and `$LOADED_FEATURES` -* Return classpath and nesting without the namespace prefix in the namespace itself [#21316](https://bugs.ruby-lang.org/issues/21316), [#21318](https://bugs.ruby-lang.org/issues/21318) ## How to use diff --git a/gc.c b/gc.c index 5e3e44e726704d..79eec5d96bf7af 100644 --- a/gc.c +++ b/gc.c @@ -1816,7 +1816,7 @@ id2ref_tbl_mark(void *data) // It's very unlikely, but if enough object ids were generated, keys may be T_BIGNUM rb_mark_set(table); } - // We purposedly don't mark values, as they are weak references. + // We purposely don't mark values, as they are weak references. // rb_gc_obj_free_vm_weak_references takes care of cleaning them up. } diff --git a/gc.rb b/gc.rb index ccad5ef2c1dbfa..59adcbc62f64d6 100644 --- a/gc.rb +++ b/gc.rb @@ -37,7 +37,7 @@ module GC # interleaved with program execution both before the method returns and afterward; # therefore sweeping may not be completed before the return. # - # Note that these keword arguments are implementation- and version-dependent, + # Note that these keyword arguments are implementation- and version-dependent, # are not guaranteed to be future-compatible, # and may be ignored in some implementations. def self.start full_mark: true, immediate_mark: true, immediate_sweep: true diff --git a/jit.c b/jit.c index ae592b2205c0be..ff44ac5b2e565d 100644 --- a/jit.c +++ b/jit.c @@ -761,6 +761,12 @@ rb_jit_fix_mod_fix(VALUE recv, VALUE obj) return rb_fix_mod_fix(recv, obj); } +VALUE +rb_jit_fix_div_fix(VALUE recv, VALUE obj) +{ + return rb_fix_div_fix(recv, obj); +} + // YJIT/ZJIT need this function to never allocate and never raise VALUE rb_yarv_str_eql_internal(VALUE str1, VALUE str2) diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 8cdc3067366a8e..411829b28a3d35 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "4.0.0".freeze + VERSION = "4.0.1".freeze def self.bundler_major_version @bundler_major_version ||= gem_version.segments.first diff --git a/lib/erb/util.rb b/lib/erb/util.rb index adeb695aa81fec..d7d69eb4f159a3 100644 --- a/lib/erb/util.rb +++ b/lib/erb/util.rb @@ -9,7 +9,7 @@ require 'cgi/escape' # Load or define ERB::Escape#html_escape. -# We don't build the C extention 'cgi/escape' for JRuby, TruffleRuby, and WASM. +# We don't build the C extension 'cgi/escape' for JRuby, TruffleRuby, and WASM. # miniruby (used by CRuby build scripts) also fails to load erb/escape.so. begin require 'erb/escape' diff --git a/lib/net/http.rb b/lib/net/http.rb index 8a4ff481872abc..c63bdddcad621e 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1323,7 +1323,7 @@ def response_body_encoding=(value) # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_pass - # Sets wheter the proxy uses SSL; + # Sets whether the proxy uses SSL; # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_use_ssl @@ -1772,26 +1772,24 @@ def connect end private :connect + tcp_socket_parameters = TCPSocket.instance_method(:initialize).parameters + TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT = if tcp_socket_parameters != [[:rest]] + tcp_socket_parameters.include?([:key, :open_timeout]) + else + # Use Socket.tcp to find out since there is no parameters information for TCPSocket#initialize + # See discussion in https://github.com/ruby/net-http/pull/224 + Socket.method(:tcp).parameters.include?([:key, :open_timeout]) + end + private_constant :TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT + def timeouted_connect(conn_addr, conn_port) - if @tcpsocket_supports_open_timeout == nil || @tcpsocket_supports_open_timeout == true - # Try to use built-in open_timeout in TCPSocket.open if: - # - The current Ruby runtime is known to support it, or - # - It is unknown whether the current Ruby runtime supports it (so we'll try). - begin - sock = TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) - @tcpsocket_supports_open_timeout = true - return sock - rescue ArgumentError => e - raise if !(e.message.include?('unknown keyword: :open_timeout') || e.message.include?('wrong number of arguments (given 5, expected 2..4)')) - @tcpsocket_supports_open_timeout = false - end + if TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) + else + Timeout.timeout(@open_timeout, Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } end - - # This Ruby runtime is known not to support `TCPSocket(open_timeout:)`. - # Directly fall back to Timeout.timeout to avoid performance penalty incured by rescue. - Timeout.timeout(@open_timeout, Net::OpenTimeout) { - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - } end private :timeouted_connect diff --git a/lib/rubygems.rb b/lib/rubygems.rb index eaeffc7a54d00b..55e727425fbe4d 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "4.0.0" + VERSION = "4.0.1" end require_relative "rubygems/defaults" diff --git a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock index 92b16c76064450..866a77125bcaed 100644 --- a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock @@ -46,4 +46,4 @@ DEPENDENCIES tapioca BUNDLED WITH - 4.0.0 + 4.0.1 diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock index 90090e5fbfdc7e..67c887c84c2554 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock @@ -36,4 +36,4 @@ DEPENDENCIES warbler! BUNDLED WITH - 4.0.0 + 4.0.1 diff --git a/string.c b/string.c index 9327306384a232..52d1f28cc1443f 100644 --- a/string.c +++ b/string.c @@ -3979,7 +3979,7 @@ rb_str_append_as_bytes(int argc, VALUE *argv, VALUE str) clear_cr: // If no fast path was hit, we clear the coderange. - // append_as_bytes is predominently meant to be used in + // append_as_bytes is predominantly meant to be used in // buffering situation, hence it's likely the coderange // will never be scanned, so it's not worth spending time // precomputing the coderange except for simple and common diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 03d2461fa0a0af..cc5143aee52b0b 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -960,6 +960,37 @@ def test(n) = C * n }, call_threshold: 2, insns: [:opt_mult] end + def test_fixnum_div + assert_compiles '12', %q{ + C = 48 + def test(n) = C / n + test(4) + test(4) + }, call_threshold: 2, insns: [:opt_div] + end + + def test_fixnum_floor + assert_compiles '0', %q{ + C = 3 + def test(n) = C / n + test(4) + test(4) + }, call_threshold: 2, insns: [:opt_div] + end + + def test_fixnum_div_zero + assert_runs '"divided by 0"', %q{ + def test(n) + n / 0 + rescue ZeroDivisionError => e + e.message + end + + test(0) + test(0) + }, call_threshold: 2, insns: [:opt_div] + end + def test_opt_not assert_compiles('[true, true, false]', <<~RUBY, insns: [:opt_not]) def test(obj) = !obj diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index 4cc6655e43d631..45062e42253876 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -129,4 +129,4 @@ CHECKSUMS turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH - 4.0.0 + 4.0.1 diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index 77bdc4b6f90a2a..b45717d4e3eaca 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -156,4 +156,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.0 + 4.0.1 diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index 354a57d660e99b..9fff0eb0fcdc89 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -176,4 +176,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.0 + 4.0.1 diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index b11a9607255aa1..ac581de02e7fd5 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -103,4 +103,4 @@ CHECKSUMS tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770 BUNDLED WITH - 4.0.0 + 4.0.1 diff --git a/yjit.c b/yjit.c index 16debf6eca5555..6d909a0da61ee3 100644 --- a/yjit.c +++ b/yjit.c @@ -292,12 +292,6 @@ rb_yjit_rb_ary_subseq_length(VALUE ary, long beg) return rb_ary_subseq(ary, beg, len); } -VALUE -rb_yjit_fix_div_fix(VALUE recv, VALUE obj) -{ - return rb_fix_div_fix(recv, obj); -} - // Return non-zero when `obj` is an array and its last item is a // `ruby2_keywords` hash. We don't support this kind of splat. size_t diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 8d11b4216ef0b0..06c475f3c8b493 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -368,7 +368,7 @@ fn main() { .allowlist_function("rb_str_neq_internal") .allowlist_function("rb_yarv_ary_entry_internal") .allowlist_function("rb_yjit_ruby2_keywords_splat_p") - .allowlist_function("rb_yjit_fix_div_fix") + .allowlist_function("rb_jit_fix_div_fix") .allowlist_function("rb_jit_fix_mod_fix") .allowlist_function("rb_FL_TEST") .allowlist_function("rb_FL_TEST_RAW") diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index 5562f73be26dab..6e6a1810c67dda 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -197,7 +197,7 @@ pub use rb_get_cikw_keywords_idx as get_cikw_keywords_idx; pub use rb_get_call_data_ci as get_call_data_ci; pub use rb_yarv_str_eql_internal as rb_str_eql_internal; pub use rb_yarv_ary_entry_internal as rb_ary_entry_internal; -pub use rb_yjit_fix_div_fix as rb_fix_div_fix; +pub use rb_jit_fix_div_fix as rb_fix_div_fix; pub use rb_jit_fix_mod_fix as rb_fix_mod_fix; pub use rb_FL_TEST as FL_TEST; pub use rb_FL_TEST_RAW as FL_TEST_RAW; diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index b9e5fb3ab12843..d2347671bbacb8 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1174,7 +1174,6 @@ extern "C" { pub fn rb_str_neq_internal(str1: VALUE, str2: VALUE) -> VALUE; pub fn rb_ary_unshift_m(argc: ::std::os::raw::c_int, argv: *mut VALUE, ary: VALUE) -> VALUE; pub fn rb_yjit_rb_ary_subseq_length(ary: VALUE, beg: ::std::os::raw::c_long) -> VALUE; - pub fn rb_yjit_fix_div_fix(recv: VALUE, obj: VALUE) -> VALUE; pub fn rb_yjit_ruby2_keywords_splat_p(obj: VALUE) -> usize; pub fn rb_yjit_splat_varg_checks( sp: *mut VALUE, @@ -1312,6 +1311,7 @@ extern "C" { end: *mut ::std::os::raw::c_void, ); pub fn rb_jit_fix_mod_fix(recv: VALUE, obj: VALUE) -> VALUE; + pub fn rb_jit_fix_div_fix(recv: VALUE, obj: VALUE) -> VALUE; pub fn rb_yarv_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE; pub fn rb_jit_str_concat_codepoint(str_: VALUE, codepoint: VALUE); } diff --git a/zjit.c b/zjit.c index d62c5c58c6334c..7731bc908f6ab9 100644 --- a/zjit.c +++ b/zjit.c @@ -103,7 +103,7 @@ rb_zjit_exit_locations_dict(VALUE *zjit_raw_samples, int *zjit_line_samples, int // Loop through the length of samples_len and add data to the // frames hash. Also push the current value onto the raw_samples - // and line_samples arrary respectively. + // and line_samples array respectively. for (int o = 0; o < num; o++) { rb_zjit_add_frame(frames, zjit_raw_samples[idx]); rb_ary_push(raw_samples, SIZET2NUM(zjit_raw_samples[idx])); diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 30e85149744e22..7b968d14bbb678 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -279,6 +279,7 @@ fn main() { .allowlist_function("rb_jit_mark_unused") .allowlist_function("rb_jit_get_page_size") .allowlist_function("rb_jit_array_len") + .allowlist_function("rb_jit_fix_div_fix") .allowlist_function("rb_jit_iseq_builtin_attrs") .allowlist_function("rb_jit_str_concat_codepoint") .allowlist_function("rb_zjit_iseq_inspect") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 73c092f72091c5..642991a4381265 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -396,6 +396,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)), Insn::FixnumSub { left, right, state } => gen_fixnum_sub(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)), Insn::FixnumMult { left, right, state } => gen_fixnum_mult(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)), + Insn::FixnumDiv { left, right, state } => gen_fixnum_div(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)), Insn::FixnumEq { left, right } => gen_fixnum_eq(asm, opnd!(left), opnd!(right)), Insn::FixnumNeq { left, right } => gen_fixnum_neq(asm, opnd!(left), opnd!(right)), Insn::FixnumLt { left, right } => gen_fixnum_lt(asm, opnd!(left), opnd!(right)), @@ -484,7 +485,6 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::ArrayHash { elements, state } => gen_opt_newarray_hash(jit, asm, opnds!(elements), &function.frame_state(*state)), &Insn::IsA { val, class } => gen_is_a(asm, opnd!(val), opnd!(class)), &Insn::ArrayMax { state, .. } - | &Insn::FixnumDiv { state, .. } | &Insn::Throw { state, .. } => return Err(state), }; @@ -1704,6 +1704,16 @@ fn gen_fixnum_mult(jit: &mut JITState, asm: &mut Assembler, left: lir::Opnd, rig asm.add(out_val, Opnd::UImm(1)) } +/// Compile Fixnum / Fixnum +fn gen_fixnum_div(jit: &mut JITState, asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd, state: &FrameState) -> lir::Opnd { + gen_prepare_leaf_call_with_gc(asm, state); + + // Side exit if rhs is 0 + asm.cmp(right, Opnd::from(VALUE::fixnum_from_usize(0))); + asm.je(side_exit(jit, state, FixnumDivByZero)); + asm_ccall!(asm, rb_jit_fix_div_fix, left, right) +} + /// Compile Fixnum == Fixnum fn gen_fixnum_eq(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> lir::Opnd { asm.cmp(left, right); diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index aed35c3c636846..c436e20087ebbe 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -2198,6 +2198,7 @@ unsafe extern "C" { start: *mut ::std::os::raw::c_void, end: *mut ::std::os::raw::c_void, ); + pub fn rb_jit_fix_div_fix(recv: VALUE, obj: VALUE) -> VALUE; pub fn rb_yarv_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE; pub fn rb_jit_str_concat_codepoint(str_: VALUE, codepoint: VALUE); pub fn rb_jit_shape_capacity(shape_id: shape_id_t) -> attr_index_t; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4323b145feb1b6..abf48e04d64be6 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -501,6 +501,7 @@ pub enum SideExitReason { BlockParamProxyNotIseqOrIfunc, StackOverflow, FixnumModByZero, + FixnumDivByZero, BoxFixnumOverflow, } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 7a076b7cda2550..2272404b690b37 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -183,6 +183,7 @@ make_counters! { exit_fixnum_mult_overflow, exit_fixnum_lshift_overflow, exit_fixnum_mod_by_zero, + exit_fixnum_div_by_zero, exit_box_fixnum_overflow, exit_guard_type_failure, exit_guard_type_not_failure, @@ -492,6 +493,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { FixnumMultOverflow => exit_fixnum_mult_overflow, FixnumLShiftOverflow => exit_fixnum_lshift_overflow, FixnumModByZero => exit_fixnum_mod_by_zero, + FixnumDivByZero => exit_fixnum_div_by_zero, BoxFixnumOverflow => exit_box_fixnum_overflow, GuardType(_) => exit_guard_type_failure, GuardTypeNot(_) => exit_guard_type_not_failure,