From a796ea8d0d4b4d61851b6411864537d4e0201476 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 29 Sep 2023 13:29:59 +0200 Subject: [PATCH 01/50] Fix warning in encoding_spec.c --- spec/ruby/optional/capi/ext/encoding_spec.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/ruby/optional/capi/ext/encoding_spec.c b/spec/ruby/optional/capi/ext/encoding_spec.c index 0ebbc9d75a81..67594fce81ce 100644 --- a/spec/ruby/optional/capi/ext/encoding_spec.c +++ b/spec/ruby/optional/capi/ext/encoding_spec.c @@ -271,12 +271,15 @@ static VALUE encoding_spec_rb_enc_str_asciionly_p(VALUE self, VALUE str) { } } +RBIMPL_WARNING_PUSH() +RBIMPL_WARNING_IGNORED(-Wformat-security) static VALUE encoding_spec_rb_enc_raise(VALUE self, VALUE encoding, VALUE exception_class, VALUE format) { rb_encoding *e = rb_to_encoding(encoding); const char *f = RSTRING_PTR(format); rb_enc_raise(e, exception_class, f); } +RBIMPL_WARNING_POP() static VALUE encoding_spec_rb_uv_to_utf8(VALUE self, VALUE buf, VALUE num) { int len = rb_uv_to_utf8(RSTRING_PTR(buf), NUM2INT(num)); From 08d1c4c18f32b25580ae427bd8baa0db3b77e905 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 29 Sep 2023 13:41:53 +0200 Subject: [PATCH 02/50] Use the original definitions for rb_fix2long() and RB_FIXNUM_P() --- lib/cext/include/ruby/internal/arithmetic/long.h | 7 ------- lib/cext/include/ruby/internal/special_consts.h | 3 +-- lib/truffle/truffle/cext.rb | 4 ---- src/main/c/cext/integer.c | 4 ---- 4 files changed, 1 insertion(+), 17 deletions(-) diff --git a/lib/cext/include/ruby/internal/arithmetic/long.h b/lib/cext/include/ruby/internal/arithmetic/long.h index 40cc9714f5f0..2c2ffcf868c0 100644 --- a/lib/cext/include/ruby/internal/arithmetic/long.h +++ b/lib/cext/include/ruby/internal/arithmetic/long.h @@ -231,10 +231,7 @@ rbimpl_right_shift_is_arithmetic_p(void) } RBIMPL_ATTR_CONST_UNLESS_DEBUG() -#ifndef TRUFFLERUBY RBIMPL_ATTR_CONSTEXPR_UNLESS_DEBUG(CXX14) -#endif - /** * Converts a Fixnum into C's `long`. * @@ -245,16 +242,12 @@ RBIMPL_ATTR_CONSTEXPR_UNLESS_DEBUG(CXX14) static inline long rb_fix2long(VALUE x) { -#ifdef TRUFFLERUBY - return ((long)polyglot_as_i64(rb_tr_unwrap(x))); -#else if /* constexpr */ (rbimpl_right_shift_is_arithmetic_p()) { return rbimpl_fix2long_by_shift(x); } else { return rbimpl_fix2long_by_idiv(x); } -#endif } RBIMPL_ATTR_CONST_UNLESS_DEBUG() diff --git a/lib/cext/include/ruby/internal/special_consts.h b/lib/cext/include/ruby/internal/special_consts.h index 8147d6a08a61..4223ead1e5d5 100644 --- a/lib/cext/include/ruby/internal/special_consts.h +++ b/lib/cext/include/ruby/internal/special_consts.h @@ -33,7 +33,6 @@ #ifdef TRUFFLERUBY RBIMPL_SYMBOL_EXPORT_BEGIN() -bool RB_FIXNUM_P(VALUE value); bool rb_tr_special_const_p(VALUE object); RBIMPL_SYMBOL_EXPORT_END() #endif @@ -238,6 +237,7 @@ RB_NIL_OR_UNDEF_P(VALUE obj) const VALUE common_bits = RUBY_Qundef & RUBY_Qnil; return (obj & mask) == common_bits; } +#endif RBIMPL_ATTR_CONST() RBIMPL_ATTR_CONSTEXPR(CXX11) @@ -256,7 +256,6 @@ RB_FIXNUM_P(VALUE obj) { return obj & RUBY_FIXNUM_FLAG; } -#endif RBIMPL_ATTR_CONST() RBIMPL_ATTR_CONSTEXPR(CXX14) diff --git a/lib/truffle/truffle/cext.rb b/lib/truffle/truffle/cext.rb index 782362d4f16f..57f9b04df458 100644 --- a/lib/truffle/truffle/cext.rb +++ b/lib/truffle/truffle/cext.rb @@ -306,10 +306,6 @@ def rb_fix2str(value, base) value.to_s(base) end - def RB_FIXNUM_P(value) - Truffle::Type.fits_into_long?(value) - end - def RB_FLOAT_TYPE_P(value) Primitive.is_a?(value, Float) end diff --git a/src/main/c/cext/integer.c b/src/main/c/cext/integer.c index 29b324ece685..143c25f6bf8d 100644 --- a/src/main/c/cext/integer.c +++ b/src/main/c/cext/integer.c @@ -12,10 +12,6 @@ // Integer, rb_integer_*, rb_*int*, rb_big_* -bool RB_FIXNUM_P(VALUE value) { - return polyglot_as_boolean(RUBY_CEXT_INVOKE_NO_WRAP("RB_FIXNUM_P", value)); -} - VALUE rb_Integer(VALUE value) { return RUBY_CEXT_INVOKE("rb_Integer", value); } From ee59f5eb5b85f6903976b9324bea629626d931cd Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 20 Oct 2023 11:16:32 +0200 Subject: [PATCH 03/50] Add specs of rb_iterate() back to ensure it is tested --- spec/ruby/optional/capi/array_spec.rb | 34 +++++++++++++++++ spec/ruby/optional/capi/ext/array_spec.c | 47 ++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/spec/ruby/optional/capi/array_spec.rb b/spec/ruby/optional/capi/array_spec.rb index 8e90980c6a55..9c35017e211e 100644 --- a/spec/ruby/optional/capi/array_spec.rb +++ b/spec/ruby/optional/capi/array_spec.rb @@ -343,6 +343,40 @@ end end + describe "rb_iterate" do + it "calls an callback function as a block passed to an method" do + s = [1,2,3,4] + s2 = @s.rb_iterate(s) + + s2.should == s + + # Make sure they're different objects + s2.equal?(s).should be_false + end + + it "calls a function with the other function available as a block" do + h = {a: 1, b: 2} + + @s.rb_iterate_each_pair(h).sort.should == [1,2] + end + + it "calls a function which can yield into the original block" do + s2 = [] + + o = Object.new + def o.each + yield 1 + yield 2 + yield 3 + yield 4 + end + + @s.rb_iterate_then_yield(o) { |x| s2 << x } + + s2.should == [1,2,3,4] + end + end + describe "rb_block_call" do it "calls an callback function as a block passed to an method" do s = [1,2,3,4] diff --git a/spec/ruby/optional/capi/ext/array_spec.c b/spec/ruby/optional/capi/ext/array_spec.c index 9386239813ea..59d3f09986c2 100644 --- a/spec/ruby/optional/capi/ext/array_spec.c +++ b/spec/ruby/optional/capi/ext/array_spec.c @@ -196,6 +196,25 @@ static VALUE copy_ary(RB_BLOCK_CALL_FUNC_ARGLIST(el, new_ary)) { return rb_ary_push(new_ary, el); } +// Suppress deprecations warnings for rb_iterate(), we want to test it while it exists +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(__clang__) && defined(__has_warning) +# if __has_warning("-Wdeprecated-declarations") +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +# endif +#endif + +static VALUE array_spec_rb_iterate(VALUE self, VALUE ary) { + VALUE new_ary = rb_ary_new(); + + rb_iterate(rb_each, ary, copy_ary, new_ary); + + return new_ary; +} + static VALUE array_spec_rb_block_call(VALUE self, VALUE ary) { VALUE new_ary = rb_ary_new(); @@ -208,6 +227,18 @@ static VALUE sub_pair(RB_BLOCK_CALL_FUNC_ARGLIST(el, holder)) { return rb_ary_push(holder, rb_ary_entry(el, 1)); } +static VALUE each_pair(VALUE obj) { + return rb_funcall(obj, rb_intern("each_pair"), 0); +} + +static VALUE array_spec_rb_iterate_each_pair(VALUE self, VALUE obj) { + VALUE new_ary = rb_ary_new(); + + rb_iterate(each_pair, obj, sub_pair, new_ary); + + return new_ary; +} + static VALUE array_spec_rb_block_call_each_pair(VALUE self, VALUE obj) { VALUE new_ary = rb_ary_new(); @@ -221,11 +252,24 @@ static VALUE iter_yield(RB_BLOCK_CALL_FUNC_ARGLIST(el, ary)) { return Qnil; } +static VALUE array_spec_rb_iterate_then_yield(VALUE self, VALUE obj) { + rb_iterate(rb_each, obj, iter_yield, obj); + return Qnil; +} + static VALUE array_spec_rb_block_call_then_yield(VALUE self, VALUE obj) { rb_block_call(obj, rb_intern("each"), 0, 0, iter_yield, obj); return Qnil; } +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic pop +#elif defined(__clang__) && defined(__has_warning) +# if __has_warning("-Wdeprecated-declarations") +# pragma clang diagnostic pop +# endif +#endif + static VALUE array_spec_rb_mem_clear(VALUE self, VALUE obj) { VALUE ary[1]; ary[0] = obj; @@ -283,6 +327,9 @@ void Init_array_spec(void) { rb_define_method(cls, "rb_ary_plus", array_spec_rb_ary_plus, 2); rb_define_method(cls, "rb_ary_unshift", array_spec_rb_ary_unshift, 2); rb_define_method(cls, "rb_assoc_new", array_spec_rb_assoc_new, 2); + rb_define_method(cls, "rb_iterate", array_spec_rb_iterate, 1); + rb_define_method(cls, "rb_iterate_each_pair", array_spec_rb_iterate_each_pair, 1); + rb_define_method(cls, "rb_iterate_then_yield", array_spec_rb_iterate_then_yield, 1); rb_define_method(cls, "rb_block_call", array_spec_rb_block_call, 1); rb_define_method(cls, "rb_block_call_each_pair", array_spec_rb_block_call_each_pair, 1); rb_define_method(cls, "rb_block_call_then_yield", array_spec_rb_block_call_then_yield, 1); From d8d3daf8e2b5840c36c9122cee82656a5fcaaa28 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 21 Sep 2023 18:45:58 +0200 Subject: [PATCH 04/50] Run C extensions natively * libtruffleruby.so is still run on Sulong to limit the changes needed. * libtrufflerubytrampoline.so is a new library which declares C API functions in native and forwards to Sulong. * st.c is copied to libtrufflerubytrampoline.so since it does not need to run on Sulong. * Define TRUFFLERUBY_ABI_VERSION in headers directly for simplicity and reliability, trampoline.c and st.c need it but have no easy way to get the value from Ruby. * Remove dependencies on polyglot.h in public extension headers, since that functionality is not available when not running on Sulong. --- .gitignore | 1 + doc/contributor/how-to-guide.md | 4 +- doc/contributor/updating-ruby.md | 2 +- lib/cext/ABI_version.txt | 1 - lib/cext/include/internal/bignum.h | 6 + lib/cext/include/internal/compile.h | 5 + lib/cext/include/internal/imemo.h | 4 + .../include/ruby/internal/arithmetic/long.h | 4 - lib/cext/include/ruby/internal/config.h | 2 +- lib/cext/include/ruby/internal/core/rarray.h | 2 +- lib/cext/include/ruby/internal/core/rbasic.h | 16 +- lib/cext/include/ruby/internal/core/rdata.h | 15 +- lib/cext/include/ruby/internal/core/rfile.h | 9 +- lib/cext/include/ruby/internal/core/rstring.h | 22 +- .../include/ruby/internal/core/rtypeddata.h | 18 +- .../include/ruby/internal/encoding/sprintf.h | 27 + lib/cext/include/ruby/internal/error.h | 66 + lib/cext/include/ruby/internal/fl_type.h | 22 +- lib/cext/include/ruby/internal/intern/array.h | 14 + lib/cext/include/ruby/internal/intern/gc.h | 6 + .../include/ruby/internal/intern/sprintf.h | 34 + .../include/ruby/internal/intern/struct.h | 42 + lib/cext/include/ruby/internal/intern/time.h | 36 + lib/cext/include/ruby/internal/iterator.h | 28 + lib/cext/include/ruby/internal/symbol.h | 6 - lib/cext/include/ruby/internal/value.h | 8 - lib/cext/include/ruby/internal/value_type.h | 16 +- lib/cext/include/ruby/io.h | 12 +- lib/cext/include/ruby/ruby.h | 8 +- .../truffleruby/truffleruby-abi-version.h | 15 + .../include/truffleruby/truffleruby-pre.h | 42 +- lib/cext/include/truffleruby/truffleruby.h | 236 +- lib/gems/gems/debug-1.7.1/ext/debug/debug.c | 2 + .../debug-1.7.1/ext/debug/iseq_collector.c | 4 + lib/mri/mkmf.rb | 2 +- lib/truffle/rbconfig.rb | 6 +- lib/truffle/truffle/cext.rb | 276 +- lib/truffle/truffle/cext_constants.rb | 6 +- lib/truffle/truffle/cext_ruby.rb | 13 +- lib/truffle/truffle/cext_structs.rb | 351 +-- mx.truffleruby/suite.py | 3 +- spec/ruby/optional/capi/ext/rbasic_spec.c | 54 +- spec/ruby/optional/capi/ext/typed_data_spec.c | 12 + spec/ruby/optional/capi/rbasic_spec.rb | 2 + spec/ruby/optional/capi/shared/rbasic.rb | 1 - spec/ruby/optional/capi/typed_data_spec.rb | 12 + spec/tags/optional/capi/encoding_tags.txt | 2 - spec/tags/optional/capi/fiber_tags.txt | 2 - spec/tags/optional/capi/gc_tags.txt | 1 - spec/tags/optional/capi/thread_tags.txt | 1 + spec/tags/truffle/capi/unimplemented_tags.txt | 1 + spec/truffle/capi/cext_lock_spec.rb | 2 +- .../ext/truffleruby_foreign_caller_spec.c | 2 +- spec/truffle/capi/ext/truffleruby_lock_spec.c | 11 +- .../capi/ext/truffleruby_rbasic_spec.c | 14 +- .../capi/ext/truffleruby_string_spec.c | 8 +- .../capi/ext/truffleruby_symbol_id_spec.c | 8 +- spec/truffle/capi/ext/unimplemented_spec.c | 2 +- spec/truffle/capi/foreign_caller_spec.rb | 4 +- spec/truffle/capi/rbasic_spec.rb | 2 +- spec/truffle/capi/string_spec.rb | 13 - spec/truffle/capi/unimplemented_spec.rb | 13 +- src/main/c/Makefile | 22 +- src/main/c/cext-trampoline/Makefile | 26 + src/main/c/cext-trampoline/cext_constants.c | 173 ++ src/main/c/cext-trampoline/st.c | 2253 +++++++++++++++++ src/main/c/cext/args.c | 12 +- src/main/c/cext/array.c | 17 +- src/main/c/cext/call.c | 16 +- src/main/c/cext/cext_constants.c | 160 +- src/main/c/cext/data.c | 105 +- src/main/c/cext/define.c | 5 +- src/main/c/cext/encoding.c | 15 +- src/main/c/cext/exception.c | 47 +- src/main/c/cext/extconf.rb | 7 +- src/main/c/cext/fd.c | 3 +- src/main/c/cext/fiber.c | 10 +- src/main/c/cext/flags.c | 20 + src/main/c/cext/float.c | 4 + src/main/c/cext/gc.c | 8 +- src/main/c/cext/globals.c | 38 +- src/main/c/cext/hash.c | 12 +- src/main/c/cext/integer.c | 6 +- src/main/c/cext/io.c | 82 +- src/main/c/cext/ivar.c | 2 +- src/main/c/cext/numeric.c | 4 + src/main/c/cext/object.c | 9 +- src/main/c/cext/printf.c | 40 +- src/main/c/cext/ruby.c | 15 +- src/main/c/cext/st.c | 5 +- src/main/c/cext/string.c | 28 +- src/main/c/cext/strlcpy.c | 51 + src/main/c/cext/struct.c | 29 +- src/main/c/cext/symbol.c | 10 +- src/main/c/cext/thread.c | 4 +- src/main/c/cext/time.c | 30 +- src/main/c/cext/truffleruby-impl.h | 65 + src/main/c/cext/truffleruby.c | 36 +- src/main/c/psych/psych_to_ruby.c | 2 +- src/main/c/ripper/parse.c | 6 +- src/main/c/ripper/ripper.c | 4 + src/main/c/zlib/zlib.c | 2 +- .../java/org/truffleruby/cext/CExtNodes.java | 68 - .../java/org/truffleruby/cext/DataHolder.java | 89 - .../org/truffleruby/cext/ValueWrapper.java | 6 +- .../core/DataObjectFinalizationService.java | 34 +- .../core/DataObjectFinalizerReference.java | 15 +- .../core/encoding/EncodingNodes.java | 11 + .../core/objectspace/ObjectSpaceNodes.java | 14 +- .../language/TruffleBootNodes.java | 24 +- .../language/loader/FeatureLoader.java | 71 +- .../language/loader/RequireNode.java | 13 +- .../objects/shared/WriteBarrierNode.java | 10 +- .../java/org/truffleruby/options/Options.java | 5 + .../platform/TruffleNFIPlatform.java | 2 +- src/main/ruby/truffleruby/core/post.rb | 2 +- .../core/truffle/encoding_operations.rb | 6 + .../ruby/truffleruby/core/truffle/gem_util.rb | 8 +- src/options.yml | 1 + .../shared/options/OptionsCatalog.java | 12 + tool/generate-cext-constants.rb | 35 +- tool/generate-cext-trampoline.rb | 146 ++ tool/import-mri-files.sh | 4 +- tool/jt.rb | 26 +- tool/lint.sh | 8 + 125 files changed, 4233 insertions(+), 1319 deletions(-) delete mode 100644 lib/cext/ABI_version.txt create mode 100644 lib/cext/include/truffleruby/truffleruby-abi-version.h delete mode 100644 spec/tags/optional/capi/fiber_tags.txt create mode 100644 spec/tags/optional/capi/thread_tags.txt create mode 100644 spec/tags/truffle/capi/unimplemented_tags.txt create mode 100644 src/main/c/cext-trampoline/Makefile create mode 100644 src/main/c/cext-trampoline/cext_constants.c create mode 100644 src/main/c/cext-trampoline/st.c create mode 100644 src/main/c/cext/flags.c create mode 100644 src/main/c/cext/strlcpy.c delete mode 100644 src/main/java/org/truffleruby/cext/DataHolder.java create mode 100755 tool/generate-cext-trampoline.rb diff --git a/.gitignore b/.gitignore index ce2542a73ea0..675178552e8b 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ lib/gems/gems/debug-*/ext/debug/debug_version.h src/main/c/*/Makefile !src/main/c/spawn-helper/Makefile !src/main/c/truffleposix/Makefile +src/main/c/cext-trampoline/trampoline.c src/main/c/etc/constdefs.h src/main/c/spawn-helper/spawn-helper diff --git a/doc/contributor/how-to-guide.md b/doc/contributor/how-to-guide.md index 8c545ee2c635..2aad701e55de 100644 --- a/doc/contributor/how-to-guide.md +++ b/doc/contributor/how-to-guide.md @@ -1292,7 +1292,7 @@ When you modify C-files (*.c, *.h) or compilation-related Ruby files affect the ABI or increase the ABI *check* version explicitly by modifying one of the files: -- lib/cext/ABI_version.txt +- lib/cext/include/truffleruby/truffleruby-abi-version.h - lib/cext/ABI_check.txt ABI change is: @@ -1300,7 +1300,7 @@ ABI change is: - removing/adding a non-static function - implementing already declared non-static functions -In case of doubt, bump `ABI_version.txt`. +In case of doubt, bump `truffleruby-abi-version.h`. ## How to choose where to add new specs - in TruffleRuby or in ruby/spec repository diff --git a/doc/contributor/updating-ruby.md b/doc/contributor/updating-ruby.md index 84d7b90ac635..75ad7a502153 100644 --- a/doc/contributor/updating-ruby.md +++ b/doc/contributor/updating-ruby.md @@ -142,7 +142,7 @@ Also update the list of `provided_executables` in `mx_truffleruby.py` if some la Update all of these: * Update `.ruby-version`, `TruffleRuby.LANGUAGE_VERSION` -* Reset `lib/cext/ABI_version.txt` and `lib/cext/ABI_check.txt` to `1` if `RUBY_VERSION` was updated. +* Reset `truffleruby-abi-version.h` to `$RUBY_VERSION.1` and `lib/cext/ABI_check.txt` to `1` if `RUBY_VERSION` was updated. * Update `versions.json` (with gem versions provided by `cat ../ruby/gems/bundled_gems | sort`, `ls -l lib/gems/specifications/default` and `grep 'VERSION =' lib/mri/rubygems.rb`) * Also update version numbers for `debug` and `rbs` in `src/main/c/Makefile` and in `mx.truffleruby/suite.py`. * Copy and paste `-h` and `--help` output to `RubyLauncher` (instructions are in the end of the file `src/launcher/java/org/truffleruby/launcher/RubyLauncher.java`) diff --git a/lib/cext/ABI_version.txt b/lib/cext/ABI_version.txt deleted file mode 100644 index b8626c4cff28..000000000000 --- a/lib/cext/ABI_version.txt +++ /dev/null @@ -1 +0,0 @@ -4 diff --git a/lib/cext/include/internal/bignum.h b/lib/cext/include/internal/bignum.h index 5cd35ede8af1..c9518e805df7 100644 --- a/lib/cext/include/internal/bignum.h +++ b/lib/cext/include/internal/bignum.h @@ -136,9 +136,13 @@ static inline bool BIGNUM_POSITIVE_P(VALUE b); static inline bool BIGNUM_NEGATIVE_P(VALUE b); static inline void BIGNUM_SET_SIGN(VALUE b, bool sign); static inline void BIGNUM_NEGATE(VALUE b); +#ifndef TRUFFLERUBY static inline size_t BIGNUM_LEN(VALUE b); +#endif static inline BDIGIT *BIGNUM_DIGITS(VALUE b); +#ifndef TRUFFLERUBY static inline int BIGNUM_LENINT(VALUE b); +#endif static inline bool BIGNUM_EMBED_P(VALUE b); RUBY_SYMBOL_EXPORT_BEGIN @@ -205,6 +209,7 @@ BIGNUM_NEGATE(VALUE b) FL_REVERSE_RAW(b, BIGNUM_SIGN_BIT); } +#ifndef TRUFFLERUBY static inline size_t BIGNUM_LEN(VALUE b) { @@ -224,6 +229,7 @@ BIGNUM_LENINT(VALUE b) { return rb_long2int(BIGNUM_LEN(b)); } +#endif /* LSB:BIGNUM_DIGITS(b)[0], MSB:BIGNUM_DIGITS(b)[BIGNUM_LEN(b)-1] */ static inline BDIGIT * diff --git a/lib/cext/include/internal/compile.h b/lib/cext/include/internal/compile.h index d32c2233c905..2f77e064b1a1 100644 --- a/lib/cext/include/internal/compile.h +++ b/lib/cext/include/internal/compile.h @@ -25,7 +25,12 @@ st_index_t rb_iseq_cdhash_hash(VALUE a); /* iseq.c */ int rb_vm_insn_addr2insn(const void *); int rb_vm_insn_decode(const VALUE encoded); + +#ifdef TRUFFLERUBY +#define ruby_vm_keep_script_lines false +#else extern bool ruby_vm_keep_script_lines; +#endif MJIT_SYMBOL_EXPORT_BEGIN /* iseq.c (export) */ diff --git a/lib/cext/include/internal/imemo.h b/lib/cext/include/internal/imemo.h index 871978268b14..33a4e156ff8f 100644 --- a/lib/cext/include/internal/imemo.h +++ b/lib/cext/include/internal/imemo.h @@ -131,8 +131,10 @@ typedef struct rb_imemo_tmpbuf_struct rb_imemo_tmpbuf_t; rb_imemo_tmpbuf_t *rb_imemo_tmpbuf_parser_heap(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt); struct vm_ifunc *rb_vm_ifunc_new(rb_block_call_func_t func, const void *data, int min_argc, int max_argc); void rb_strterm_mark(VALUE obj); +#ifndef TRUFFLERUBY static inline enum imemo_type imemo_type(VALUE imemo); static inline int imemo_type_p(VALUE imemo, enum imemo_type imemo_type); +#endif static inline bool imemo_throw_data_p(VALUE imemo); static inline struct vm_ifunc *rb_vm_ifunc_proc_new(rb_block_call_func_t func, const void *data); #ifdef TRUFFLERUBY @@ -160,6 +162,7 @@ VALUE rb_imemo_new(enum imemo_type type, VALUE v1, VALUE v2, VALUE v3, VALUE v0) const char *rb_imemo_name(enum imemo_type type); RUBY_SYMBOL_EXPORT_END +#ifndef TRUFFLERUBY static inline enum imemo_type imemo_type(VALUE imemo) { @@ -182,6 +185,7 @@ imemo_type_p(VALUE imemo, enum imemo_type imemo_type) } #define IMEMO_TYPE_P(v, t) imemo_type_p((VALUE)v, t) +#endif static inline bool imemo_throw_data_p(VALUE imemo) diff --git a/lib/cext/include/ruby/internal/arithmetic/long.h b/lib/cext/include/ruby/internal/arithmetic/long.h index 2c2ffcf868c0..7dab59114acc 100644 --- a/lib/cext/include/ruby/internal/arithmetic/long.h +++ b/lib/cext/include/ruby/internal/arithmetic/long.h @@ -337,11 +337,7 @@ rb_ulong2num_inline(unsigned long v) if (RB_POSFIXABLE(v)) return RB_LONG2FIX(v); else -#ifdef TRUFFLERUBY - return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_ulong2num", (long) v)); -#else return rb_uint2big(v); -#endif } /** diff --git a/lib/cext/include/ruby/internal/config.h b/lib/cext/include/ruby/internal/config.h index 3159b743025d..3d7ccbb4d9d1 100644 --- a/lib/cext/include/ruby/internal/config.h +++ b/lib/cext/include/ruby/internal/config.h @@ -155,7 +155,7 @@ #endif #define TRUFFLERUBY -// Loaded at the end of config.h, included from defines.h. Needs STRINGIZE(). +// Loaded at the end of config.h, included from defines.h. #include #endif /* RBIMPL_CONFIG_H */ diff --git a/lib/cext/include/ruby/internal/core/rarray.h b/lib/cext/include/ruby/internal/core/rarray.h index e9b10e714ecc..9a49e22b85b9 100644 --- a/lib/cext/include/ruby/internal/core/rarray.h +++ b/lib/cext/include/ruby/internal/core/rarray.h @@ -283,6 +283,7 @@ VALUE rb_tr_rarray_aref(VALUE array, long index); #endif RBIMPL_SYMBOL_EXPORT_END() +#ifndef TRUFFLERUBY RBIMPL_ATTR_PURE_UNLESS_DEBUG() RBIMPL_ATTR_ARTIFICIAL() /** @@ -312,7 +313,6 @@ RARRAY_EMBED_LEN(VALUE ary) return RBIMPL_CAST((long)f); } -#ifndef TRUFFLERUBY RBIMPL_ATTR_PURE_UNLESS_DEBUG() /** * Queries the length of the array. diff --git a/lib/cext/include/ruby/internal/core/rbasic.h b/lib/cext/include/ruby/internal/core/rbasic.h index 44c378719b19..1c586a02fea0 100644 --- a/lib/cext/include/ruby/internal/core/rbasic.h +++ b/lib/cext/include/ruby/internal/core/rbasic.h @@ -37,9 +37,7 @@ * @param obj Arbitrary Ruby object. * @return The passed object casted to ::RBasic. */ -#ifdef TRUFFLERUBY -#define RBASIC(obj) (polyglot_as_RBasic(polyglot_invoke(RUBY_CEXT, "RBASIC", rb_tr_unwrap(obj)))) -#else +#ifndef TRUFFLERUBY #define RBASIC(obj) RBIMPL_CAST((struct RBasic *)(obj)) #endif @@ -75,7 +73,8 @@ enum ruby_rvalue_flags { struct RUBY_ALIGNAS(SIZEOF_VALUE) RBasic { - +#ifndef TRUFFLERUBY + // TruffleRuby: we cannot support writing to the flags field, so don't expose the field /** * Per-object flags. Each ruby objects have their own characteristics * apart from their classes. For instance whether an object is frozen or @@ -98,11 +97,7 @@ RBasic { * Also note the `const` qualifier. In ruby an object cannot "change" its * class. */ -#ifdef TRUFFLERUBY - VALUE klass; -#else const VALUE klass; -#endif #ifdef __cplusplus public: @@ -122,12 +117,9 @@ RBasic { { } #endif +#endif // TRUFFLERUBY }; -#ifdef TRUFFLERUBY -POLYGLOT_DECLARE_STRUCT(RBasic) -#endif - RBIMPL_SYMBOL_EXPORT_BEGIN() /** * Make the object invisible from Ruby code. diff --git a/lib/cext/include/ruby/internal/core/rdata.h b/lib/cext/include/ruby/internal/core/rdata.h index a65dda104828..2e08842b8ed3 100644 --- a/lib/cext/include/ruby/internal/core/rdata.h +++ b/lib/cext/include/ruby/internal/core/rdata.h @@ -61,7 +61,8 @@ * @return The passed object casted to ::RData. */ #ifdef TRUFFLERUBY -#define RDATA(obj) (polyglot_as_RData(polyglot_invoke(RUBY_CEXT, "RDATA", rb_tr_unwrap(obj)))) +struct RData* rb_tr_rdata(VALUE object); +#define RDATA(obj) rb_tr_rdata(obj) #else #define RDATA(obj) RBIMPL_CAST((struct RData *)(obj)) #endif @@ -127,8 +128,10 @@ typedef void (*RUBY_DATA_FUNC)(void*); */ struct RData { +#ifndef TRUFFLERUBY /** Basic part, including flags and class. */ struct RBasic basic; +#endif /** * This function is called when the object is experiencing GC marks. If it @@ -154,10 +157,6 @@ struct RData { void *data; }; -#ifdef TRUFFLERUBY -POLYGLOT_DECLARE_STRUCT(RData) -#endif - RBIMPL_SYMBOL_EXPORT_BEGIN() /** @@ -319,12 +318,10 @@ rb_data_object_wrap_warning(VALUE klass, void *ptr, RUBY_DATA_FUNC mark, RUBY_DA static inline void * rb_data_object_get(VALUE obj) { -#ifdef TRUFFLERUBY - return polyglot_invoke(RUBY_CEXT, "RDATA_PTR", rb_tr_unwrap(obj)); -#else +#ifndef TRUFFLERUBY // TruffleRuby always does the check in RDATA() Check_Type(obj, RUBY_T_DATA); - return DATA_PTR(obj); #endif + return DATA_PTR(obj); } RBIMPL_ATTRSET_UNTYPED_DATA_FUNC() diff --git a/lib/cext/include/ruby/internal/core/rfile.h b/lib/cext/include/ruby/internal/core/rfile.h index 9c1536f8e4a0..9d9746a70609 100644 --- a/lib/cext/include/ruby/internal/core/rfile.h +++ b/lib/cext/include/ruby/internal/core/rfile.h @@ -34,17 +34,15 @@ struct rb_io_t; */ struct RFile { +#ifndef TRUFFLERUBY /** Basic part, including flags and class. */ struct RBasic basic; +#endif /** IO's specific fields. */ struct rb_io_t *fptr; }; -#ifdef TRUFFLERUBY -POLYGLOT_DECLARE_STRUCT(RFile) -#endif - /** * Convenient casting macro. * @@ -52,7 +50,8 @@ POLYGLOT_DECLARE_STRUCT(RFile) * @return The passed object casted to ::RFile. */ #ifdef TRUFFLERUBY -#define RFILE(obj) (polyglot_as_RFile(RUBY_CEXT_INVOKE_NO_WRAP("RFILE", obj))) +struct RFile* rb_tr_io_get_rfile(VALUE io); +#define RFILE(obj) rb_tr_io_get_rfile(obj) #else #define RFILE(obj) RBIMPL_CAST((struct RFile *)(obj)) #endif diff --git a/lib/cext/include/ruby/internal/core/rstring.h b/lib/cext/include/ruby/internal/core/rstring.h index cae70df9258b..15eed501bef8 100644 --- a/lib/cext/include/ruby/internal/core/rstring.h +++ b/lib/cext/include/ruby/internal/core/rstring.h @@ -61,12 +61,6 @@ * @{ */ -#ifdef TRUFFLERUBY -#define StringValue(v) rb_tr_string_value(&(v)) -#define StringValuePtr(v) rb_tr_string_value_ptr(&(v)) -#define StringValueCStr(v) rb_tr_string_value_cstr(&(v)) -#else - /** * Ensures that the parameter object is a String. This is done by calling its * `to_str` method. @@ -99,7 +93,6 @@ * @post `v` is a String. */ #define StringValueCStr(v) rb_string_value_cstr(&(v)) -#endif // TRUFFLERUBY /** * @private @@ -321,12 +314,6 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() */ VALUE rb_str_to_str(VALUE obj); -#ifdef TRUFFLERUBY -VALUE rb_string_value(VALUE *ptr); -char *rb_string_value_ptr(VALUE *ptr); -char *rb_string_value_cstr(VALUE *ptr); -#else - /** * Identical to rb_str_to_str(), except it fills the passed pointer with the * converted object. @@ -361,7 +348,6 @@ char *rb_string_value_ptr(volatile VALUE *ptr); * @return Pointer to the contents of the return value. */ char *rb_string_value_cstr(volatile VALUE *ptr); -#endif // TRUFFLERUBY /** * Identical to rb_str_to_str(), except it additionally converts the string @@ -417,8 +403,8 @@ void rb_debug_rstring_null_ptr(const char *func); #ifdef TRUFFLERUBY int rb_tr_str_len(VALUE string); -char *RSTRING_PTR_IMPL(VALUE string); -char *RSTRING_END_IMPL(VALUE string); +char* rb_tr_rstring_ptr(VALUE string); +char* rb_tr_rstring_end(VALUE string); #endif RBIMPL_SYMBOL_EXPORT_END() @@ -523,7 +509,7 @@ static inline char * RSTRING_PTR(VALUE str) { #ifdef TRUFFLERUBY - return RSTRING_PTR_IMPL(str); + return rb_tr_rstring_ptr(str); #else char *ptr = rbimpl_rstring_getmem(str).as.heap.ptr; @@ -557,7 +543,7 @@ static inline char * RSTRING_END(VALUE str) { #ifdef TRUFFLERUBY - return RSTRING_END_IMPL(str); + return rb_tr_rstring_end(str); #else struct RString buf = rbimpl_rstring_getmem(str); diff --git a/lib/cext/include/ruby/internal/core/rtypeddata.h b/lib/cext/include/ruby/internal/core/rtypeddata.h index a51c64cf575b..fb3c1c888b8c 100644 --- a/lib/cext/include/ruby/internal/core/rtypeddata.h +++ b/lib/cext/include/ruby/internal/core/rtypeddata.h @@ -92,7 +92,8 @@ * @return The passed object casted to ::RTypedData. */ #ifdef TRUFFLERUBY -#define RTYPEDDATA(obj) (polyglot_as_RTypedData(polyglot_invoke(RUBY_CEXT, "RDATA", rb_tr_unwrap(obj)))) +struct RTypedData* rb_tr_rtypeddata(VALUE object); +#define RTYPEDDATA(obj) rb_tr_rtypeddata(obj) #else #define RTYPEDDATA(obj) RBIMPL_CAST((struct RTypedData *)(obj)) #endif @@ -344,8 +345,6 @@ struct rb_data_type_struct { struct RTypedData { #ifndef TRUFFLERUBY - // TruffleRuby: RBasic is an empty struct. clang makes it size 0 for C, but size 1 for C++. That difference affects field offsets, so we comment out the reference to ensure the size is always 0. - /** The part that all ruby objects have in common. */ struct RBasic basic; #endif @@ -370,10 +369,6 @@ struct RTypedData { void *data; }; -#ifdef TRUFFLERUBY -POLYGLOT_DECLARE_STRUCT(RTypedData) -#endif - RBIMPL_SYMBOL_EXPORT_BEGIN() RBIMPL_ATTR_NONNULL((3)) /** @@ -440,6 +435,7 @@ int rb_typeddata_is_kind_of(VALUE obj, const rb_data_type_t *data_type); void *rb_check_typeddata(VALUE obj, const rb_data_type_t *data_type); #ifdef TRUFFLERUBY VALUE rb_data_typed_object_make(VALUE ruby_class, const rb_data_type_t *type, void **data_pointer, size_t size); +bool rb_tr_rtypeddata_p(VALUE obj); #endif RBIMPL_SYMBOL_EXPORT_END() @@ -538,11 +534,7 @@ RBIMPL_ATTR_ARTIFICIAL() static inline bool rbimpl_rtypeddata_p(VALUE obj) { -#ifdef TRUFFLERUBY - return polyglot_as_boolean(polyglot_invoke(RUBY_CEXT, "rbimpl_rtypeddata_p", rb_tr_unwrap(obj))); -#else return RTYPEDDATA(obj)->typed_flag == 1; -#endif } RBIMPL_ATTR_PURE_UNLESS_DEBUG() @@ -565,7 +557,11 @@ RTYPEDDATA_P(VALUE obj) } #endif +#ifdef TRUFFLERUBY + return rb_tr_rtypeddata_p(obj); +#else return rbimpl_rtypeddata_p(obj); +#endif } RBIMPL_ATTR_PURE_UNLESS_DEBUG() diff --git a/lib/cext/include/ruby/internal/encoding/sprintf.h b/lib/cext/include/ruby/internal/encoding/sprintf.h index cb8737b4149a..51f6c4ed0c2d 100644 --- a/lib/cext/include/ruby/internal/encoding/sprintf.h +++ b/lib/cext/include/ruby/internal/encoding/sprintf.h @@ -29,6 +29,13 @@ #include "ruby/internal/encoding/encoding.h" #include "ruby/internal/value.h" +#ifdef TRUFFLERUBY +// We need to declare rb_enc_vsprintf() before rb_enc_sprintf() +RBIMPL_ATTR_NONNULL((2)) +RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 0) +VALUE rb_enc_vsprintf(rb_encoding *enc, const char *fmt, va_list ap); +#endif + RBIMPL_SYMBOL_EXPORT_BEGIN() RBIMPL_ATTR_NONNULL((2)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3) @@ -42,7 +49,17 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3) * @param[in] ... Variadic number of contents to format. * @return A rendered new instance of ::rb_cString, of `enc` encoding. */ +#ifdef TRUFFLERUBY +static inline VALUE rb_enc_sprintf(rb_encoding *enc, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + VALUE result = rb_enc_vsprintf(enc, fmt, args); + va_end(args); + return result; +} +#else VALUE rb_enc_sprintf(rb_encoding *enc, const char *fmt, ...); +#endif RBIMPL_ATTR_NONNULL((2)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 0) @@ -71,7 +88,17 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 3, 4) * @exception exc The specified exception. * @note It never returns. */ +#ifdef TRUFFLERUBY +static inline void rb_enc_raise(rb_encoding *enc, VALUE exc, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + VALUE mesg = rb_enc_vsprintf(enc, fmt, args); + va_end(args); + rb_exc_raise(rb_exc_new_str(exc, mesg)); +} +#else void rb_enc_raise(rb_encoding *enc, VALUE exc, const char *fmt, ...); +#endif RBIMPL_SYMBOL_EXPORT_END() diff --git a/lib/cext/include/ruby/internal/error.h b/lib/cext/include/ruby/internal/error.h index 49e2276cb957..f5a2f77a2ea3 100644 --- a/lib/cext/include/ruby/internal/error.h +++ b/lib/cext/include/ruby/internal/error.h @@ -100,6 +100,11 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3) */ void rb_raise(VALUE exc, const char *fmt, ...); +#ifdef TRUFFLERUBY +RBIMPL_ATTR_NORETURN() +void rb_tr_fatal_va_list(const char *fmt, va_list args); +#endif + RBIMPL_ATTR_NORETURN() RBIMPL_ATTR_NONNULL((1)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 2) @@ -112,7 +117,21 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 2) * @exception rb_eFatal An exception that you cannot rescue. * @note It never returns. */ +#ifdef TRUFFLERUBY +static inline void rb_fatal(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + rb_tr_fatal_va_list(fmt, args); + va_end(args); +} +#else void rb_fatal(const char *fmt, ...); +#endif + +#ifdef TRUFFLERUBY +RBIMPL_ATTR_NORETURN() +void rb_tr_bug_va_list(const char *fmt, va_list args); +#endif RBIMPL_ATTR_COLD() RBIMPL_ATTR_NORETURN() @@ -134,7 +153,16 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 2) * @param[in] fmt Format specifier string compatible with rb_sprintf(). * @note It never returns. */ +#ifdef TRUFFLERUBY +static inline void rb_bug(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + rb_tr_bug_va_list(fmt, args); + va_end(args); +} +#else void rb_bug(const char *fmt, ...); +#endif RBIMPL_ATTR_NORETURN() RBIMPL_ATTR_NONNULL(()) @@ -469,6 +497,10 @@ VALUE *rb_ruby_debug_ptr(void); */ #define ruby_debug (*rb_ruby_debug_ptr()) +#ifdef TRUFFLERUBY +void rb_tr_warn_va_list(const char *fmt, va_list args); +#endif + /* reports if `-W' specified */ RBIMPL_ATTR_NONNULL((1)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 2) @@ -491,7 +523,18 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 2) * * Above description is in fact inaccurate. This API interfaces with Ractors. */ +#ifdef TRUFFLERUBY +static inline void rb_warning(const char *fmt, ...) { + if (RTEST(ruby_verbose)) { + va_list args; + va_start(args, fmt); + rb_tr_warn_va_list(fmt, args); + va_end(args); + } +} +#else void rb_warning(const char *fmt, ...); +#endif RBIMPL_ATTR_NONNULL((2)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3) @@ -514,7 +557,13 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 3, 4) * @param[in] line The number corresponding to Ruby level `__LINE__`. * @param[in] fmt Format specifier string compatible with rb_sprintf(). */ +#ifdef TRUFFLERUBY +static inline void rb_compile_warning(const char *file, int line, const char *fmt, ...) { + rb_tr_not_implemented("rb_compile_warning"); +} +#else void rb_compile_warning(const char *file, int line, const char *fmt, ...); +#endif RBIMPL_ATTR_NONNULL((1)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 2) @@ -537,7 +586,18 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 2) * * @param[in] fmt Format specifier string compatible with rb_sprintf(). */ +#ifdef TRUFFLERUBY +static inline void rb_warn(const char *fmt, ...) { + if (!NIL_P(ruby_verbose)) { + va_list args; + va_start(args, fmt); + rb_tr_warn_va_list(fmt, args); + va_end(args); + } +} +#else void rb_warn(const char *fmt, ...); +#endif RBIMPL_ATTR_COLD() RBIMPL_ATTR_NONNULL((2)) @@ -561,7 +621,13 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 3, 4) * @param[in] line The number corresponding to Ruby level `__LINE__`. * @param[in] fmt Format specifier string compatible with rb_sprintf(). */ +#ifdef TRUFFLERUBY +static inline void rb_compile_warn(const char *file, int line, const char *fmt, ...) { + rb_tr_not_implemented("rb_compile_warn"); +} +#else void rb_compile_warn(const char *file, int line, const char *fmt, ...); +#endif RBIMPL_ATTR_NONNULL((2, 4)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 4, 5) diff --git a/lib/cext/include/ruby/internal/fl_type.h b/lib/cext/include/ruby/internal/fl_type.h index 285b9a0333f5..663f16d99b4e 100644 --- a/lib/cext/include/ruby/internal/fl_type.h +++ b/lib/cext/include/ruby/internal/fl_type.h @@ -465,10 +465,6 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() * @post `klass` gets frozen. */ void rb_freeze_singleton_class(VALUE klass); -#ifdef TRUFFLERUBY -int rb_tr_flags(VALUE value); -void rb_tr_add_flags(VALUE value, int flags); -#endif RBIMPL_SYMBOL_EXPORT_END() RBIMPL_ATTR_PURE_UNLESS_DEBUG() @@ -513,7 +509,7 @@ RB_FL_TEST_RAW(VALUE obj, VALUE flags) { RBIMPL_ASSERT_OR_ASSUME(RB_FL_ABLE(obj)); #ifdef TRUFFLERUBY - return rb_tr_flags(obj) & flags; + return RBASIC_FLAGS(obj) & flags; #else return RBASIC(obj)->flags & flags; #endif @@ -617,6 +613,7 @@ RB_FL_ALL(VALUE obj, VALUE flags) return RB_FL_TEST(obj, flags) == flags; } +#ifndef TRUFFLERUBY RBIMPL_ATTR_NOALIAS() RBIMPL_ATTR_ARTIFICIAL() /** @@ -639,6 +636,7 @@ rbimpl_fl_set_raw_raw(struct RBasic *obj, VALUE flags) { obj->flags |= flags; } +#endif RBIMPL_ATTR_ARTIFICIAL() /** @@ -654,7 +652,7 @@ RB_FL_SET_RAW(VALUE obj, VALUE flags) { RBIMPL_ASSERT_OR_ASSUME(RB_FL_ABLE(obj)); #ifdef TRUFFLERUBY - rb_tr_add_flags(obj, flags); + rb_tr_set_flags(obj, RBASIC_FLAGS(obj) | flags); #else rbimpl_fl_set_raw_raw(RBASIC(obj), flags); #endif @@ -681,6 +679,7 @@ RB_FL_SET(VALUE obj, VALUE flags) } } +#ifndef TRUFFLERUBY RBIMPL_ATTR_NOALIAS() RBIMPL_ATTR_ARTIFICIAL() /** @@ -703,6 +702,7 @@ rbimpl_fl_unset_raw_raw(struct RBasic *obj, VALUE flags) { obj->flags &= ~flags; } +#endif RBIMPL_ATTR_ARTIFICIAL() /** @@ -717,7 +717,11 @@ static inline void RB_FL_UNSET_RAW(VALUE obj, VALUE flags) { RBIMPL_ASSERT_OR_ASSUME(RB_FL_ABLE(obj)); +#ifdef TRUFFLERUBY + rb_tr_set_flags(obj, RBASIC_FLAGS(obj) & ~flags); +#else rbimpl_fl_unset_raw_raw(RBASIC(obj), flags); +#endif } RBIMPL_ATTR_ARTIFICIAL() @@ -736,6 +740,7 @@ RB_FL_UNSET(VALUE obj, VALUE flags) } } +#ifndef TRUFFLERUBY RBIMPL_ATTR_NOALIAS() RBIMPL_ATTR_ARTIFICIAL() /** @@ -758,6 +763,7 @@ rbimpl_fl_reverse_raw_raw(struct RBasic *obj, VALUE flags) { obj->flags ^= flags; } +#endif RBIMPL_ATTR_ARTIFICIAL() /** @@ -772,7 +778,11 @@ static inline void RB_FL_REVERSE_RAW(VALUE obj, VALUE flags) { RBIMPL_ASSERT_OR_ASSUME(RB_FL_ABLE(obj)); +#ifdef TRUFFLERUBY + rb_tr_set_flags(obj, RBASIC_FLAGS(obj) ^ flags); +#else rbimpl_fl_reverse_raw_raw(RBASIC(obj), flags); +#endif } RBIMPL_ATTR_ARTIFICIAL() diff --git a/lib/cext/include/ruby/internal/intern/array.h b/lib/cext/include/ruby/internal/intern/array.h index 2262c6f0c621..c38cd313eccf 100644 --- a/lib/cext/include/ruby/internal/intern/array.h +++ b/lib/cext/include/ruby/internal/intern/array.h @@ -88,6 +88,10 @@ VALUE rb_ary_new(void); */ VALUE rb_ary_new_capa(long capa); +#ifdef TRUFFLERUBY +VALUE rb_tr_ary_new_from_args_va_list(long n, va_list args); +#endif + /** * Constructs an array from the passed objects. * @@ -95,7 +99,17 @@ VALUE rb_ary_new_capa(long capa); * @param[in] ... Arbitrary ruby objects, filled into the returning array. * @return An array of size `n`, whose contents are the passed objects. */ +#ifdef TRUFFLERUBY +static inline VALUE rb_ary_new_from_args(long n, ...) { + va_list args; + va_start(args, n); + VALUE array = rb_tr_ary_new_from_args_va_list(n, args); + va_end(args); + return array; +} +#else VALUE rb_ary_new_from_args(long n, ...); +#endif /** * Identical to rb_ary_new_from_args(), except how objects are passed. diff --git a/lib/cext/include/ruby/internal/intern/gc.h b/lib/cext/include/ruby/internal/intern/gc.h index 2ee1d257db8e..d44aafbc3e77 100644 --- a/lib/cext/include/ruby/internal/intern/gc.h +++ b/lib/cext/include/ruby/internal/intern/gc.h @@ -385,7 +385,13 @@ VALUE rb_gc_latest_gc_info(VALUE key_or_buf); * * @param[in] diff Amount of memory increased(+)/decreased(-). */ +#ifdef TRUFFLERUBY +static inline void rb_gc_adjust_memory_usage(ssize_t diff) { + // No-op for now +} +#else void rb_gc_adjust_memory_usage(ssize_t diff); +#endif RBIMPL_SYMBOL_EXPORT_END() diff --git a/lib/cext/include/ruby/internal/intern/sprintf.h b/lib/cext/include/ruby/internal/intern/sprintf.h index aedc0f9ab1d3..fd6858ac49de 100644 --- a/lib/cext/include/ruby/internal/intern/sprintf.h +++ b/lib/cext/include/ruby/internal/intern/sprintf.h @@ -42,6 +42,13 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() */ VALUE rb_f_sprintf(int argc, const VALUE *argv); +#ifdef TRUFFLERUBY +// We need to declare rb_vsprintf() before rb_sprintf() +RBIMPL_ATTR_NONNULL((1)) +RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 0) +VALUE rb_vsprintf(const char *fmt, va_list ap); +#endif + RBIMPL_ATTR_NONNULL((1)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 2) /** @@ -79,7 +86,17 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 2) * * :FIXME: We can improve this document. */ +#ifdef TRUFFLERUBY +static inline VALUE rb_sprintf(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + VALUE result = rb_vsprintf(fmt, args); + va_end(args); + return result; +} +#else VALUE rb_sprintf(const char *fmt, ...); +#endif RBIMPL_ATTR_NONNULL((1)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 0) @@ -92,6 +109,13 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 0) */ VALUE rb_vsprintf(const char *fmt, va_list ap); +#ifdef TRUFFLERUBY +// We need to declare rb_str_vcatf() before rb_str_catf() +RBIMPL_ATTR_NONNULL((2)) +RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 0) +VALUE rb_str_vcatf(VALUE dst, const char *fmt, va_list ap); +#endif + RBIMPL_ATTR_NONNULL((2)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3) /** @@ -105,7 +129,17 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3) * @return Passed `dst`. * @post `dst` has the rendered output appended to its end. */ +#ifdef TRUFFLERUBY +static inline VALUE rb_str_catf(VALUE dst, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + VALUE result = rb_str_vcatf(dst, fmt, args); + va_end(args); + return result; +} +#else VALUE rb_str_catf(VALUE dst, const char *fmt, ...); +#endif RBIMPL_ATTR_NONNULL((2)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 0) diff --git a/lib/cext/include/ruby/internal/intern/struct.h b/lib/cext/include/ruby/internal/intern/struct.h index 312cf444e219..96e66e1e0529 100644 --- a/lib/cext/include/ruby/internal/intern/struct.h +++ b/lib/cext/include/ruby/internal/intern/struct.h @@ -29,6 +29,10 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() /* struct.c */ +#ifdef TRUFFLERUBY +VALUE rb_tr_struct_new_va_list(VALUE klass, va_list args); +#endif + /** * Creates an instance of the given struct. * @@ -39,7 +43,21 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() * @note Number of variadic arguments must much that of the passed klass' * fields. */ +#ifdef TRUFFLERUBY +static inline VALUE rb_struct_new(VALUE klass, ...) { + va_list args; + va_start(args, klass); + VALUE result = rb_tr_struct_new_va_list(klass, args); + va_end(args); + return result; +} +#else VALUE rb_struct_new(VALUE klass, ...); +#endif + +#ifdef TRUFFLERUBY +VALUE rb_tr_struct_define_va_list(const char *name, va_list args); +#endif /** * Defines a struct class. @@ -60,7 +78,21 @@ VALUE rb_struct_new(VALUE klass, ...); * Not seriously checked but it seems this function does not share its * implementation with how `Struct.new` is implemented...? */ +#ifdef TRUFFLERUBY +static inline VALUE rb_struct_define(const char *name, ...) { + va_list args; + va_start(args, name); + VALUE result = rb_tr_struct_define_va_list(name, args); + va_end(args); + return result; +} +#else VALUE rb_struct_define(const char *name, ...); +#endif + +#ifdef TRUFFLERUBY +VALUE rb_tr_struct_define_under_va_list(VALUE space, const char *name, va_list args); +#endif RBIMPL_ATTR_NONNULL((2)) /** @@ -79,7 +111,17 @@ RBIMPL_ATTR_NONNULL((2)) * @note In contrast to rb_struct_define(), it doesn't make any sense to * pass a null pointer to this function. */ +#ifdef TRUFFLERUBY +static inline VALUE rb_struct_define_under(VALUE space, const char *name, ...) { + va_list args; + va_start(args, name); + VALUE result = rb_tr_struct_define_under_va_list(space, name, args); + va_end(args); + return result; +} +#else VALUE rb_struct_define_under(VALUE space, const char *name, ...); +#endif /** * Identical to rb_struct_new(), except it takes the field values as a Ruby diff --git a/lib/cext/include/ruby/internal/intern/time.h b/lib/cext/include/ruby/internal/intern/time.h index df482862eb02..97449276caf2 100644 --- a/lib/cext/include/ruby/internal/intern/time.h +++ b/lib/cext/include/ruby/internal/intern/time.h @@ -105,6 +105,10 @@ VALUE rb_time_timespec_new(const struct timespec *ts, int offset); */ VALUE rb_time_num_new(VALUE timev, VALUE off); +#ifdef TRUFFLERUBY +void rb_tr_time_interval(VALUE num, struct timeval *result); +#endif + /** * Creates a "time interval". This basically converts an instance of * ::rb_cNumeric into a struct `timeval`, but for instance negative time @@ -115,7 +119,19 @@ VALUE rb_time_num_new(VALUE timev, VALUE off); * @exception rb_eRangeError `num` is out of range of `timeval::tv_sec`. * @return A struct that represents the identical time to `num`. */ +#ifdef TRUFFLERUBY +static inline struct timeval rb_time_interval(VALUE num) { + struct timeval result; + rb_tr_time_interval(num, &result); + return result; +} +#else struct timeval rb_time_interval(VALUE num); +#endif + +#ifdef TRUFFLERUBY +void rb_tr_time_timeval(VALUE time, struct timeval *result); +#endif /** * Converts an instance of rb_cTime to a struct timeval that represents the @@ -126,7 +142,19 @@ struct timeval rb_time_interval(VALUE num); * @exception rb_eRangeError `time` is out of range of `timeval::tv_sec`. * @return A struct that represents the identical time to `num`. */ +#ifdef TRUFFLERUBY +static inline struct timeval rb_time_timeval(VALUE time) { + struct timeval result; + rb_tr_time_timeval(time, &result); + return result; +} +#else struct timeval rb_time_timeval(VALUE time); +#endif + +#ifdef TRUFFLERUBY +void rb_tr_time_timespec(VALUE time, struct timespec *result); +#endif /** * Identical to rb_time_timeval(), except for return type. @@ -135,7 +163,15 @@ struct timeval rb_time_timeval(VALUE time); * @exception rb_eRangeError `time` is out of range of `timeval::tv_sec`. * @return A struct that represents the identical time to `num`. */ +#ifdef TRUFFLERUBY +static inline struct timespec rb_time_timespec(VALUE time) { + struct timespec result; + rb_tr_time_timespec(time, &result); + return result; +} +#else struct timespec rb_time_timespec(VALUE time); +#endif /** * Identical to rb_time_interval(), except for return type. diff --git a/lib/cext/include/ruby/internal/iterator.h b/lib/cext/include/ruby/internal/iterator.h index 6e6146b350bc..8a6d32a79d0a 100644 --- a/lib/cext/include/ruby/internal/iterator.h +++ b/lib/cext/include/ruby/internal/iterator.h @@ -135,6 +135,10 @@ VALUE rb_each(VALUE obj); */ VALUE rb_yield(VALUE val); +#ifdef TRUFFLERUBY +VALUE rb_tr_yield_values_va_list(int n, va_list args); +#endif + /** * Identical to rb_yield(), except it takes variadic number of parameters and * pass them to the block. @@ -144,7 +148,17 @@ VALUE rb_yield(VALUE val); * @exception rb_eLocalJumpError There is no block given. * @return Evaluated value of the given block. */ +#ifdef TRUFFLERUBY +static inline VALUE rb_yield_values(int n, ...) { + va_list args; + va_start(args, n); + VALUE result = rb_tr_yield_values_va_list(n, args); + va_end(args); + return result; +} +#else VALUE rb_yield_values(int n, ...); +#endif /** * Identical to rb_yield_values(), except it takes the parameters as a C array @@ -359,6 +373,10 @@ VALUE rb_block_call_kw(VALUE obj, ID mid, int argc, const VALUE *argv, rb_block_ */ VALUE rb_rescue(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*r_proc)(VALUE, VALUE), VALUE data2); +#ifdef TRUFFLERUBY +VALUE rb_tr_rescue2_va_list(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*r_proc)(VALUE, VALUE), VALUE data2, va_list args); +#endif + /** * An equivalent of `rescue` clause. * @@ -381,7 +399,17 @@ VALUE rb_rescue(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*r_proc)(VALUE, VALU * @see rb_protect * @ingroup exception */ +#ifdef TRUFFLERUBY +static inline VALUE rb_rescue2(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*r_proc)(VALUE, VALUE), VALUE data2, ...) { + va_list args; + va_start(args, data2); + VALUE result = rb_tr_rescue2_va_list(b_proc, data1, r_proc, data2, args); + va_end(args); + return result; +} +#else VALUE rb_rescue2(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*r_proc)(VALUE, VALUE), VALUE data2, ...); +#endif /** * Identical to rb_rescue2(), except it takes `va_list` instead of variadic diff --git a/lib/cext/include/ruby/internal/symbol.h b/lib/cext/include/ruby/internal/symbol.h index d0c535b25dd4..869a31115ce0 100644 --- a/lib/cext/include/ruby/internal/symbol.h +++ b/lib/cext/include/ruby/internal/symbol.h @@ -289,15 +289,9 @@ RBIMPL_ATTR_NONNULL(()) static inline ID rbimpl_intern_const(ID *ptr, const char *str) { -#ifdef TRUFFLERUBY - if (!polyglot_is_value((void*) *ptr)) { - *ptr = rb_intern_const(str); - } -#else while (! *ptr) { *ptr = rb_intern_const(str); } -#endif return *ptr; } diff --git a/lib/cext/include/ruby/internal/value.h b/lib/cext/include/ruby/internal/value.h index 4fb2c388d92f..805cd835131c 100644 --- a/lib/cext/include/ruby/internal/value.h +++ b/lib/cext/include/ruby/internal/value.h @@ -24,10 +24,6 @@ #include "ruby/backward/2/long_long.h" #include "ruby/backward/2/limits.h" -#ifdef TRUFFLERUBY -#include -#endif - #if defined(__DOXYGEN__) /** @@ -128,10 +124,6 @@ typedef unsigned LONG_LONG ID; # error ---->> ruby requires sizeof(void*) == sizeof(long) or sizeof(LONG_LONG) to be compiled. <<---- #endif -#ifdef TRUFFLERUBY -POLYGLOT_DECLARE_TYPE(VALUE) -#endif - /** @cond INTERNAL_MACRO */ RBIMPL_STATIC_ASSERT(sizeof_int, SIZEOF_INT == sizeof(int)); RBIMPL_STATIC_ASSERT(sizeof_long, SIZEOF_LONG == sizeof(long)); diff --git a/lib/cext/include/ruby/internal/value_type.h b/lib/cext/include/ruby/internal/value_type.h index 9ac609a83cd0..2f3ac0666884 100644 --- a/lib/cext/include/ruby/internal/value_type.h +++ b/lib/cext/include/ruby/internal/value_type.h @@ -200,7 +200,7 @@ RB_BUILTIN_TYPE(VALUE obj) } #ifdef TRUFFLERUBY -#define RB_BUILTIN_TYPE_NATIVE(x) RBIMPL_CAST((enum ruby_value_type) (((struct RBasic*)(x))->flags & RUBY_T_MASK)) +bool rb_tr_integer_type_p(VALUE obj); #endif RBIMPL_ATTR_PURE_UNLESS_DEBUG() @@ -215,7 +215,7 @@ static inline bool rb_integer_type_p(VALUE obj) { #ifdef TRUFFLERUBY - return polyglot_as_boolean(polyglot_invoke(RUBY_CEXT, "rb_integer_type_p", rb_tr_unwrap(obj))); + return RB_FIXNUM_P(obj) || rb_tr_integer_type_p(obj); #else if (RB_FIXNUM_P(obj)) { return true; @@ -268,6 +268,10 @@ rb_type(VALUE obj) } #endif +#ifdef TRUFFLERUBY +bool rb_tr_float_type_p(VALUE obj); +#endif + RBIMPL_ATTR_PURE_UNLESS_DEBUG() RBIMPL_ATTR_ARTIFICIAL() /** @@ -282,7 +286,7 @@ RB_FLOAT_TYPE_P(VALUE obj) { #ifdef TRUFFLERUBY /* TruffleRuby: Simplify the RB_FLOAT_TYPE_P check based on our representation of Floats. */ - return polyglot_as_boolean(polyglot_invoke(RUBY_CEXT, "RB_FLOAT_TYPE_P", rb_tr_unwrap(obj))); + return rb_tr_float_type_p(obj); #else if (RB_FLONUM_P(obj)) { return true; @@ -316,6 +320,10 @@ RB_DYNAMIC_SYM_P(VALUE obj) } } +#ifdef TRUFFLERUBY +bool rb_tr_symbol_p(VALUE obj); +#endif + RBIMPL_ATTR_PURE_UNLESS_DEBUG() RBIMPL_ATTR_ARTIFICIAL() /** @@ -329,7 +337,7 @@ static inline bool RB_SYMBOL_P(VALUE obj) { #ifdef TRUFFLERUBY - return polyglot_as_boolean(polyglot_invoke(RUBY_CEXT, "SYMBOL_P", rb_tr_unwrap(obj))); + return rb_tr_symbol_p(obj); #else return RB_STATIC_SYM_P(obj) || RB_DYNAMIC_SYM_P(obj); #endif diff --git a/lib/cext/include/ruby/io.h b/lib/cext/include/ruby/io.h index 89b6be3a5f7b..8bd8505cd5b8 100644 --- a/lib/cext/include/ruby/io.h +++ b/lib/cext/include/ruby/io.h @@ -140,8 +140,10 @@ typedef struct rb_io_t { /** The IO's Ruby level counterpart. */ VALUE self; +#ifndef TRUFFLERUBY /** stdio ptr for read/write, if available. */ FILE *stdio_file; +#endif /** file descriptor. */ int fd; @@ -149,15 +151,18 @@ typedef struct rb_io_t { /** mode flags: FMODE_XXXs */ int mode; +#ifndef TRUFFLERUBY /** child's pid (for pipes) */ rb_pid_t pid; /** number of lines read */ int lineno; +#endif /** pathname for file */ VALUE pathv; +#ifndef TRUFFLERUBY /** finalize proc */ void (*finalize)(struct rb_io_t*,int); @@ -169,6 +174,7 @@ typedef struct rb_io_t { * ::rb_io_t::cbuf, which also concerns read IO. */ rb_io_buffer_t rbuf; +#endif /** * Duplex IO object, if set. @@ -177,6 +183,7 @@ typedef struct rb_io_t { */ VALUE tied_io_for_writing; +#ifndef TRUFFLERUBY struct rb_io_enc_t encs; /**< Decomposed encoding flags. */ /** Encoding converter used when reading from this IO. */ @@ -227,6 +234,7 @@ typedef struct rb_io_t { * The timeout associated with this IO when performing blocking operations. */ VALUE timeout; +#endif } rb_io_t; /** @alias{rb_io_enc_t} */ @@ -354,8 +362,8 @@ typedef struct rb_io_enc_t rb_io_enc_t; * @post `fp` holds `obj`'s underlying IO. */ #ifdef TRUFFLERUBY -POLYGLOT_DECLARE_STRUCT(rb_io_t) -#define RB_IO_POINTER(obj,fp) rb_io_check_closed((fp) = polyglot_as_rb_io_t(RUBY_CEXT_INVOKE_NO_WRAP("GetOpenFile", obj))) +rb_io_t* rb_tr_io_get_rb_io_t(VALUE io); +#define RB_IO_POINTER(obj,fp) rb_io_check_closed((fp) = rb_tr_io_get_rb_io_t(rb_io_taint_check(obj))) #else #define RB_IO_POINTER(obj,fp) rb_io_check_closed((fp) = RFILE(rb_io_taint_check(obj))->fptr) #endif diff --git a/lib/cext/include/ruby/ruby.h b/lib/cext/include/ruby/ruby.h index 77f9adc333be..738d1968fa83 100644 --- a/lib/cext/include/ruby/ruby.h +++ b/lib/cext/include/ruby/ruby.h @@ -108,7 +108,6 @@ VALUE rb_get_path_no_checksafe(VALUE); #define FilePathStringValue(v) ((v) = rb_get_path(v)) /** @cond INTERNAL_MACRO */ -#ifndef TRUFFLERUBY #if defined(HAVE_BUILTIN___BUILTIN_CONSTANT_P) && defined(HAVE_STMT_AND_DECL_IN_EXPR) # define rb_varargs_argc_check_runtime(argc, vargc) \ (((argc) <= (vargc)) ? (argc) : \ @@ -134,7 +133,6 @@ ERRORFUNC((" argument length doesn't match"), int rb_varargs_bad_length(int,int) rb_varargs_argc_check_runtime(argc, vargc) # endif #endif -#endif /** @endcond */ /** @@ -278,9 +276,15 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 3, 0) int ruby_vsnprintf(char *str, size_t n, char const *fmt, va_list ap); /** @cond INTERNAL_MACRO */ +// TruffleRuby: enable this optimization in all cases, +// by commenting/nesting the below checks under `#ifndef TRUFFLERUBY`/`#endif`. +#ifndef TRUFFLERUBY #if RBIMPL_HAS_WARNING("-Wgnu-zero-variadic-macro-arguments") # /* Skip it; clang -pedantic doesn't like the following */ #elif defined(__GNUC__) && defined(HAVE_VA_ARGS_MACRO) && defined(__OPTIMIZE__) +#endif +#endif +#if 1 /* always enabled on TruffleRuby */ # define rb_yield_values(argc, ...) \ __extension__({ \ const int rb_yield_values_argc = (argc); \ diff --git a/lib/cext/include/truffleruby/truffleruby-abi-version.h b/lib/cext/include/truffleruby/truffleruby-abi-version.h new file mode 100644 index 000000000000..cd9b9087b9df --- /dev/null +++ b/lib/cext/include/truffleruby/truffleruby-abi-version.h @@ -0,0 +1,15 @@ +#ifndef TRUFFLERUBY_ABI_VERSION_H +#define TRUFFLERUBY_ABI_VERSION_H + +// The TruffleRuby ABI version must be of the form: +// * For releases, i.e. on a release/graal-vm/X.Y branch: +// $RUBY_VERSION.$GRAALVM_VERSION.$ABI_NUMBER e.g. 3.2.2.23.1.0.1 +// * For non-release: +// $RUBY_VERSION.$ABI_NUMBER e.g. 3.2.2.1 +// +// $RUBY_VERSION must be the same as TruffleRuby.LANGUAGE_VERSION. +// $ABI_NUMBER starts at 1 and is incremented for every ABI-incompatible change. + +#define TRUFFLERUBY_ABI_VERSION "3.2.2.4" + +#endif diff --git a/lib/cext/include/truffleruby/truffleruby-pre.h b/lib/cext/include/truffleruby/truffleruby-pre.h index f753809d2350..851541f521a0 100644 --- a/lib/cext/include/truffleruby/truffleruby-pre.h +++ b/lib/cext/include/truffleruby/truffleruby-pre.h @@ -20,10 +20,6 @@ extern "C" { RUBY_SYMBOL_EXPORT_BEGIN -#include - -#include // isdigit - // Configuration // We disable USE_FLONUM, as we do not use pointer tagging for Float. @@ -45,35 +41,43 @@ RUBY_SYMBOL_EXPORT_BEGIN // Support -extern void* rb_tr_cext; -#define RUBY_CEXT rb_tr_cext +#include #ifndef TRUFFLERUBY_ABI_VERSION -#error "TRUFFLERUBY_ABI_VERSION must be defined when compiling native extensions. Does the extension override CPPFLAGS or DEFS?" +#error "TRUFFLERUBY_ABI_VERSION must be defined when compiling native extensions." #endif -void* rb_tr_abi_version(void) __attribute__((weak)); -void* rb_tr_abi_version(void) { - const char* abi_version = STRINGIZE(TRUFFLERUBY_ABI_VERSION); - return polyglot_from_string(abi_version, "US-ASCII"); + +const char* rb_tr_abi_version(void) __attribute__((weak)); +const char* rb_tr_abi_version(void) { + return TRUFFLERUBY_ABI_VERSION; } -// Wrapping and unwrapping of values. +// Declare VALUE for below +#include "ruby/defines.h" #include "ruby/internal/value.h" -extern void* (*rb_tr_unwrap)(VALUE obj); -extern VALUE (*rb_tr_wrap)(void *obj); -extern VALUE (*rb_tr_longwrap)(long obj); -extern void* (*rb_tr_id2sym)(ID obj); -extern ID (*rb_tr_sym2id)(VALUE sym); -extern void* (*rb_tr_force_native)(VALUE obj); - // Helpers #ifndef offsetof #define offsetof(p_type,field) ((size_t)&(((p_type *)0)->field)) #endif +// Non-standard. rb_tr_* is all private, the rest is there because the C API does not have a good replacement for it. + +NORETURN(void rb_tr_not_implemented(const char *function_name)); +VALUE rb_tr_zlib_crc_table(void); +VALUE rb_tr_cext_lock_owned_p(void); +VALUE rb_tr_invoke(VALUE recv, const char* meth); +unsigned long rb_tr_flags(VALUE object); +void rb_tr_set_flags(VALUE object, unsigned long flags); + +#define RBASIC_FLAGS(object) rb_tr_flags(object) +#define RBASIC_SET_FLAGS(obj, flags_to_set) rb_tr_set_flags(obj, flags_to_set) + +void rb_exc_set_message(VALUE exc, VALUE message); +VALUE rb_ivar_lookup(VALUE object, const char *name, VALUE default_value); + // Defines // To support racc releases before https://github.com/ruby/racc/pull/165 diff --git a/lib/cext/include/truffleruby/truffleruby.h b/lib/cext/include/truffleruby/truffleruby.h index 7886bb84bd1b..bb89e9cf7fd8 100644 --- a/lib/cext/include/truffleruby/truffleruby.h +++ b/lib/cext/include/truffleruby/truffleruby.h @@ -18,48 +18,83 @@ extern "C" { #endif -NORETURN(VALUE rb_f_notimplement(int argc, const VALUE *argv, VALUE obj, VALUE marker)); +// These refer Ruby global variables and their value can change, +// so we use macros instead of C global variables like MRI, which would be complicated to update. +VALUE rb_tr_stdin(void); +VALUE rb_tr_stdout(void); +VALUE rb_tr_stderr(void); +VALUE rb_tr_fs(void); +VALUE rb_tr_output_fs(void); +VALUE rb_tr_rs(void); +VALUE rb_tr_output_rs(void); +VALUE rb_tr_default_rs(void); + +#define rb_stdin rb_tr_stdin() +#define rb_stdout rb_tr_stdout() +#define rb_stderr rb_tr_stderr() +#define rb_fs rb_tr_fs() +#define rb_output_fs rb_tr_output_fs() +#define rb_rs rb_tr_rs() +#define rb_output_rs rb_tr_output_rs() +#define rb_default_rs rb_tr_default_rs() + +// A typedef for the callback argument of rb_thread_call_with_gvl() -// Non-standard +typedef void* (gvl_call)(void *); -NORETURN(void rb_tr_error(const char *message)); -void rb_tr_log_warning(const char *message); -#define rb_tr_debug(args...) polyglot_invoke(RUBY_CEXT, "rb_tr_debug", args) -int rb_tr_obj_equal(VALUE first, VALUE second); +// Exceptions -// Initialization +#define rb_raise(EXCEPTION, FORMAT, ...) \ + rb_exc_raise(rb_exc_new_str(EXCEPTION, rb_sprintf(FORMAT, ##__VA_ARGS__))) -void rb_tr_init_global_constants(void); +// Macros for rb_funcall(). As written they currently only work on Sulong. +// +// We use this pair of macros because ##__VA_ARGS__ args will not +// have macro substitution done on them at the right point in +// preprocessing and will prevent rb_funcall(..., rb_funcall(...)) +// from being expanded correctly. +/* +#define RUBY_FUNCALL_IMPL_0(recv, name) polyglot_invoke(recv, name) +#define RUBY_FUNCALL_IMPL_1(recv, name, V1) polyglot_invoke(recv, name, rb_tr_unwrap(V1)) +#define RUBY_FUNCALL_IMPL_2(recv, name, V1, V2) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2)) +#define RUBY_FUNCALL_IMPL_3(recv, name, V1, V2, V3) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3)) +#define RUBY_FUNCALL_IMPL_4(recv, name, V1, V2, V3, V4) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4)) +#define RUBY_FUNCALL_IMPL_5(recv, name, V1, V2, V3, V4, V5) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5)) +#define RUBY_FUNCALL_IMPL_6(recv, name, V1, V2, V3, V4, V5, V6) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6)) +#define RUBY_FUNCALL_IMPL_7(recv, name, V1, V2, V3, V4, V5, V6, V7) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7)) +#define RUBY_FUNCALL_IMPL_8(recv, name, V1, V2, V3, V4, V5, V6, V7, V8) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8)) +#define RUBY_FUNCALL_IMPL_9(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9)) +#define RUBY_FUNCALL_IMPL_10(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10)) +#define RUBY_FUNCALL_IMPL_11(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11)) +#define RUBY_FUNCALL_IMPL_12(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12)) +#define RUBY_FUNCALL_IMPL_13(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13)) +#define RUBY_FUNCALL_IMPL_14(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14)) +#define RUBY_FUNCALL_IMPL_15(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15)) +#define RUBY_FUNCALL_IMPL_16(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15), rb_tr_unwrap(V16)) +#define RUBY_FUNCALL_IMPL_17(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15), rb_tr_unwrap(V16), rb_tr_unwrap(V17)) +#define RUBY_FUNCALL_IMPL_18(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15), rb_tr_unwrap(V16), rb_tr_unwrap(V17), rb_tr_unwrap(V18)) +#define FUNCALL_IMPL(RECV, MESG, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, NAME, ...) NAME +#define RUBY_FUNCALL_IMPL_NO_WRAP(RECV, NAME, ...) FUNCALL_IMPL(RECV, NAME, ##__VA_ARGS__, RUBY_FUNCALL_IMPL_18, RUBY_FUNCALL_IMPL_17, RUBY_FUNCALL_IMPL_16, RUBY_FUNCALL_IMPL_15, RUBY_FUNCALL_IMPL_14, RUBY_FUNCALL_IMPL_13, RUBY_FUNCALL_IMPL_12, RUBY_FUNCALL_IMPL_11, RUBY_FUNCALL_IMPL_10, RUBY_FUNCALL_IMPL_9, RUBY_FUNCALL_IMPL_8, RUBY_FUNCALL_IMPL_7, RUBY_FUNCALL_IMPL_6, RUBY_FUNCALL_IMPL_5, RUBY_FUNCALL_IMPL_4, RUBY_FUNCALL_IMPL_3, RUBY_FUNCALL_IMPL_2, RUBY_FUNCALL_IMPL_1, RUBY_FUNCALL_IMPL_0)(RECV, NAME, ##__VA_ARGS__) +#define RUBY_FUNCALL_IMPL(RECV, NAME, ...) rb_tr_wrap(RUBY_FUNCALL_IMPL_NO_WRAP(RECV, NAME, ##__VA_ARGS__)) +#define RUBY_CEXT_FUNCALL(NAME, ...) RUBY_FUNCALL_IMPL(RUBY_CEXT, NAME, ##__VA_ARGS__) -// These refer Ruby global variables and their value can change, -// so we use macros instead of C global variables like MRI, which would be complicated to update. -#define rb_stdin RUBY_CEXT_INVOKE("rb_stdin") -#define rb_stdout RUBY_CEXT_INVOKE("rb_stdout") -#define rb_stderr RUBY_CEXT_INVOKE("rb_stderr") -#define rb_fs RUBY_CEXT_INVOKE("rb_fs") -#define rb_output_fs RUBY_CEXT_INVOKE("rb_output_fs") -#define rb_rs RUBY_CEXT_INVOKE("rb_rs") -#define rb_output_rs RUBY_CEXT_INVOKE("rb_output_rs") -#define rb_default_rs RUBY_CEXT_INVOKE("rb_default_rs") - -int rb_tr_readable(int mode); -int rb_tr_writable(int mode); +#define rb_tr_funcall(object, method, n,...) RUBY_CEXT_FUNCALL("rb_funcall", object, ID2SYM(method), INT2FIX(n), ##__VA_ARGS__) +#define rb_funcall(object, method, ...) rb_tr_funcall(object, method, __VA_ARGS__) +*/ -typedef void* (gvl_call)(void *); +// Optimizations for rb_iv_get() and rb_iv_set() -// Utilities +#define rb_iv_get(obj, name) \ + (__builtin_constant_p(name) ? \ + rb_ivar_get(obj, rb_intern(name)) : \ + rb_iv_get(obj, name)) -#define rb_warn(FORMAT, ...) __extension__ ({ \ -if (polyglot_as_boolean(polyglot_invoke(RUBY_CEXT, "warn?"))) { \ - RUBY_INVOKE(rb_mKernel, "warn", rb_sprintf(FORMAT, ##__VA_ARGS__)); \ -} \ -}) +#define rb_iv_set(obj, name, val) \ + (__builtin_constant_p(name) ? \ + rb_ivar_set(obj, rb_intern(name), val) : \ + rb_iv_set(obj, name, val)) -#define rb_warning(FORMAT, ...) __extension__({ \ -if (polyglot_as_boolean(polyglot_invoke(RUBY_CEXT, "warning?"))) { \ - RUBY_INVOKE(rb_mKernel, "warn", rb_sprintf(FORMAT, ##__VA_ARGS__)); \ -} \ -}) +// rb_scan_args() and rb_scan_args_kw() implementation struct rb_tr_scan_args_parse_data { int pre; @@ -99,127 +134,10 @@ struct rb_tr_scan_args_parse_data { #define rb_scan_args(ARGC, ARGV, FORMAT, ...) SCAN_ARGS_KW_IMPL(__VA_ARGS__, rb_tr_scan_args_kw_10, rb_tr_scan_args_kw_9, rb_tr_scan_args_kw_8, rb_tr_scan_args_kw_7, rb_tr_scan_args_kw_6, rb_tr_scan_args_kw_5, rb_tr_scan_args_kw_4, rb_tr_scan_args_kw_3, rb_tr_scan_args_kw_2, rb_tr_scan_args_kw_1)(RB_SCAN_ARGS_PASS_CALLED_KEYWORDS, ARGC, ARGV, FORMAT, __VA_ARGS__) - -// Invoking ruby methods. - -// These macros implement ways to call the methods on Truffle::CExt -// (RUBY_CEXT_INVOKE) and other ruby objects (RUBY_INVOKE) and handle -// all the unwrapping of arguments. They also come in _NO_WRAP -// variants which will not attempt to wrap the result. This is -// important if it is not an actual ruby object being returned as an -// error will be raised when attempting to wrap such objects. - -// Internal macros for the implementation -#define RUBY_INVOKE_IMPL_0(recv, name) polyglot_invoke(recv, name) -#define RUBY_INVOKE_IMPL_1(recv, name, V1) polyglot_invoke(recv, name, rb_tr_unwrap(V1)) -#define RUBY_INVOKE_IMPL_2(recv, name, V1, V2) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2)) -#define RUBY_INVOKE_IMPL_3(recv, name, V1, V2, V3) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3)) -#define RUBY_INVOKE_IMPL_4(recv, name, V1, V2, V3, V4) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4)) -#define RUBY_INVOKE_IMPL_5(recv, name, V1, V2, V3, V4, V5) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5)) -#define RUBY_INVOKE_IMPL_6(recv, name, V1, V2, V3, V4, V5, V6) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6)) -#define RUBY_INVOKE_IMPL_7(recv, name, V1, V2, V3, V4, V5, V6, V7) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7)) -#define RUBY_INVOKE_IMPL_8(recv, name, V1, V2, V3, V4, V5, V6, V7, V8) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8)) -#define RUBY_INVOKE_IMPL_9(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9)) -#define RUBY_INVOKE_IMPL_10(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10)) -#define RUBY_INVOKE_IMPL_11(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11)) -#define RUBY_INVOKE_IMPL_12(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12)) -#define RUBY_INVOKE_IMPL_13(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13)) -#define RUBY_INVOKE_IMPL_14(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14)) -#define RUBY_INVOKE_IMPL_15(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15)) -#define RUBY_INVOKE_IMPL_16(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15), rb_tr_unwrap(V16)) -#define RUBY_INVOKE_IMPL_17(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15), rb_tr_unwrap(V16), rb_tr_unwrap(V17)) -#define RUBY_INVOKE_IMPL_18(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15), rb_tr_unwrap(V16), rb_tr_unwrap(V17), rb_tr_unwrap(V18)) -#define INVOKE_IMPL(RECV, MESG, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, NAME, ...) NAME -#define RUBY_INVOKE_IMPL_NO_WRAP(RECV, NAME, ...) INVOKE_IMPL(RECV, NAME, ##__VA_ARGS__, RUBY_INVOKE_IMPL_18, RUBY_INVOKE_IMPL_17, RUBY_INVOKE_IMPL_16, RUBY_INVOKE_IMPL_15, RUBY_INVOKE_IMPL_14, RUBY_INVOKE_IMPL_13, RUBY_INVOKE_IMPL_12, RUBY_INVOKE_IMPL_11, RUBY_INVOKE_IMPL_10, RUBY_INVOKE_IMPL_9, RUBY_INVOKE_IMPL_8, RUBY_INVOKE_IMPL_7, RUBY_INVOKE_IMPL_6, RUBY_INVOKE_IMPL_5, RUBY_INVOKE_IMPL_4, RUBY_INVOKE_IMPL_3, RUBY_INVOKE_IMPL_2, RUBY_INVOKE_IMPL_1, RUBY_INVOKE_IMPL_0)(RECV, NAME, ##__VA_ARGS__) -#define RUBY_INVOKE_IMPL(RECV, NAME, ...) rb_tr_wrap(RUBY_INVOKE_IMPL_NO_WRAP(RECV, NAME, ##__VA_ARGS__)) - - -#define rb_id2sym(x) rb_tr_wrap(rb_tr_id2sym(x)) -#define rb_sym2id(x) rb_tr_sym2id(x) - -// Public macros used in this header and ruby.c -#define RUBY_INVOKE(RECV, NAME, ...) RUBY_INVOKE_IMPL(rb_tr_unwrap(RECV), NAME, ##__VA_ARGS__) -#define RUBY_INVOKE_NO_WRAP(RECV, NAME, ...) RUBY_INVOKE_IMPL_NO_WRAP(rb_tr_unwrap(RECV), NAME, ##__VA_ARGS__) - -#define RUBY_CEXT_INVOKE(NAME, ...) RUBY_INVOKE_IMPL(RUBY_CEXT, NAME, ##__VA_ARGS__) -#define RUBY_CEXT_INVOKE_NO_WRAP(NAME, ...) RUBY_INVOKE_IMPL_NO_WRAP(RUBY_CEXT, NAME, ##__VA_ARGS__) - -// Calls - -// We use this pair of macros because ##__VA_ARGS__ args will not -// have macro substitution done on them at the right point in -// preprocessing and will prevent rb_funcall(..., rb_funcall(...)) -// from being expanded correctly. - -#define RUBY_FUNCALL_IMPL_0(recv, name) polyglot_invoke(recv, name) -#define RUBY_FUNCALL_IMPL_1(recv, name, V1) polyglot_invoke(recv, name, rb_tr_unwrap(V1)) -#define RUBY_FUNCALL_IMPL_2(recv, name, V1, V2) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2)) -#define RUBY_FUNCALL_IMPL_3(recv, name, V1, V2, V3) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3)) -#define RUBY_FUNCALL_IMPL_4(recv, name, V1, V2, V3, V4) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4)) -#define RUBY_FUNCALL_IMPL_5(recv, name, V1, V2, V3, V4, V5) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5)) -#define RUBY_FUNCALL_IMPL_6(recv, name, V1, V2, V3, V4, V5, V6) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6)) -#define RUBY_FUNCALL_IMPL_7(recv, name, V1, V2, V3, V4, V5, V6, V7) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7)) -#define RUBY_FUNCALL_IMPL_8(recv, name, V1, V2, V3, V4, V5, V6, V7, V8) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8)) -#define RUBY_FUNCALL_IMPL_9(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9)) -#define RUBY_FUNCALL_IMPL_10(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10)) -#define RUBY_FUNCALL_IMPL_11(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11)) -#define RUBY_FUNCALL_IMPL_12(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12)) -#define RUBY_FUNCALL_IMPL_13(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13)) -#define RUBY_FUNCALL_IMPL_14(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14)) -#define RUBY_FUNCALL_IMPL_15(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15)) -#define RUBY_FUNCALL_IMPL_16(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15), rb_tr_unwrap(V16)) -#define RUBY_FUNCALL_IMPL_17(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15), rb_tr_unwrap(V16), rb_tr_unwrap(V17)) -#define RUBY_FUNCALL_IMPL_18(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_id2sym(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15), rb_tr_unwrap(V16), rb_tr_unwrap(V17), rb_tr_unwrap(V18)) -#define FUNCALL_IMPL(RECV, MESG, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, NAME, ...) NAME -#define RUBY_FUNCALL_IMPL_NO_WRAP(RECV, NAME, ...) FUNCALL_IMPL(RECV, NAME, ##__VA_ARGS__, RUBY_FUNCALL_IMPL_18, RUBY_FUNCALL_IMPL_17, RUBY_FUNCALL_IMPL_16, RUBY_FUNCALL_IMPL_15, RUBY_FUNCALL_IMPL_14, RUBY_FUNCALL_IMPL_13, RUBY_FUNCALL_IMPL_12, RUBY_FUNCALL_IMPL_11, RUBY_FUNCALL_IMPL_10, RUBY_FUNCALL_IMPL_9, RUBY_FUNCALL_IMPL_8, RUBY_FUNCALL_IMPL_7, RUBY_FUNCALL_IMPL_6, RUBY_FUNCALL_IMPL_5, RUBY_FUNCALL_IMPL_4, RUBY_FUNCALL_IMPL_3, RUBY_FUNCALL_IMPL_2, RUBY_FUNCALL_IMPL_1, RUBY_FUNCALL_IMPL_0)(RECV, NAME, ##__VA_ARGS__) -#define RUBY_FUNCALL_IMPL(RECV, NAME, ...) rb_tr_wrap(RUBY_FUNCALL_IMPL_NO_WRAP(RECV, NAME, ##__VA_ARGS__)) -#define RUBY_CEXT_FUNCALL(NAME, ...) RUBY_FUNCALL_IMPL(RUBY_CEXT, NAME, ##__VA_ARGS__) - -#define rb_tr_funcall(object, method, n,...) RUBY_CEXT_FUNCALL("rb_funcall", object, ID2SYM(method), INT2FIX(n), ##__VA_ARGS__) -#define rb_funcall(object, method, ...) rb_tr_funcall(object, method, __VA_ARGS__) - -// Exceptions - -#define rb_raise(EXCEPTION, FORMAT, ...) \ - rb_exc_raise(rb_exc_new_str(EXCEPTION, rb_sprintf(FORMAT, ##__VA_ARGS__))) - -// Additional non-standard -VALUE rb_java_class_of(VALUE val); -VALUE rb_java_to_string(VALUE val); -VALUE rb_equal_opt(VALUE a, VALUE b); -int rb_encdb_alias(const char *alias, const char *orig); -VALUE rb_ivar_lookup(VALUE object, const char *name, VALUE default_value); - -// Additional macro to make sure the RSTRING_PTR and the bytes are in native memory, for testing. -#define NATIVE_RSTRING_PTR(str) ((char*) polyglot_as_i64(RUBY_CEXT_INVOKE_NO_WRAP("NATIVE_RSTRING_PTR", str))) - -// Inline implementations - -ALWAYS_INLINE(static VALUE rb_tr_string_value(VALUE *value_pointer)); -static inline VALUE rb_tr_string_value(VALUE *value_pointer) { - VALUE value = *value_pointer; - if (!RB_TYPE_P(value, T_STRING)) { - value = rb_str_to_str(value); - *value_pointer = value; - } - return value; -} - -ALWAYS_INLINE(static char *rb_tr_string_value_ptr(VALUE *value_pointer)); -static inline char *rb_tr_string_value_ptr(VALUE *value_pointer) { - VALUE string = rb_tr_string_value(value_pointer); - return RSTRING_PTR(string); -} - -ALWAYS_INLINE(static char *rb_tr_string_value_cstr(VALUE *value_pointer)); -static inline char *rb_tr_string_value_cstr(VALUE *value_pointer) { - VALUE string = rb_tr_string_value(value_pointer); - RUBY_CEXT_INVOKE("rb_string_value_cstr_check", string); - return RSTRING_PTR(string); -} - void rb_tr_scan_args_kw_parse(const char *format, struct rb_tr_scan_args_parse_data *parse_data); +bool rb_tr_scan_args_test_kwargs(VALUE kwargs, VALUE raise_error); + ALWAYS_INLINE(static int rb_tr_scan_args_kw_int(int kw_flag, int argc, VALUE *argv, struct rb_tr_scan_args_parse_data parse_data, VALUE *v1, VALUE *v2, VALUE *v3, VALUE *v4, VALUE *v5, VALUE *v6, VALUE *v7, VALUE *v8, VALUE *v9, VALUE *v10)); static inline int rb_tr_scan_args_kw_int(int kw_flag, int argc, VALUE *argv, struct rb_tr_scan_args_parse_data parse_data, VALUE *v1, VALUE *v2, VALUE *v3, VALUE *v4, VALUE *v5, VALUE *v6, VALUE *v7, VALUE *v8, VALUE *v9, VALUE *v10) { @@ -290,7 +208,7 @@ static inline int rb_tr_scan_args_kw_int(int kw_flag, int argc, VALUE *argv, str /* Ruby 3: Remove if branch, as it will not attempt to split hashes */ if (!NIL_P(hash)) { - if (!polyglot_as_boolean(RUBY_CEXT_INVOKE_NO_WRAP("test_kwargs", argv[argc - 1], Qfalse))) { + if (!rb_tr_scan_args_test_kwargs(argv[argc - 1], Qfalse)) { // Does not handle the case where "The last argument is split into positional and keyword parameters" // Instead assumes that it is all one hash parse_data.kwargs = false; @@ -342,7 +260,7 @@ static inline int rb_tr_scan_args_kw_int(int kw_flag, int argc, VALUE *argv, str } else if (parse_data.kwargs && !taken_kwargs) { if (argn < argc) { arg = argv[argn]; - RUBY_CEXT_INVOKE_NO_WRAP("test_kwargs", arg, Qtrue); + rb_tr_scan_args_test_kwargs(arg, Qtrue); argn++; found_kwargs = true; } else { @@ -404,16 +322,6 @@ static inline int rb_tr_scan_args_kw_non_const(int kw_flag, int argc, VALUE *arg return rb_tr_scan_args_kw_int(kw_flag, argc, argv, parse_data, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10); } -#define rb_iv_get(obj, name) \ - (__builtin_constant_p(name) ? \ - rb_ivar_get(obj, rb_intern(name)) : \ - rb_iv_get(obj, name)) - -#define rb_iv_set(obj, name, val) \ - (__builtin_constant_p(name) ? \ - rb_ivar_set(obj, rb_intern(name), val) : \ - rb_iv_set(obj, name, val)) - #if defined(__cplusplus) } #endif diff --git a/lib/gems/gems/debug-1.7.1/ext/debug/debug.c b/lib/gems/gems/debug-1.7.1/ext/debug/debug.c index 9eaac2bbd521..efac0f87f140 100644 --- a/lib/gems/gems/debug-1.7.1/ext/debug/debug.c +++ b/lib/gems/gems/debug-1.7.1/ext/debug/debug.c @@ -210,5 +210,7 @@ Init_debug(void) rb_define_method(rb_cISeq, "last_line", iseq_last_line, 0); #endif +#ifndef TRUFFLERUBY Init_iseq_collector(); +#endif } diff --git a/lib/gems/gems/debug-1.7.1/ext/debug/iseq_collector.c b/lib/gems/gems/debug-1.7.1/ext/debug/iseq_collector.c index f87c4f9781db..101c7aebb5ae 100644 --- a/lib/gems/gems/debug-1.7.1/ext/debug/iseq_collector.c +++ b/lib/gems/gems/debug-1.7.1/ext/debug/iseq_collector.c @@ -1,5 +1,7 @@ #include +#ifndef TRUFFLERUBY + VALUE rb_iseqw_new(VALUE v); void rb_objspace_each_objects( int (*callback)(void *start, void *end, size_t stride, void *data), @@ -89,3 +91,5 @@ Init_iseq_collector(void) rb_define_singleton_method(rb_mObjSpace, "each_iseq", each_iseq, 0); rb_define_singleton_method(rb_mObjSpace, "count_iseq", count_iseq, 0); } + +#endif diff --git a/lib/mri/mkmf.rb b/lib/mri/mkmf.rb index ac4a8fe1825c..5cac93e57987 100644 --- a/lib/mri/mkmf.rb +++ b/lib/mri/mkmf.rb @@ -2346,7 +2346,7 @@ def depend_rules(depend) # +VPATH+ and added to the list of +INCFLAGS+. # def create_makefile(target, srcprefix = nil) - if defined?(::TruffleRuby) and $LIBRUBYARG.to_s.strip.empty? + if defined?(::TruffleRuby) and $LIBRUBYARG == nil # $LIBRUBYARG was explicitly unset, the built library is not a C extension but used with FFI (e.g., sassc does). # Since $LIBRUBYARG is unset we won't link to libgraalvm-llvm.so, which is expected. # In the case the library uses C++ code, libc++.so/libc++abi.so will be linked and needs to be found by NFI. diff --git a/lib/truffle/rbconfig.rb b/lib/truffle/rbconfig.rb index 711988ef2a5a..a47cec63a15e 100644 --- a/lib/truffle/rbconfig.rb +++ b/lib/truffle/rbconfig.rb @@ -52,7 +52,7 @@ module RbConfig ruby_install_name = 'truffleruby' ruby_base_name = 'ruby' - ruby_abi_version = Truffle::GemUtil.abi_version + ruby_abi_version = Truffle::GemUtil::ABI_VERSION arch = "#{host_cpu}-#{host_os}" libs = '' @@ -82,7 +82,7 @@ module RbConfig '-ferror-limit=500' ] - defs = "-DTRUFFLERUBY_ABI_VERSION=#{ruby_abi_version}" + defs = '' cppflags = '' ldflags = '' dldflags = Truffle::Platform.darwin? ? '-Wl,-undefined,dynamic_lookup' : '' @@ -118,7 +118,7 @@ module RbConfig end # We do not link to libtruffleruby here to workaround GR-29448 - librubyarg = '-lgraalvm-llvm' + librubyarg = '' warnflags = warnflags.join(' ') diff --git a/lib/truffle/truffle/cext.rb b/lib/truffle/truffle/cext.rb index 57f9b04df458..f68306a3868a 100644 --- a/lib/truffle/truffle/cext.rb +++ b/lib/truffle/truffle/cext.rb @@ -17,12 +17,25 @@ module Truffle::CExt DATA_TYPE = Primitive.object_hidden_var_create :data_type - DATA_HOLDER = Primitive.object_hidden_var_create :data_holder + DATA_STRUCT = Primitive.object_hidden_var_create :data_struct # struct RData* or struct RTypedData* + DATA_MARKER = Primitive.object_hidden_var_create :data_marker DATA_MEMSIZER = Primitive.object_hidden_var_create :data_memsizer RB_TYPE = Primitive.object_hidden_var_create :rb_type ALLOCATOR_FUNC = Primitive.object_hidden_var_create :allocator_func RB_IO_STRUCT = Primitive.object_hidden_var_create :rb_io_struct + SULONG = Truffle::Boot.get_option('cexts-sulong') + NFI = !SULONG + + if NFI + # rb_block_call_func_t + RB_BLOCK_CALL_FUNC_SIGNATURE = Primitive.interop_eval_nfi('(pointer,pointer,sint32,pointer,pointer):pointer') + POINTER_TO_VOID_SIGNATURE = Primitive.interop_eval_nfi('(pointer):void') + POINTER_TO_POINTER_SIGNATURE = Primitive.interop_eval_nfi('(pointer):pointer') + TWO_POINTERS_TO_POINTER_SIGNATURE = Primitive.interop_eval_nfi('(pointer,pointer):pointer') + THREE_POINTERS_TO_INT_SIGNATURE = Primitive.interop_eval_nfi('(pointer,pointer,pointer):sint32') + end + extend self T_NONE = 0x00 @@ -123,12 +136,41 @@ def self.register_libtruffleruby(libtruffleruby) SET_LIBTRUFFLERUBY.call(libtruffleruby) end + def self.init_libtrufflerubytrampoline(libtrampoline) + keep_alive = [] + + # rb_tr_longwrap_sig = Primitive.interop_eval_nfi('(sint64):pointer') + # rb_tr_longwrap = rb_tr_longwrap_sig.createClosure(self.rb_tr_wrap_function) + # + # globals_args = [ + # rb_tr_longwrap + # ] + # keep_alive << globals_args + # + # init_globals = libtrampoline['rb_tr_trampoline_init_globals'] + # init_globals = Primitive.interop_eval_nfi("(#{Array.new(globals_args.size, 'pointer').join(',')}):void").bind(init_globals) + # init_globals.call(*globals_args) + + init_functions = libtrampoline[:rb_tr_trampoline_init_functions] + init_functions = Primitive.interop_eval_nfi('((string):pointer):void').bind(init_functions) + init_functions.call(-> name { LIBTRUFFLERUBY[name] }) + + init_constants = libtrampoline[:rb_tr_trampoline_init_global_constants] + init_constants = Primitive.interop_eval_nfi('((string):pointer):void').bind(init_constants) + # TODO: use a Hash instead to be faster? (or Array with fixed indices) + init_constants.call(-> name { value = Truffle::CExt.send(name); keep_alive << value; Primitive.cext_wrap(value) }) + + LIBTRUFFLERUBY[:set_rb_tr_rb_f_notimplement].call(libtrampoline[:rb_f_notimplement]) + + @init_libtrufflerubytrampoline_keep_alive = keep_alive.freeze + end + def supported? Interop.mime_type_supported?('application/x-sulong-library') end def check_abi_version(embedded_abi_version, extension_path) - runtime_abi_version = Truffle::GemUtil.abi_version + runtime_abi_version = Truffle::GemUtil::ABI_VERSION if embedded_abi_version != runtime_abi_version message = "The native extension at #{extension_path} has a different ABI version: #{embedded_abi_version.inspect} " \ "than the running TruffleRuby: #{runtime_abi_version.inspect}" @@ -192,7 +234,7 @@ def rb_type(value) def rb_tr_cached_type(value, type) if type == T_NONE - if Primitive.data_holder_is_holder?(Primitive.object_hidden_var_get(value, DATA_HOLDER)) + if Primitive.object_hidden_var_defined?(value, DATA_STRUCT) T_DATA else T_OBJECT @@ -262,7 +304,7 @@ def rb_check_type(value, type) end end - def rbimpl_rtypeddata_p(obj) + def RTYPEDDATA_P(obj) Primitive.object_hidden_var_defined?(obj, DATA_TYPE) end @@ -496,6 +538,8 @@ def rb_f_global_variables end def rb_ivar_foreach(object, func, arg) + func = THREE_POINTERS_TO_INT_SIGNATURE.bind(func) if NFI + keys_and_vals = [] if Primitive.is_a?(object, Module) keys_and_vals << :__classpath__ @@ -686,7 +730,11 @@ def rb_str_new_frozen(value) def rb_tracepoint_new(events, func, data) TracePoint.new(*events_to_events_array(events)) do |tp| - Primitive.call_with_c_mutex_and_frame(func, [tp, data], Primitive.caller_special_variables_if_available, nil) + Primitive.call_with_c_mutex_and_frame( + func, + [tp, data], + Primitive.caller_special_variables_if_available, + nil) end end @@ -993,6 +1041,7 @@ def rb_hash_set_ifnone(hash, value) ST_REPLACE = 4 def rb_hash_foreach(hash, func, farg) + func = THREE_POINTERS_TO_INT_SIGNATURE.bind(func) if NFI hash.each do |key, value| st_result = Truffle::Interop.execute_without_conversion(func, Primitive.cext_wrap(key), Primitive.cext_wrap(value), farg) @@ -1020,6 +1069,7 @@ def rb_path_to_class(path) end def rb_proc_new(function, value) + function = RB_BLOCK_CALL_FUNC_SIGNATURE.bind(function) if NFI Proc.new do |*args, &block| Primitive.call_with_c_mutex_and_frame_and_unwrap(function, [ Primitive.cext_wrap(args.first), # yieldarg @@ -1055,6 +1105,7 @@ def rb_proc_call(prc, args) # throw it and allow normal error handling to continue. def rb_protect(function, arg, write_status, status) + function = POINTER_TO_POINTER_SIGNATURE.bind(function) if NFI # We wrap nil here to avoid wrapping any result returned, as the # function called will do that. In general we try not to touch the # values passed in or out of protected functions as C extensions @@ -1282,8 +1333,13 @@ def rb_enumeratorize(obj, meth, args) def rb_enumeratorize_with_size(obj, meth, args, size_fn) return rb_enumeratorize(obj, meth, args) if Primitive.interop_null?(size_fn) + size_fn = Primitive.interop_eval_nfi('(pointer,pointer,pointer):pointer').bind(size_fn) if NFI enum = obj.to_enum(meth, *args) do - Primitive.call_with_c_mutex_and_frame_and_unwrap(size_fn, [Primitive.cext_wrap(obj), Primitive.cext_wrap(args), Primitive.cext_wrap(enum)], Primitive.caller_special_variables_if_available, nil) + Primitive.call_with_c_mutex_and_frame_and_unwrap( + size_fn, + [Primitive.cext_wrap(obj), Primitive.cext_wrap(args), Primitive.cext_wrap(enum)], + Primitive.caller_special_variables_if_available, + nil) end enum end @@ -1297,8 +1353,13 @@ def rb_newobj_of(ruby_class) end def rb_define_alloc_func(ruby_class, function) + function = POINTER_TO_POINTER_SIGNATURE.bind(function) if NFI ruby_class.singleton_class.define_method(:__allocate__) do - Primitive.call_with_c_mutex_and_frame_and_unwrap(function, [Primitive.cext_wrap(self)], Primitive.caller_special_variables_if_available, nil) + Primitive.call_with_c_mutex_and_frame_and_unwrap( + function, + [Primitive.cext_wrap(self)], + Primitive.caller_special_variables_if_available, + nil) end class << ruby_class private :__allocate__ @@ -1427,6 +1488,7 @@ def rb_mutex_sleep(mutex, timeout) end def rb_mutex_synchronize(mutex, func, arg) + func = POINTER_TO_POINTER_SIGNATURE.bind(func) if NFI mutex.synchronize do Primitive.cext_unwrap(Primitive.interop_execute(func, [arg])) end @@ -1484,22 +1546,63 @@ def rb_nativethread_lock_destroy(lock) end def rb_set_end_proc(func, data) - at_exit { Primitive.call_with_c_mutex_and_frame(func, [data], Primitive.caller_special_variables_if_available, nil) } + at_exit do + func = POINTER_TO_VOID_SIGNATURE.bind(func) if NFI + Primitive.call_with_c_mutex_and_frame(func, [data], Primitive.caller_special_variables_if_available, nil) + end + end + + def mark_object_on_call_exit(object) + Primitive.cext_mark_object_on_call_exit(object) + end + + def RDATA(object) + # A specialized version of rb_check_type(object, T_DATA) + data_struct = Primitive.object_hidden_var_get(object, DATA_STRUCT) + unless data_struct + raise TypeError, "wrong argument type #{Primitive.class(object)} (expected T_DATA)" + end + data_struct end - def define_marker(object, marker) - Primitive.cext_mark_object_on_call_exit(object) unless Truffle::Interop.null?(marker) + def RTYPEDDATA(object) + # A specialized version of rb_check_type(object, T_DATA) + data_struct = Primitive.object_hidden_var_get(object, DATA_STRUCT) + unless data_struct + raise TypeError, "wrong argument type #{Primitive.class(object)} (expected T_DATA)" + end + data_struct + end + + private def data_marker(marker_function, struct) + if Truffle::Interop.null?(marker_function) + nil + else + -> { Primitive.interop_execute(marker_function, [struct]) } + end + end + + private def data_sizer(sizer_function, rtypeddata) + raise unless sizer_function.respond_to?(:call) + proc { + Primitive.call_with_c_mutex_and_frame(sizer_function, [rtypeddata], Primitive.caller_special_variables_if_available, nil) + } end def rb_data_object_wrap(ruby_class, data, mark, free) ruby_class = Object unless ruby_class object = ruby_class.__send__(:__layout_allocate__) - data_holder = Primitive.data_holder_create(data, mark, free) - Primitive.object_hidden_var_set object, DATA_HOLDER, data_holder + if NFI and !Truffle::Interop.null?(mark) + mark = POINTER_TO_VOID_SIGNATURE.bind(mark) + end - Primitive.objectspace_define_data_finalizer object, data_holder unless Truffle::Interop.null?(free) + rdata = LIBTRUFFLERUBY.rb_tr_rdata_create(mark, free, data) + Primitive.object_hidden_var_set object, DATA_STRUCT, rdata + Primitive.object_hidden_var_set object, DATA_MARKER, data_marker(LIBTRUFFLERUBY[:rb_tr_rdata_run_marker], rdata) + # Could use a simpler finalizer if Truffle::Interop.null?(free) + Primitive.objectspace_define_data_finalizer object, LIBTRUFFLERUBY[:rb_tr_rdata_run_finalizer], rdata - define_marker object, mark + Primitive.cext_mark_object_on_call_exit(object) unless Truffle::Interop.null?(mark) object end @@ -1507,14 +1610,22 @@ def rb_data_object_wrap(ruby_class, data, mark, free) def rb_data_typed_object_wrap(ruby_class, data, data_type, mark, free, size) ruby_class = Object unless ruby_class object = ruby_class.__send__(:__layout_allocate__) - data_holder = Primitive.data_holder_create(data, mark, free) - Primitive.object_hidden_var_set object, DATA_TYPE, data_type - Primitive.object_hidden_var_set object, DATA_HOLDER, data_holder - Primitive.object_hidden_var_set object, DATA_MEMSIZER, data_sizer(size, data_holder) unless Truffle::Interop.null?(size) + if NFI and !Truffle::Interop.null?(mark) + mark = POINTER_TO_VOID_SIGNATURE.bind(mark) + end - Primitive.objectspace_define_data_finalizer object, data_holder unless Truffle::Interop.null?(free) + rtypeddata = LIBTRUFFLERUBY.rb_tr_rtypeddata_create(data_type, data) + Primitive.object_hidden_var_set object, DATA_STRUCT, rtypeddata + Primitive.object_hidden_var_set object, DATA_MARKER, data_marker(LIBTRUFFLERUBY[:rb_tr_rtypeddata_run_marker], rtypeddata) + # Could use a simpler finalizer if Truffle::Interop.null?(free) + Primitive.objectspace_define_data_finalizer object, LIBTRUFFLERUBY[:rb_tr_rtypeddata_run_finalizer], rtypeddata - define_marker object, mark + unless Truffle::Interop.null?(size) + Primitive.object_hidden_var_set object, DATA_MEMSIZER, data_sizer(LIBTRUFFLERUBY[:rb_tr_rtypeddata_run_memsizer], rtypeddata) + end + Primitive.object_hidden_var_set object, DATA_TYPE, data_type + + Primitive.cext_mark_object_on_call_exit(object) unless Truffle::Interop.null?(mark) object end @@ -1522,23 +1633,14 @@ def rb_data_typed_object_wrap(ruby_class, data, data_type, mark, free, size) def run_marker(obj) Primitive.array_mark_store(obj) if Primitive.array_store_native?(obj) - data_holder = Primitive.object_hidden_var_get obj, DATA_HOLDER - mark = Primitive.data_holder_get_marker(data_holder) - unless Truffle::Interop.null?(mark) + marker = Primitive.object_hidden_var_get obj, DATA_MARKER + unless Primitive.nil?(marker) create_mark_list(obj) - data = Primitive.data_holder_get_data(data_holder) - mark.call(data) unless Truffle::Interop.null?(data) + marker.call set_mark_list_on_object(obj) end end - def data_sizer(sizer, data_holder) - raise unless sizer.respond_to?(:call) - proc { - Primitive.call_with_c_mutex_and_frame(sizer, [Primitive.data_holder_get_data(data_holder)], Primitive.caller_special_variables_if_available, nil) - } - end - def rb_ruby_verbose_ptr $VERBOSE end @@ -1547,8 +1649,13 @@ def rb_ruby_debug_ptr $DEBUG end - def rb_tr_error(message) - raise RuntimeError, message + def rb_tr_not_implemented(function_name) + raise NotImplementedError, "The C API function #{function_name} is not implemented yet on TruffleRuby" + end + + def rb_f_notimplement + function = caller(1, 1) + raise NotImplementedError, "#{function}() function is unimplemented on this machine" end def test_kwargs(kwargs, raise_error) @@ -1569,6 +1676,8 @@ def send_splatted(object, method, args) def rb_block_call(object, method, args, func, data) object.__send__(method, *args) do |*block_args| + func = RB_BLOCK_CALL_FUNC_SIGNATURE.bind(func) if NFI + Primitive.cext_unwrap(Primitive.call_with_c_mutex(func, [ # Probably need to save the frame here for blocks. Primitive.cext_wrap(block_args.first), data, @@ -1584,21 +1693,25 @@ def rb_module_new end def rb_ensure(b_proc, data1, e_proc, data2) + b_proc = POINTER_TO_POINTER_SIGNATURE.bind(b_proc) if NFI begin Primitive.interop_execute(b_proc, [data1]) ensure + e_proc = POINTER_TO_POINTER_SIGNATURE.bind(e_proc) if NFI Primitive.interop_execute(e_proc, [data2]) end end Truffle::Graal.always_split instance_method(:rb_ensure) def rb_rescue(b_proc, data1, r_proc, data2) + b_proc = POINTER_TO_POINTER_SIGNATURE.bind(b_proc) if NFI begin Primitive.interop_execute(b_proc, [data1]) rescue StandardError => e if Truffle::Interop.null?(r_proc) Primitive.cext_wrap(nil) else + r_proc = TWO_POINTERS_TO_POINTER_SIGNATURE.bind(r_proc) if NFI Primitive.interop_execute(r_proc, [data2, Primitive.cext_wrap(e)]) end end @@ -1606,9 +1719,11 @@ def rb_rescue(b_proc, data1, r_proc, data2) Truffle::Graal.always_split instance_method(:rb_rescue) def rb_rescue2(b_proc, data1, r_proc, data2, rescued) + b_proc = POINTER_TO_POINTER_SIGNATURE.bind(b_proc) if NFI begin Primitive.interop_execute(b_proc, [data1]) rescue *rescued => e + r_proc = TWO_POINTERS_TO_POINTER_SIGNATURE.bind(r_proc) if NFI Primitive.interop_execute(r_proc, [data2, Primitive.cext_wrap(e)]) end end @@ -1617,8 +1732,11 @@ def rb_rescue2(b_proc, data1, r_proc, data2, rescued) def rb_exec_recursive(func, obj, arg) result = nil + func = Primitive.interop_eval_nfi('(pointer,pointer,sint32):pointer').bind(func) if NFI recursive = Truffle::ThreadOperations.detect_recursion(obj) do - result = Primitive.cext_unwrap(Primitive.interop_execute(func, [Primitive.cext_wrap(obj), Primitive.cext_wrap(arg), 0])) + result = Primitive.cext_unwrap(Primitive.interop_execute( + func, + [Primitive.cext_wrap(obj), Primitive.cext_wrap(arg), 0])) end if recursive @@ -1631,6 +1749,7 @@ def rb_exec_recursive(func, obj, arg) def rb_catch_obj(tag, func, data) catch tag do |caught| + func = RB_BLOCK_CALL_FUNC_SIGNATURE.bind(func) if NFI Primitive.cext_unwrap(Primitive.call_with_c_mutex(func, [ Primitive.cext_wrap(caught), Primitive.cext_wrap(data), @@ -1679,15 +1798,6 @@ def yield_no_block raise LocalJumpError end - def warn? - !Primitive.nil?($VERBOSE) - end - - def warning? - # has to return true or false - true == $VERBOSE - end - def rb_time_nano_new(sec, nsec) ORIGINAL_TIME_AT.call(sec, nsec, :nanosecond) end @@ -1713,17 +1823,25 @@ def rb_time_interval_acceptable(time_val) end def rb_thread_create(fn, args) + fn = POINTER_TO_POINTER_SIGNATURE.bind(fn) if NFI Thread.new do Primitive.call_with_c_mutex_and_frame(fn, [args], Primitive.caller_special_variables_if_available, nil) end end def rb_thread_call_with_gvl(function, data) + function = POINTER_TO_POINTER_SIGNATURE.bind(function) if NFI Primitive.call_with_c_mutex(function, [data]) end def rb_thread_call_without_gvl(function, data1, unblock, data2) - Primitive.send_without_cext_lock(self, :rb_thread_call_without_gvl_inner, [function, data1, unblock, data2], nil) + function = POINTER_TO_POINTER_SIGNATURE.bind(function) if NFI + unblock = POINTER_TO_VOID_SIGNATURE.bind(unblock) if NFI && !Primitive.nil?(unblock) + Primitive.send_without_cext_lock( + self, + :rb_thread_call_without_gvl_inner, + [function, data1, unblock, data2], + nil) end private def rb_thread_call_without_gvl_inner(function, data1, unblock, data2) @@ -1850,12 +1968,22 @@ def rb_define_hooked_variable(name, gvar, getter, setter) name = "$#{name}" unless name.start_with?('$') id = name.to_sym + getter = TWO_POINTERS_TO_POINTER_SIGNATURE.bind(getter) if NFI getter_proc = -> { - Primitive.call_with_c_mutex_and_frame_and_unwrap(getter, [Primitive.cext_wrap(id), gvar, Primitive.cext_wrap(nil)], Primitive.caller_special_variables_if_available, nil) + Primitive.call_with_c_mutex_and_frame_and_unwrap( + getter, + [Primitive.cext_wrap(id), gvar], + Primitive.caller_special_variables_if_available, + nil) } + setter = Primitive.interop_eval_nfi('(pointer,pointer,pointer):void').bind(setter) if NFI setter_proc = -> value { - Primitive.call_with_c_mutex_and_frame(setter, [Primitive.cext_wrap(value), Primitive.cext_wrap(id), gvar, Primitive.cext_wrap(nil)], Primitive.caller_special_variables_if_available, nil) + Primitive.call_with_c_mutex_and_frame( + setter, + [Primitive.cext_wrap(value), Primitive.cext_wrap(id), gvar], + Primitive.caller_special_variables_if_available, + nil) } Truffle::KernelOperations.define_hooked_variable id, getter_proc, setter_proc @@ -1879,10 +2007,6 @@ def rb_to_encoding(encoding) RbEncoding.get(encoding) end - def GetOpenFile(io) - Primitive.object_hidden_var_get(io, RB_IO_STRUCT) || RbIO.new(io) - end - def rb_enc_from_encoding(rb_encoding) rb_encoding.encoding end @@ -1895,7 +2019,7 @@ def native_string?(string) Primitive.string_is_native?(string) end - def NATIVE_RSTRING_PTR(string) + def RSTRING_PTR(string) Primitive.string_pointer_to_native(string) end @@ -1976,6 +2100,7 @@ def rb_fiber_current def rb_fiber_new(function, value) Fiber.new do |*args| + function = RB_BLOCK_CALL_FUNC_SIGNATURE.bind(function) if NFI Primitive.call_with_c_mutex_and_frame_and_unwrap(function, [ Primitive.cext_wrap(args.first), # yieldarg nil, # procarg, @@ -1992,7 +2117,7 @@ def rb_tr_pointer(pointer) Truffle::FFI::Pointer.new(pointer) end - def rb_exception_set_message(e, mesg) + def rb_exc_set_message(e, mesg) Primitive.exception_set_message(e, mesg) end @@ -2039,4 +2164,49 @@ def rb_eval_cmd_kw(cmd, args, kw_splat) def rb_warning_category_enabled_p(category) Warning[category] end + + def rb_tr_flags(object) + Truffle::CExt::RBasic.new(object).compute_flags + end + + def rb_tr_set_flags(object, flags) + Truffle::CExt::RBasic.new(object).set_flags(flags) + end + + def rb_io_get_write_io(io) + if Primitive.is_a?(io, IO::BidirectionalPipe) + Primitive.object_ivar_get(io, :@write) + else + io + end + end + + def new_memory_pointer(size) + Truffle::FFI::MemoryPointer.new(size) + end + + def rb_io_mode(io) + io.instance_variable_get(:@mode) + end + + def rb_io_path(io) + io.instance_variable_get(:@path) + end + + def rb_tr_io_pointer(io) + Primitive.object_hidden_var_get(io, RB_IO_STRUCT) + end + + def rb_tr_io_attach_pointer(io, rb_io_t) + unless Primitive.is_a?(rb_io_t, Truffle::FFI::MemoryPointer) + raise 'The rb_io_t must be a MemoryPointer to keep it alive as long as IO object' + end + Primitive.object_hidden_var_set(io, RB_IO_STRUCT, rb_io_t) + end + + def rb_enc_alias(alias_name, original_name) + enc = Encoding.find(original_name) + Truffle::EncodingOperations.define_alias(enc, alias_name) + enc + end end diff --git a/lib/truffle/truffle/cext_constants.rb b/lib/truffle/truffle/cext_constants.rb index 708b3767c4fc..02dffa7cd167 100644 --- a/lib/truffle/truffle/cext_constants.rb +++ b/lib/truffle/truffle/cext_constants.rb @@ -8,7 +8,7 @@ # GNU General Public License version 2, or # GNU Lesser General Public License version 2.1. -# From ./tool/generate-cext-constants.rb +# From tool/generate-cext-constants.rb module Truffle::CExt def rb_cArray @@ -319,4 +319,8 @@ def rb_eFatal Truffle::CExt.rb_const_get(Object, 'fatal') end + def rb_argv0 + $0 + end + end diff --git a/lib/truffle/truffle/cext_ruby.rb b/lib/truffle/truffle/cext_ruby.rb index 3d1e4281956d..72fd463bf280 100644 --- a/lib/truffle/truffle/cext_ruby.rb +++ b/lib/truffle/truffle/cext_ruby.rb @@ -20,6 +20,16 @@ def rb_define_method(mod, name, function, argc) raise ArgumentError, "arity out of range: #{argc} for -2..15" end + if argc == -1 # (int argc, VALUE *argv, VALUE obj) + sig = '(sint32,pointer,pointer):uint64' + elsif argc == -2 # (VALUE obj, VALUE rubyArrayArgs) + sig = '(pointer,pointer):uint64' + elsif argc >= 0 # (VALUE obj); (VALUE obj, VALUE arg1); (VALUE obj, VALUE arg1, VALUE arg2); ... + sig = -"(#{Array.new(1 + argc, 'pointer').join(',')}):uint64" + end + + function = Primitive.interop_eval_nfi(sig).bind(function) if NFI + method_body = Truffle::Graal.copy_captured_locals -> *args, &block do if argc == -1 # (int argc, VALUE *argv, VALUE obj) args = [args.size, Truffle::CExt.RARRAY_PTR(args), Primitive.cext_wrap(self)] @@ -37,8 +47,7 @@ def rb_define_method(mod, name, function, argc) # Using raw execute instead of #call here to avoid argument conversion # We must set block argument if given here so that the - # `rb_block_*` functions will be able to find it by walking the - # stack. + # `rb_block_*` functions will be able to find it by walking the stack. res = Primitive.call_with_c_mutex_and_frame_and_unwrap(function, args, Primitive.caller_special_variables_if_available, block) Primitive.thread_set_exception(exc) res diff --git a/lib/truffle/truffle/cext_structs.rb b/lib/truffle/truffle/cext_structs.rb index ad71c204c6d5..e722f4608a0e 100644 --- a/lib/truffle/truffle/cext_structs.rb +++ b/lib/truffle/truffle/cext_structs.rb @@ -13,27 +13,6 @@ # instance as function return value). module Truffle::CExt - def RDATA(object) - # A specialized version of rb_check_type(object, T_DATA) - data_holder = Primitive.object_hidden_var_get(object, DATA_HOLDER) - unless data_holder - raise TypeError, "wrong argument type #{Primitive.class(object)} (expected T_DATA)" - end - - RData.new(object, data_holder) - end - - def RDATA_PTR(object) - # A specialized version of rb_check_type(object, T_DATA) - data_holder = Primitive.object_hidden_var_get(object, DATA_HOLDER) - unless data_holder - raise TypeError, "wrong argument type #{Primitive.class(object)} (expected T_DATA)" - end - - Primitive.cext_mark_object_on_call_exit(object) unless Truffle::Interop.null?(Primitive.data_holder_get_marker(data_holder)) - Primitive.data_holder_get_data(data_holder) - end - def RBASIC(object) if Primitive.immediate_value?(object) raise TypeError, "immediate values don't include the RBasic struct" @@ -44,108 +23,6 @@ def RBASIC(object) def RARRAY_PTR(array) RArrayPtr.new(array) end - - def RFILE(file) - RFile.new(RBASIC(file), GetOpenFile(file)) - end -end - -# ruby.h: `struct RData` and `struct RTypedData` -class Truffle::CExt::RData - def initialize(object, data_holder) - @object = object - @data_holder = data_holder - end - - private - - def polyglot_has_members? - true - end - - def polyglot_members(internal) - %w[data type typed_flag dmark dfree basic] - end - - def polyglot_read_member(name) - case name - when 'data' - Primitive.cext_mark_object_on_call_exit(@object) unless Truffle::Interop.null?(Primitive.data_holder_get_marker(@data_holder)) - Primitive.data_holder_get_data(@data_holder) - when 'type' - type - when 'typed_flag' - type ? 1 : 0 - when 'dmark' - Primitive.data_holder_get_marker(@data_holder) - when 'dfree' - Primitive.data_holder_get_free(@data_holder) - when 'basic' - get_basic - else - raise Truffle::Interop::UnknownIdentifierException - end - end - - def polyglot_write_member(name, value) - case name - when 'data' - Primitive.data_holder_set_data(@data_holder, value) - when 'dfree' - Primitive.data_holder_set_free(@data_holder, value) - else - raise Truffle::Interop::UnknownIdentifierException - end - - end - - def polyglot_remove_member(name) - raise Truffle::Interop::UnsupportedMessageException - end - - def polyglot_invoke_member(name, *args) - raise Truffle::Interop::UnsupportedMessageException - end - - def polyglot_member_readable?(name) - name == 'data' or name == 'type' or name == 'typed_flag' or name == 'dmark' or name == 'dfree' or name == 'basic' - end - - def polyglot_member_modifiable?(name) - name == 'data' or name == 'dfree' - end - - def polyglot_member_removable?(name) - false - end - - def polyglot_member_insertable?(name) - false - end - - def polyglot_member_invocable?(name) - false - end - - def polyglot_member_internal?(name) - false - end - - def polyglot_has_member_read_side_effects?(name) - false - end - - def polyglot_has_member_write_side_effects?(name) - false - end - - def get_basic - @basic ||= Truffle::CExt::RBasic.new(@object) - end - - def type - Primitive.object_hidden_var_get(@object, Truffle::CExt::DATA_TYPE) - end end # ruby.h: `struct RBasic` @@ -164,8 +41,6 @@ def initialize(object) @object = object end - private - def user_flags Primitive.object_hidden_var_get(@object, USER_FLAGS) || 0 end @@ -228,73 +103,8 @@ def set_flags(flags) raise ArgumentError, "can't unfreeze object" end - raise ArgumentError, "unsupported remaining flags: #{flags_to_string(flags)}" if flags != 0 end - - def polyglot_has_members? - true - end - - def polyglot_members(internal) - %w[flags] - end - - def polyglot_read_member(name) - case name - when 'flags' - compute_flags - when 'klass' - Primitive.cext_wrap(Primitive.metaclass(@object)) - else - raise Truffle::Interop::UnknownIdentifierException - end - end - - def polyglot_write_member(name, value) - raise Truffle::Interop::UnknownIdentifierException unless name == 'flags' - set_flags value - end - - def polyglot_remove_member(name) - raise Truffle::Interop::UnsupportedMessageException - end - - def polyglot_invoke_member(name, *args) - raise Truffle::Interop::UnsupportedMessageException - end - - def polyglot_member_readable?(name) - name == 'flags' || name == 'klass' - end - - def polyglot_member_modifiable?(name) - name == 'flags' - end - - def polyglot_member_removable?(name) - false - end - - def polyglot_member_insertable?(name) - false - end - - def polyglot_member_invocable?(name) - false - end - - def polyglot_member_internal?(name) - false - end - - def polyglot_has_member_read_side_effects?(name) - false - end - - def polyglot_has_member_write_side_effects?(name) - true - end end # ruby.h: `struct RArray` @@ -351,95 +161,6 @@ def polyglot_array_element_removable?(index) end end -# io.h: `struct rb_io_t` -class Truffle::CExt::RbIO - def initialize(io) - @io = io - Primitive.object_hidden_var_set(io, Truffle::CExt::RB_IO_STRUCT, self) - @tied_io_for_writing = false - end - - private - - def polyglot_has_members? - true - end - - def polyglot_members(internal) - ['self', 'stdio_file', 'fd', 'mode', 'pathv', 'pid', 'lineno', 'tied_io_for_writing'] - end - - def polyglot_read_member(name) - case name - when 'self' - Primitive.cext_wrap(@io) - when 'fd' - Primitive.io_fd(@io) - when 'mode' - @io.instance_variable_get(:@mode) - when 'pathv' - Primitive.cext_wrap(@io.instance_variable_get(:@path)) - when 'tied_io_for_writing' - Primitive.cext_wrap(@tied_io_for_writing) - else - raise Truffle::Interop::UnknownIdentifierException - end - end - - def polyglot_write_member(name, value) - case name - when 'mode' - @io.instance_variable_set(:@mode, value) - when 'pathv' - @io.instance_variable_set(:@path, Primitive.cext_unwrap(value)) - when 'tied_io_for_writing' - @tied_io_for_writing = Primitive.cext_unwrap(value) - else - raise Truffle::Interop::UnknownIdentifierException - end - end - - def polyglot_remove_member(name) - raise Truffle::Interop::UnsupportedMessageException - end - - def polyglot_invoke_member(name, *args) - raise Truffle::Interop::UnsupportedMessageException - end - - def polyglot_member_readable?(name) - name == 'self' || name == 'fd' || name == 'mode' || name == 'pathv' || name == 'tied_io_for_writing' - end - - def polyglot_member_modifiable?(name) - name == 'mode' || name == 'pathv' || name == 'tied_io_for_writing' - end - - def polyglot_member_removable?(name) - false - end - - def polyglot_member_insertable?(name) - false - end - - def polyglot_member_invocable?(name) - false - end - - def polyglot_member_internal?(name) - false - end - - def polyglot_has_member_read_side_effects?(name) - false - end - - def polyglot_has_member_write_side_effects?(name) - false - end -end - # encoding.h: `struct rb_encoding` class Truffle::CExt::RbEncoding ENCODING_CACHE = Array.new(Encoding.list.size, nil) # Encoding index => RbEncoding @@ -476,7 +197,7 @@ def self.get_encoding_from_native(rbencoding_ptr) def initialize(encoding) @encoding = encoding @pointer = nil - @name = Truffle::CExt::LIBTRUFFLERUBY.RSTRING_PTR_IMPL(Primitive.cext_wrap(encoding.name)) + @name = Truffle::CExt::LIBTRUFFLERUBY.rb_tr_rstring_ptr(Primitive.cext_wrap(encoding.name)) end private @@ -559,73 +280,3 @@ def polyglot_as_pointer Truffle::Interop.as_pointer(pointer) end end - -class Truffle::CExt::RFile - def initialize(basic, file) - @basic = basic - @fptr = file - end - - def polyglot_has_members? - true - end - - def polyglot_members(internal) - ['basic', 'fptr'] - end - - def polyglot_read_member(name) - case name - when 'basic' - @basic - when 'fptr' - @fptr - else - raise Truffle::Interop::UnknownIdentifierException - end - end - - def polyglot_write_member(name, value) - raise Truffle::Interop::UnsupportedMessageException - end - - def polyglot_remove_member(name) - raise Truffle::Interop::UnsupportedMessageException - end - - def polyglot_invoke_member(name, *args) - raise Truffle::Interop::UnsupportedMessageException - end - - def polyglot_member_readable?(name) - name == 'basic' || name == 'fptr' - end - - def polyglot_member_modifiable?(name) - false - end - - def polyglot_member_removable?(name) - false - end - - def polyglot_member_insertable?(name) - false - end - - def polyglot_member_invocable?(name) - false - end - - def polyglot_member_internal?(name) - false - end - - def polyglot_has_member_read_side_effects?(name) - false - end - - def polyglot_has_member_write_side_effects?(name) - true - end -end diff --git a/mx.truffleruby/suite.py b/mx.truffleruby/suite.py index 4fbb19519a2a..ebbbd915385a 100644 --- a/mx.truffleruby/suite.py +++ b/mx.truffleruby/suite.py @@ -392,6 +392,7 @@ "src/main/c/spawn-helper/spawn-helper", "src/main/c/truffleposix/", "src/main/c/cext/", + "src/main/c/cext-trampoline/", "src/main/c/bigdecimal/", "src/main/c/date/", "src/main/c/etc/", @@ -653,7 +654,6 @@ ], "lib/cext/": [ "file:lib/cext/*.rb", - "file:lib/cext/ABI_version.txt", ], "lib/cext/include/": [ "file:lib/cext/include/*", @@ -697,6 +697,7 @@ "lib/cext/": [ "dependency:org.truffleruby.cext/src/main/c/truffleposix/", "dependency:org.truffleruby.cext/src/main/c/cext/", + "dependency:org.truffleruby.cext/src/main/c/cext-trampoline/", "dependency:org.truffleruby.rubysignal", ], # The platform-specific files from debug and rbs, see comment above diff --git a/spec/ruby/optional/capi/ext/rbasic_spec.c b/spec/ruby/optional/capi/ext/rbasic_spec.c index 9178e5f63900..26be2fed6d37 100644 --- a/spec/ruby/optional/capi/ext/rbasic_spec.c +++ b/spec/ruby/optional/capi/ext/rbasic_spec.c @@ -5,6 +5,14 @@ extern "C" { #endif +#ifndef RBASIC_FLAGS +#define RBASIC_FLAGS(obj) (RBASIC(obj)->flags) +#endif + +#ifndef RBASIC_SET_FLAGS +#define RBASIC_SET_FLAGS(obj, flags_to_set) (RBASIC(obj)->flags = flags_to_set) +#endif + #ifndef FL_SHAREABLE static const VALUE VISIBLE_BITS = FL_TAINT | FL_FREEZE; static const VALUE DATA_VISIBLE_BITS = FL_TAINT | FL_FREEZE | ~(FL_USER0 - 1); @@ -34,47 +42,53 @@ VALUE rbasic_spec_freeze_flag(VALUE self) { return VALUE2NUM(RUBY_FL_FREEZE); } - static VALUE spec_get_flags(const struct RBasic *b, VALUE visible_bits) { - VALUE flags = b->flags & visible_bits; +static VALUE spec_get_flags(VALUE obj, VALUE visible_bits) { + VALUE flags = RB_FL_TEST(obj, visible_bits); return VALUE2NUM(flags); } -static VALUE spec_set_flags(struct RBasic *b, VALUE flags, VALUE visible_bits) { +static VALUE spec_set_flags(VALUE obj, VALUE flags, VALUE visible_bits) { flags &= visible_bits; - b->flags = (b->flags & ~visible_bits) | flags; + + // Could also be done like: + // RB_FL_UNSET(obj, visible_bits); + // RB_FL_SET(obj, flags); + // But that seems rather indirect + RBASIC_SET_FLAGS(obj, (RBASIC_FLAGS(obj) & ~visible_bits) | flags); + return VALUE2NUM(flags); } -VALUE rbasic_spec_get_flags(VALUE self, VALUE val) { - return spec_get_flags(RBASIC(val), VISIBLE_BITS); +static VALUE rbasic_spec_get_flags(VALUE self, VALUE obj) { + return spec_get_flags(obj, VISIBLE_BITS); } -VALUE rbasic_spec_set_flags(VALUE self, VALUE val, VALUE flags) { - return spec_set_flags(RBASIC(val), NUM2VALUE(flags), VISIBLE_BITS); +static VALUE rbasic_spec_set_flags(VALUE self, VALUE obj, VALUE flags) { + return spec_set_flags(obj, NUM2VALUE(flags), VISIBLE_BITS); } -VALUE rbasic_spec_copy_flags(VALUE self, VALUE to, VALUE from) { - return spec_set_flags(RBASIC(to), RBASIC(from)->flags, VISIBLE_BITS); +static VALUE rbasic_spec_copy_flags(VALUE self, VALUE to, VALUE from) { + return spec_set_flags(to, RBASIC_FLAGS(from), VISIBLE_BITS); } -VALUE rbasic_spec_get_klass(VALUE self, VALUE val) { - return RBASIC(val)->klass; +static VALUE rbasic_spec_get_klass(VALUE self, VALUE obj) { + return RBASIC_CLASS(obj); } -VALUE rbasic_rdata_spec_get_flags(VALUE self, VALUE structure) { - return spec_get_flags(&RDATA(structure)->basic, DATA_VISIBLE_BITS); +static VALUE rbasic_rdata_spec_get_flags(VALUE self, VALUE structure) { + return spec_get_flags(structure, DATA_VISIBLE_BITS); } -VALUE rbasic_rdata_spec_set_flags(VALUE self, VALUE structure, VALUE flags) { - return spec_set_flags(&RDATA(structure)->basic, NUM2VALUE(flags), DATA_VISIBLE_BITS); +static VALUE rbasic_rdata_spec_set_flags(VALUE self, VALUE structure, VALUE flags) { + return spec_set_flags(structure, NUM2VALUE(flags), DATA_VISIBLE_BITS); } -VALUE rbasic_rdata_spec_copy_flags(VALUE self, VALUE to, VALUE from) { - return spec_set_flags(&RDATA(to)->basic, RDATA(from)->basic.flags, DATA_VISIBLE_BITS); +static VALUE rbasic_rdata_spec_copy_flags(VALUE self, VALUE to, VALUE from) { + return spec_set_flags(to, RBASIC_FLAGS(from), DATA_VISIBLE_BITS); } -VALUE rbasic_rdata_spec_get_klass(VALUE self, VALUE structure) { - return RDATA(structure)->basic.klass; +static VALUE rbasic_rdata_spec_get_klass(VALUE self, VALUE structure) { + return RBASIC_CLASS(structure); } void Init_rbasic_spec(void) { diff --git a/spec/ruby/optional/capi/ext/typed_data_spec.c b/spec/ruby/optional/capi/ext/typed_data_spec.c index eca2b667cc22..38889ecf5c8e 100644 --- a/spec/ruby/optional/capi/ext/typed_data_spec.c +++ b/spec/ruby/optional/capi/ext/typed_data_spec.c @@ -106,6 +106,12 @@ VALUE sws_typed_wrap_struct(VALUE self, VALUE val) { return TypedData_Wrap_Struct(rb_cObject, &sample_typed_wrapped_struct_data_type, bar); } +VALUE sws_untyped_wrap_struct(VALUE self, VALUE val) { + int* data = (int*) malloc(sizeof(int)); + *data = FIX2INT(val); + return Data_Wrap_Struct(rb_cObject, NULL, free, data); +} + VALUE sws_typed_get_struct(VALUE self, VALUE obj) { struct sample_typed_wrapped_struct* bar; TypedData_Get_Struct(obj, struct sample_typed_wrapped_struct, &sample_typed_wrapped_struct_data_type, bar); @@ -165,12 +171,17 @@ VALUE sws_typed_rb_check_typeddata_different_type(VALUE self, VALUE obj) { return rb_check_typeddata(obj, &sample_typed_wrapped_struct_other_data_type) == DATA_PTR(obj) ? Qtrue : Qfalse; } +VALUE sws_typed_RTYPEDDATA_P(VALUE self, VALUE obj) { + return RTYPEDDATA_P(obj) ? Qtrue : Qfalse; +} + void Init_typed_data_spec(void) { VALUE cls = rb_define_class("CApiAllocTypedSpecs", rb_cObject); rb_define_alloc_func(cls, sdaf_alloc_typed_func); rb_define_method(cls, "typed_wrapped_data", sdaf_typed_get_struct, 0); cls = rb_define_class("CApiWrappedTypedStructSpecs", rb_cObject); rb_define_method(cls, "typed_wrap_struct", sws_typed_wrap_struct, 1); + rb_define_method(cls, "untyped_wrap_struct", sws_untyped_wrap_struct, 1); rb_define_method(cls, "typed_get_struct", sws_typed_get_struct, 1); rb_define_method(cls, "typed_get_struct_other", sws_typed_get_struct_different_type, 1); rb_define_method(cls, "typed_get_struct_parent", sws_typed_get_struct_parent_type, 1); @@ -181,6 +192,7 @@ void Init_typed_data_spec(void) { rb_define_method(cls, "rb_check_typeddata_same_type", sws_typed_rb_check_typeddata_same_type, 1); rb_define_method(cls, "rb_check_typeddata_same_type_parent", sws_typed_rb_check_typeddata_same_type_parent, 1); rb_define_method(cls, "rb_check_typeddata_different_type", sws_typed_rb_check_typeddata_different_type, 1); + rb_define_method(cls, "RTYPEDDATA_P", sws_typed_RTYPEDDATA_P, 1); } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/rbasic_spec.rb b/spec/ruby/optional/capi/rbasic_spec.rb index 577f2060dade..f3367e05ffcf 100644 --- a/spec/ruby/optional/capi/rbasic_spec.rb +++ b/spec/ruby/optional/capi/rbasic_spec.rb @@ -33,6 +33,8 @@ initial = @specs.get_flags(obj1) @specs.get_flags(obj2).should == initial @specs.set_flags(obj1, 1 << 14 | 1 << 16 | initial) + @specs.get_flags(obj1).should == 1 << 14 | 1 << 16 | initial + @specs.copy_flags(obj2, obj1) @specs.get_flags(obj2).should == 1 << 14 | 1 << 16 | initial @specs.set_flags(obj1, initial) diff --git a/spec/ruby/optional/capi/shared/rbasic.rb b/spec/ruby/optional/capi/shared/rbasic.rb index 95c313714364..9d80a93e1d6d 100644 --- a/spec/ruby/optional/capi/shared/rbasic.rb +++ b/spec/ruby/optional/capi/shared/rbasic.rb @@ -1,5 +1,4 @@ describe :rbasic, shared: true do - before :all do specs = CApiRBasicSpecs.new @taint = ruby_version_is(''...'3.1') ? specs.taint_flag : 0 diff --git a/spec/ruby/optional/capi/typed_data_spec.rb b/spec/ruby/optional/capi/typed_data_spec.rb index 23b7c157efb3..6d1398a1a0b9 100644 --- a/spec/ruby/optional/capi/typed_data_spec.rb +++ b/spec/ruby/optional/capi/typed_data_spec.rb @@ -85,4 +85,16 @@ -> { @s.rb_check_typeddata_different_type(a) }.should raise_error(TypeError) end end + + describe "RTYPEDDATA_P" do + it "returns true for a typed data" do + a = @s.typed_wrap_struct(1024) + @s.RTYPEDDATA_P(a).should == true + end + + it "returns false for an untyped data object" do + a = @s.untyped_wrap_struct(1024) + @s.RTYPEDDATA_P(a).should == false + end + end end diff --git a/spec/tags/optional/capi/encoding_tags.txt b/spec/tags/optional/capi/encoding_tags.txt index 7046a1d5388b..f476def1e723 100644 --- a/spec/tags/optional/capi/encoding_tags.txt +++ b/spec/tags/optional/capi/encoding_tags.txt @@ -2,8 +2,6 @@ fails:C-API Encoding function rb_enc_associate sets the encoding of a Regexp to fails:C-API Encoding function rb_enc_associate sets the encoding of a String to a default when the encoding is NULL fails:C-API Encoding function rb_enc_associate_index sets the encoding of a Regexp to the encoding fails:C-API Encoding function rb_enc_copy sets the encoding of a Regexp to that of the second argument -fails:C-API Encoding function rb_enc_nth returns the byte index of the given character index -fails:C-API Encoding function rb_enc_alias creates an alias for an existing Encoding fails:C-API Encoding function rb_enc_get_index returns -1 for an object without an encoding fails:C-API Encoding function rb_enc_set_index raises an ArgumentError for a non-encoding capable object fails:C-API Encoding function ENCODING_SET raises an ArgumentError for a non-encoding capable object diff --git a/spec/tags/optional/capi/fiber_tags.txt b/spec/tags/optional/capi/fiber_tags.txt deleted file mode 100644 index f51ade08a06b..000000000000 --- a/spec/tags/optional/capi/fiber_tags.txt +++ /dev/null @@ -1,2 +0,0 @@ -fails:C-API Fiber function rb_fiber_raise raises an exception on the resumed fiber -fails:C-API Fiber function rb_fiber_raise raises an exception on the transferred fiber diff --git a/spec/tags/optional/capi/gc_tags.txt b/spec/tags/optional/capi/gc_tags.txt index 4b6752938e88..10f95e367070 100644 --- a/spec/tags/optional/capi/gc_tags.txt +++ b/spec/tags/optional/capi/gc_tags.txt @@ -1,5 +1,4 @@ slow:CApiGCSpecs rb_gc increases gc count -fails:CApiGCSpecs rb_gc_adjust_memory_usage adjusts the amount of registered external memory slow:CApiGCSpecs rb_gc_register_address keeps the value alive even if the value is assigned after rb_gc_register_address() is called slow:CApiGCSpecs rb_global_variable keeps the value alive even if the value is assigned after rb_global_variable() is called native-g1:CApiGCSpecs rb_gc increases gc count diff --git a/spec/tags/optional/capi/thread_tags.txt b/spec/tags/optional/capi/thread_tags.txt new file mode 100644 index 000000000000..f4fce423ec49 --- /dev/null +++ b/spec/tags/optional/capi/thread_tags.txt @@ -0,0 +1 @@ +fails(TODO, does not work yet when run natively):C-API Thread function ruby_native_thread_p returns zero for a non ruby thread diff --git a/spec/tags/truffle/capi/unimplemented_tags.txt b/spec/tags/truffle/capi/unimplemented_tags.txt new file mode 100644 index 000000000000..88e88ac1bd58 --- /dev/null +++ b/spec/tags/truffle/capi/unimplemented_tags.txt @@ -0,0 +1 @@ +slow:Unimplemented functions in the C-API abort the process and show an error including the function name diff --git a/spec/truffle/capi/cext_lock_spec.rb b/spec/truffle/capi/cext_lock_spec.rb index acd519e3625f..d045a00031af 100644 --- a/spec/truffle/capi/cext_lock_spec.rb +++ b/spec/truffle/capi/cext_lock_spec.rb @@ -30,6 +30,6 @@ end it "is released inside rb_funcall" do - @t.has_lock_in_rb_funcall?(Truffle::CExt).should == false + @t.has_lock_in_rb_funcall?.should == false end end diff --git a/spec/truffle/capi/ext/truffleruby_foreign_caller_spec.c b/spec/truffle/capi/ext/truffleruby_foreign_caller_spec.c index 892a1f31b911..e7da43ef565f 100644 --- a/spec/truffle/capi/ext/truffleruby_foreign_caller_spec.c +++ b/spec/truffle/capi/ext/truffleruby_foreign_caller_spec.c @@ -16,7 +16,7 @@ extern "C" { #endif static VALUE call_binding(VALUE self) { - return rb_tr_wrap(polyglot_invoke(rb_tr_unwrap(self), "binding")); + return rb_tr_invoke(self, "binding"); } static VALUE call_binding_rb_funcall(VALUE self) { diff --git a/spec/truffle/capi/ext/truffleruby_lock_spec.c b/spec/truffle/capi/ext/truffleruby_lock_spec.c index 6f4f5d6874d7..b3bc1215e9fa 100644 --- a/spec/truffle/capi/ext/truffleruby_lock_spec.c +++ b/spec/truffle/capi/ext/truffleruby_lock_spec.c @@ -15,27 +15,30 @@ extern "C" { #endif +static VALUE truffleCExt; + static VALUE has_lock(VALUE self) { - return RUBY_CEXT_FUNCALL("cext_lock_owned?"); + return rb_tr_cext_lock_owned_p(); } static void* called_without_gvl(void* data) { - return RUBY_CEXT_FUNCALL("cext_lock_owned?"); + return rb_tr_cext_lock_owned_p(); } static VALUE has_lock_in_call_without_gvl(VALUE self) { return rb_thread_call_without_gvl(called_without_gvl, 0, RUBY_UBF_IO, 0); } -static VALUE has_lock_in_rb_funcall(VALUE self, VALUE truffleCExt) { +static VALUE has_lock_in_rb_funcall(VALUE self) { return rb_funcall(truffleCExt, rb_intern("cext_lock_owned?"), 0); } void Init_truffleruby_lock_spec(void) { + truffleCExt = rb_const_get(rb_const_get(rb_cObject, rb_intern("Truffle")), rb_intern("CExt")); VALUE cls = rb_define_class("CApiTruffleRubyLockSpecs", rb_cObject); rb_define_method(cls, "has_lock?", has_lock, 0); rb_define_method(cls, "has_lock_in_call_without_gvl?", has_lock_in_call_without_gvl, 0); - rb_define_method(cls, "has_lock_in_rb_funcall?", has_lock_in_rb_funcall, 1); + rb_define_method(cls, "has_lock_in_rb_funcall?", has_lock_in_rb_funcall, 0); } #ifdef __cplusplus diff --git a/spec/truffle/capi/ext/truffleruby_rbasic_spec.c b/spec/truffle/capi/ext/truffleruby_rbasic_spec.c index 4cb0c8a60070..18e779d96b62 100644 --- a/spec/truffle/capi/ext/truffleruby_rbasic_spec.c +++ b/spec/truffle/capi/ext/truffleruby_rbasic_spec.c @@ -15,21 +15,21 @@ extern "C" { #endif -VALUE rbasic_spec_finalize_flag(VALUE self) { +static VALUE rbasic_spec_finalize_flag(VALUE self) { return INT2FIX(RUBY_FL_FINALIZE); } -VALUE rbasic_spec_promoted_flag(VALUE self) { +static VALUE rbasic_spec_promoted_flag(VALUE self) { return INT2FIX(RUBY_FL_PROMOTED); } -VALUE rbasic_spec_get_flags(VALUE self, VALUE val) { - return INT2FIX(RBASIC(val)->flags); +static VALUE rbasic_spec_get_flags(VALUE self, VALUE obj) { + return INT2FIX(RBASIC_FLAGS(obj)); } -VALUE rbasic_spec_set_flags(VALUE self, VALUE val, VALUE flags) { - RBASIC(val)->flags = FIX2INT(flags); - return INT2FIX(RBASIC(val)->flags); +static VALUE rbasic_spec_set_flags(VALUE self, VALUE obj, VALUE flags) { + RBASIC_SET_FLAGS(obj, FIX2INT(flags)); + return INT2FIX(RBASIC_FLAGS(obj)); } void Init_truffleruby_rbasic_spec(void) { diff --git a/spec/truffle/capi/ext/truffleruby_string_spec.c b/spec/truffle/capi/ext/truffleruby_string_spec.c index 32532e259689..c9699d4094df 100644 --- a/spec/truffle/capi/ext/truffleruby_string_spec.c +++ b/spec/truffle/capi/ext/truffleruby_string_spec.c @@ -15,7 +15,7 @@ extern "C" { #endif // Tests that Sulong does not force ptr to native -char* function_returning_char_ptr(char* ptr) { +static char* function_returning_char_ptr(char* ptr) { return ptr; } @@ -25,11 +25,6 @@ static VALUE string_ptr(VALUE self, VALUE str) { return rb_str_new_cstr(cstring); } -static VALUE string_NATIVE_RSTRING_PTR(VALUE self, VALUE str) { - NATIVE_RSTRING_PTR(str); - return str; -} - static VALUE string_ptr_return_address(VALUE self, VALUE str) { char* ptr = RSTRING_PTR(str); return LONG2NUM((long) ptr); @@ -39,7 +34,6 @@ void Init_truffleruby_string_spec(void) { VALUE cls; cls = rb_define_class("CApiTruffleStringSpecs", rb_cObject); rb_define_method(cls, "string_ptr", string_ptr, 1); - rb_define_method(cls, "NATIVE_RSTRING_PTR", string_NATIVE_RSTRING_PTR, 1); rb_define_method(cls, "string_ptr_return_address", string_ptr_return_address, 1); } diff --git a/spec/truffle/capi/ext/truffleruby_symbol_id_spec.c b/spec/truffle/capi/ext/truffleruby_symbol_id_spec.c index c320d03e9b1e..3754717e0261 100644 --- a/spec/truffle/capi/ext/truffleruby_symbol_id_spec.c +++ b/spec/truffle/capi/ext/truffleruby_symbol_id_spec.c @@ -16,7 +16,7 @@ extern "C" { #endif -VALUE symbol_id_spec_ID2SYM(VALUE self, VALUE str) { +static VALUE symbol_id_spec_ID2SYM(VALUE self, VALUE str) { char *name = StringValueCStr(str); if (strcmp(name, "idPLUS") == 0){ return ID2SYM(idPLUS); @@ -35,7 +35,7 @@ VALUE symbol_id_spec_ID2SYM(VALUE self, VALUE str) { } } -VALUE symbol_id_spec_SYM2ID(VALUE self, VALUE sym, VALUE str) { +static VALUE symbol_id_spec_SYM2ID(VALUE self, VALUE sym, VALUE str) { ID id = SYM2ID(sym); char *name = StringValueCStr(str); if (strcmp(name, "idPLUS") == 0){ @@ -55,7 +55,7 @@ VALUE symbol_id_spec_SYM2ID(VALUE self, VALUE sym, VALUE str) { } } -VALUE symbol_id_spec_ID2SYM_SYM2ID(VALUE self, VALUE str) { +static VALUE symbol_id_spec_ID2SYM_SYM2ID(VALUE self, VALUE str) { char *name = StringValueCStr(str); if (strcmp(name, "idPLUS") == 0){ return SYM2ID(ID2SYM(idPLUS)) == idPLUS ? Qtrue : Qfalse; @@ -74,7 +74,7 @@ VALUE symbol_id_spec_ID2SYM_SYM2ID(VALUE self, VALUE str) { } } -VALUE symbol_id_spec_SYM2ID_ID2SYM(VALUE self, VALUE sym) { +static VALUE symbol_id_spec_SYM2ID_ID2SYM(VALUE self, VALUE sym) { return ID2SYM(SYM2ID(sym)); } diff --git a/spec/truffle/capi/ext/unimplemented_spec.c b/spec/truffle/capi/ext/unimplemented_spec.c index 8677864eaeec..19e5874d36b9 100644 --- a/spec/truffle/capi/ext/unimplemented_spec.c +++ b/spec/truffle/capi/ext/unimplemented_spec.c @@ -15,7 +15,7 @@ extern "C" { #endif static VALUE unimplemented_spec_not_implemented(VALUE self, VALUE str) { - // One of the functions not implemented in ruby.c + // One of the functions not implemented rb_str_shared_replace(str, str); return Qnil; } diff --git a/spec/truffle/capi/foreign_caller_spec.rb b/spec/truffle/capi/foreign_caller_spec.rb index 2abaed29ce58..97ad8d6e487f 100644 --- a/spec/truffle/capi/foreign_caller_spec.rb +++ b/spec/truffle/capi/foreign_caller_spec.rb @@ -21,13 +21,13 @@ }.should raise_error(RuntimeError, 'Kernel#binding needs the caller frame but it was not passed (cannot be called directly from a foreign language)') end - it "using rb_funcall() yields the Binding of rb_funcall()" do + it "using rb_funcall() yields the Binding of rb_funcallv()" do caller_variable = nil binding = @s.call_binding_rb_funcall binding.should be_kind_of(Binding) # On CRuby it would instead return the Binding of the caller Ruby frame - binding.local_variables.should.include?(:args) + binding.local_variables.should.include?(:argv) binding.local_variables.should_not.include?(:caller_variable) caller_variable.should == nil diff --git a/spec/truffle/capi/rbasic_spec.rb b/spec/truffle/capi/rbasic_spec.rb index 7817c9ba653d..2c7697e52046 100644 --- a/spec/truffle/capi/rbasic_spec.rb +++ b/spec/truffle/capi/rbasic_spec.rb @@ -11,7 +11,7 @@ load_extension("truffleruby_rbasic") describe "RBasic support" do - it "should raise an ArugmentError for unsupported flags" do + it "should raise an ArgumentError for unsupported flags" do specs = CApiTruffleRubyRBasicSpecs.new obj = Object.new diff --git a/spec/truffle/capi/string_spec.rb b/spec/truffle/capi/string_spec.rb index c41ce5ef4f98..d3bc202c75f5 100644 --- a/spec/truffle/capi/string_spec.rb +++ b/spec/truffle/capi/string_spec.rb @@ -27,16 +27,3 @@ Truffle::CExt.native_string?(str).should == true end end - -describe "TruffleRuby NATIVE_RSTRING_PTR" do - before :each do - @s = CApiTruffleStringSpecs.new - end - - it "ensures the String is stored in native memory" do - str = "foobar" - Truffle::CExt.native_string?(str).should == false - @s.NATIVE_RSTRING_PTR(str) - Truffle::CExt.native_string?(str).should == true - end -end diff --git a/spec/truffle/capi/unimplemented_spec.rb b/spec/truffle/capi/unimplemented_spec.rb index 764af8066586..84aa96927dca 100644 --- a/spec/truffle/capi/unimplemented_spec.rb +++ b/spec/truffle/capi/unimplemented_spec.rb @@ -8,16 +8,11 @@ require_relative '../../ruby/optional/capi/spec_helper' -load_extension("unimplemented") +extension_path = load_extension("unimplemented") describe "Unimplemented functions in the C-API" do - before :each do - @s = CApiRbTrErrorSpecs.new - end - - it "raise a useful RuntimeError including the function name" do - -> { - @s.not_implemented_function("foo") - }.should raise_error(Polyglot::ForeignException, /rb_str_shared_replace cannot be found/) + it "abort the process and show an error including the function name" do + out = ruby_exe('require ARGV[0]; CApiRbTrErrorSpecs.new.not_implemented_function("foo")', args: "#{extension_path} 2>&1", exit_status: 127) + out.should.include?('undefined symbol: rb_str_shared_replace') end end diff --git a/src/main/c/Makefile b/src/main/c/Makefile index b555e40849c7..6d4291d65774 100644 --- a/src/main/c/Makefile +++ b/src/main/c/Makefile @@ -19,13 +19,15 @@ RUBY := $(TRUFFLERUBY_BOOTSTRAP_LAUNCHER) TRUFFLE_POSIX := truffleposix/libtruffleposix.$(SOEXT) SPAWN_HELPER := spawn-helper/spawn-helper +TRAMPOLINE := cext-trampoline/libtrufflerubytrampoline.$(SOEXT) RUBY_HEADERS := $(shell find $(ROOT)/lib/cext/include -name '*.h') -ABI_VERSION := $(ROOT)/lib/cext/ABI_version.txt +CEXT_C_FILES := $(shell find cext -name '*.c') RBCONFIG := $(ROOT)/lib/truffle/rbconfig.rb MKMF := $(ROOT)/lib/mri/mkmf.rb LIBTRUFFLERUBY = cext/libtruffleruby.$(SOEXT) -BASIC_EXTCONF_DEPS := Makefile $(SPAWN_HELPER) $(TRUFFLE_POSIX) $(RUBY_HEADERS) $(ABI_VERSION) $(RBCONFIG) $(MKMF) +BASIC_DEPS := Makefile $(SPAWN_HELPER) $(TRUFFLE_POSIX) $(RUBY_HEADERS) +BASIC_EXTCONF_DEPS := $(BASIC_DEPS) $(RBCONFIG) $(MKMF) # C extensions link against libtruffleruby (and might do have_func() checks against it), so it needs to be there before. # However, if libtruffleruby is recompiled, there is no need to rebuild C extensions, so it's a order-only-prerequisite. EXTCONF_DEPS := $(BASIC_EXTCONF_DEPS) | $(LIBTRUFFLERUBY) @@ -42,16 +44,19 @@ RBS_GEM_LIB_SO := $(RBS_GEM)/lib/rbs_extension.$(DLEXT) IF_EXTCONF_FAIL := ( echo "`pwd`/extconf.rb failed:" 1>&2 && cat mkmf.log && false ) -all: $(LIBTRUFFLERUBY) openssl/openssl.$(DLEXT) zlib/zlib.$(DLEXT) \ +all: $(TRAMPOLINE) $(LIBTRUFFLERUBY) openssl/openssl.$(DLEXT) zlib/zlib.$(DLEXT) \ psych/psych.$(DLEXT) ripper/ripper.$(DLEXT) syslog/syslog.$(DLEXT) nkf/nkf.$(DLEXT) \ bigdecimal/bigdecimal.$(DLEXT) date/date_core.$(DLEXT) io-console/console.$(DLEXT) etc/etc.$(DLEXT) \ rbconfig-sizeof/sizeof.$(DLEXT) $(DEBUG_GEM_LIB_SO) $(RBS_GEM_LIB_SO) -clean: clean_cexts clean_truffleposix +clean: clean_cexts clean_truffleposix clean_trampoline clean_truffleposix: $(Q) rm -f $(TRUFFLE_POSIX) truffleposix/*.o +clean_trampoline: + $(Q) rm -f $(TRAMPOLINE) cext-trampoline/trampoline.c cext-trampoline/*.o + clean_cexts: $(Q) rm -f cext/Makefile cext/*.o $(LIBTRUFFLERUBY) $(Q) rm -f openssl/Makefile openssl/*.o openssl/openssl.$(DLEXT) openssl/extconf.h @@ -76,8 +81,15 @@ $(SPAWN_HELPER): spawn-helper/Makefile spawn-helper/spawn-helper.c $(TRUFFLE_POSIX): truffleposix/Makefile truffleposix/truffleposix.c $(Q) cd truffleposix && $(MAKE) +# cext-trampoline +cext-trampoline/trampoline.c: $(CEXT_C_FILES) $(BASIC_DEPS) $(ROOT)/tool/generate-cext-trampoline.rb + $(Q) cd $(ROOT) && $(RUBY) tool/generate-cext-trampoline.rb + +$(TRAMPOLINE): cext-trampoline/Makefile cext-trampoline/trampoline.c cext-trampoline/*.c Makefile + $(Q) cd cext-trampoline && $(MAKE) + # libtruffleruby -cext/Makefile: cext/extconf.rb $(BASIC_EXTCONF_DEPS) +cext/Makefile: cext/extconf.rb $(BASIC_EXTCONF_DEPS) $(TRAMPOLINE) $(Q) cd cext && $(RUBY) extconf.rb || $(IF_EXTCONF_FAIL) $(LIBTRUFFLERUBY): cext/Makefile cext/*.c cext/*.h diff --git a/src/main/c/cext-trampoline/Makefile b/src/main/c/cext-trampoline/Makefile new file mode 100644 index 000000000000..24dbd25f5527 --- /dev/null +++ b/src/main/c/cext-trampoline/Makefile @@ -0,0 +1,26 @@ +Q$(MX_VERBOSE) = @ + +OS := $(shell uname) +ifeq ($(OS),Darwin) +SOEXT := dylib +else +SOEXT := so +endif + +# cc should be enough to compile this library but might produce different warnings based on the system compiler. +# If that is the case and fails the build, it might be better to use instead: +# CC := $(GRAALVM_TOOLCHAIN_CC) +CC := cc +CFLAGS := -Wall -Werror -fPIC -std=c99 -g +LDFLAGS := -m64 + +ROOT := $(realpath ../../../..) +RUBY_HDR_DIR := $(ROOT)/lib/cext/include + +OBJECT_FILES := trampoline.o st.o cext_constants.o + +libtrufflerubytrampoline.$(SOEXT): $(OBJECT_FILES) Makefile + $(Q) $(CC) -shared $(LDFLAGS) -o $@ $(OBJECT_FILES) + +%.o: %.c Makefile + $(Q) $(CC) -o $@ -c $(CFLAGS) $(LDFLAGS) -I$(RUBY_HDR_DIR) -I$(ROOT)/lib/cext/include/stubs $< diff --git a/src/main/c/cext-trampoline/cext_constants.c b/src/main/c/cext-trampoline/cext_constants.c new file mode 100644 index 000000000000..8f3003546e1c --- /dev/null +++ b/src/main/c/cext-trampoline/cext_constants.c @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2017, 2023 Oracle and/or its affiliates. All rights reserved. This + * code is released under a tri EPL/GPL/LGPL license. You can use it, + * redistribute it and/or modify it under the terms of the: + * + * Eclipse Public License version 2.0, or + * GNU General Public License version 2, or + * GNU Lesser General Public License version 2.1. + */ + +// From tool/generate-cext-constants.rb + +#include + +VALUE rb_cArray; +VALUE rb_cBasicObject; +VALUE rb_cBinding; +VALUE rb_cClass; +VALUE rb_mComparable; +VALUE rb_cComplex; +VALUE rb_cDir; +VALUE rb_cEncoding; +VALUE rb_mEnumerable; +VALUE rb_cEnumerator; +VALUE rb_cFalseClass; +VALUE rb_cFile; +VALUE rb_mFileTest; +VALUE rb_cStat; +VALUE rb_cFloat; +VALUE rb_mGC; +VALUE rb_cHash; +VALUE rb_cInteger; +VALUE rb_cIO; +VALUE rb_mKernel; +VALUE rb_cMatch; +VALUE rb_mMath; +VALUE rb_cMethod; +VALUE rb_cModule; +VALUE rb_cNilClass; +VALUE rb_cNumeric; +VALUE rb_cObject; +VALUE rb_cProc; +VALUE rb_mProcess; +VALUE rb_cRandom; +VALUE rb_cRange; +VALUE rb_cRational; +VALUE rb_cRegexp; +VALUE rb_cString; +VALUE rb_cStruct; +VALUE rb_cSymbol; +VALUE rb_cTime; +VALUE rb_cThread; +VALUE rb_cTrueClass; +VALUE rb_cUnboundMethod; +VALUE rb_eArgError; +VALUE rb_eEncodingError; +VALUE rb_eEOFError; +VALUE rb_mErrno; +VALUE rb_eException; +VALUE rb_eFloatDomainError; +VALUE rb_eFrozenError; +VALUE rb_eIndexError; +VALUE rb_eInterrupt; +VALUE rb_eIOError; +VALUE rb_eKeyError; +VALUE rb_eLoadError; +VALUE rb_eLocalJumpError; +VALUE rb_eMathDomainError; +VALUE rb_eEncCompatError; +VALUE rb_eNameError; +VALUE rb_eNoMemError; +VALUE rb_eNoMethodError; +VALUE rb_eNotImpError; +VALUE rb_eRangeError; +VALUE rb_eRegexpError; +VALUE rb_eRuntimeError; +VALUE rb_eScriptError; +VALUE rb_eSecurityError; +VALUE rb_eSignal; +VALUE rb_eStandardError; +VALUE rb_eStopIteration; +VALUE rb_eSyntaxError; +VALUE rb_eSystemCallError; +VALUE rb_eSystemExit; +VALUE rb_eSysStackError; +VALUE rb_eTypeError; +VALUE rb_eThreadError; +VALUE rb_mWaitReadable; +VALUE rb_mWaitWritable; +VALUE rb_eZeroDivError; +VALUE rb_eFatal; +VALUE rb_argv0; + +void rb_tr_trampoline_init_global_constants(VALUE (*get_constant)(const char*)) { + rb_cArray = get_constant("rb_cArray"); + rb_cBasicObject = get_constant("rb_cBasicObject"); + rb_cBinding = get_constant("rb_cBinding"); + rb_cClass = get_constant("rb_cClass"); + rb_mComparable = get_constant("rb_mComparable"); + rb_cComplex = get_constant("rb_cComplex"); + rb_cDir = get_constant("rb_cDir"); + rb_cEncoding = get_constant("rb_cEncoding"); + rb_mEnumerable = get_constant("rb_mEnumerable"); + rb_cEnumerator = get_constant("rb_cEnumerator"); + rb_cFalseClass = get_constant("rb_cFalseClass"); + rb_cFile = get_constant("rb_cFile"); + rb_mFileTest = get_constant("rb_mFileTest"); + rb_cStat = get_constant("rb_cStat"); + rb_cFloat = get_constant("rb_cFloat"); + rb_mGC = get_constant("rb_mGC"); + rb_cHash = get_constant("rb_cHash"); + rb_cInteger = get_constant("rb_cInteger"); + rb_cIO = get_constant("rb_cIO"); + rb_mKernel = get_constant("rb_mKernel"); + rb_cMatch = get_constant("rb_cMatch"); + rb_mMath = get_constant("rb_mMath"); + rb_cMethod = get_constant("rb_cMethod"); + rb_cModule = get_constant("rb_cModule"); + rb_cNilClass = get_constant("rb_cNilClass"); + rb_cNumeric = get_constant("rb_cNumeric"); + rb_cObject = get_constant("rb_cObject"); + rb_cProc = get_constant("rb_cProc"); + rb_mProcess = get_constant("rb_mProcess"); + rb_cRandom = get_constant("rb_cRandom"); + rb_cRange = get_constant("rb_cRange"); + rb_cRational = get_constant("rb_cRational"); + rb_cRegexp = get_constant("rb_cRegexp"); + rb_cString = get_constant("rb_cString"); + rb_cStruct = get_constant("rb_cStruct"); + rb_cSymbol = get_constant("rb_cSymbol"); + rb_cTime = get_constant("rb_cTime"); + rb_cThread = get_constant("rb_cThread"); + rb_cTrueClass = get_constant("rb_cTrueClass"); + rb_cUnboundMethod = get_constant("rb_cUnboundMethod"); + rb_eArgError = get_constant("rb_eArgError"); + rb_eEncodingError = get_constant("rb_eEncodingError"); + rb_eEOFError = get_constant("rb_eEOFError"); + rb_mErrno = get_constant("rb_mErrno"); + rb_eException = get_constant("rb_eException"); + rb_eFloatDomainError = get_constant("rb_eFloatDomainError"); + rb_eFrozenError = get_constant("rb_eFrozenError"); + rb_eIndexError = get_constant("rb_eIndexError"); + rb_eInterrupt = get_constant("rb_eInterrupt"); + rb_eIOError = get_constant("rb_eIOError"); + rb_eKeyError = get_constant("rb_eKeyError"); + rb_eLoadError = get_constant("rb_eLoadError"); + rb_eLocalJumpError = get_constant("rb_eLocalJumpError"); + rb_eMathDomainError = get_constant("rb_eMathDomainError"); + rb_eEncCompatError = get_constant("rb_eEncCompatError"); + rb_eNameError = get_constant("rb_eNameError"); + rb_eNoMemError = get_constant("rb_eNoMemError"); + rb_eNoMethodError = get_constant("rb_eNoMethodError"); + rb_eNotImpError = get_constant("rb_eNotImpError"); + rb_eRangeError = get_constant("rb_eRangeError"); + rb_eRegexpError = get_constant("rb_eRegexpError"); + rb_eRuntimeError = get_constant("rb_eRuntimeError"); + rb_eScriptError = get_constant("rb_eScriptError"); + rb_eSecurityError = get_constant("rb_eSecurityError"); + rb_eSignal = get_constant("rb_eSignal"); + rb_eStandardError = get_constant("rb_eStandardError"); + rb_eStopIteration = get_constant("rb_eStopIteration"); + rb_eSyntaxError = get_constant("rb_eSyntaxError"); + rb_eSystemCallError = get_constant("rb_eSystemCallError"); + rb_eSystemExit = get_constant("rb_eSystemExit"); + rb_eSysStackError = get_constant("rb_eSysStackError"); + rb_eTypeError = get_constant("rb_eTypeError"); + rb_eThreadError = get_constant("rb_eThreadError"); + rb_mWaitReadable = get_constant("rb_mWaitReadable"); + rb_mWaitWritable = get_constant("rb_mWaitWritable"); + rb_eZeroDivError = get_constant("rb_eZeroDivError"); + rb_eFatal = get_constant("rb_eFatal"); + rb_argv0 = get_constant("rb_argv0"); +} diff --git a/src/main/c/cext-trampoline/st.c b/src/main/c/cext-trampoline/st.c new file mode 100644 index 000000000000..3d71c7b946c0 --- /dev/null +++ b/src/main/c/cext-trampoline/st.c @@ -0,0 +1,2253 @@ +#include + +RBIMPL_WARNING_IGNORED(-Wunused-function) +RBIMPL_WARNING_IGNORED(-Wattributes) + +/* This is a public domain general purpose hash table package + originally written by Peter Moore @ UCB. + + The hash table data structures were redesigned and the package was + rewritten by Vladimir Makarov . */ + +/* The original package implemented classic bucket-based hash tables + with entries doubly linked for an access by their insertion order. + To decrease pointer chasing and as a consequence to improve a data + locality the current implementation is based on storing entries in + an array and using hash tables with open addressing. The current + entries are more compact in comparison with the original ones and + this also improves the data locality. + + The hash table has two arrays called *bins* and *entries*. + + bins: + ------- + | | entries array: + |-------| -------------------------------- + | index | | | entry: | | | + |-------| | | | | | + | ... | | ... | hash | ... | ... | + |-------| | | key | | | + | empty | | | record | | | + |-------| -------------------------------- + | ... | ^ ^ + |-------| |_ entries start |_ entries bound + |deleted| + ------- + + o The entry array contains table entries in the same order as they + were inserted. + + When the first entry is deleted, a variable containing index of + the current first entry (*entries start*) is changed. In all + other cases of the deletion, we just mark the entry as deleted by + using a reserved hash value. + + Such organization of the entry storage makes operations of the + table shift and the entries traversal very fast. + + o The bins provide access to the entries by their keys. The + key hash is mapped to a bin containing *index* of the + corresponding entry in the entry array. + + The bin array size is always power of two, it makes mapping very + fast by using the corresponding lower bits of the hash. + Generally it is not a good idea to ignore some part of the hash. + But alternative approach is worse. For example, we could use a + modulo operation for mapping and a prime number for the size of + the bin array. Unfortunately, the modulo operation for big + 64-bit numbers are extremely slow (it takes more than 100 cycles + on modern Intel CPUs). + + Still other bits of the hash value are used when the mapping + results in a collision. In this case we use a secondary hash + value which is a result of a function of the collision bin + index and the original hash value. The function choice + guarantees that we can traverse all bins and finally find the + corresponding bin as after several iterations the function + becomes a full cycle linear congruential generator because it + satisfies requirements of the Hull-Dobell theorem. + + When an entry is removed from the table besides marking the + hash in the corresponding entry described above, we also mark + the bin by a special value in order to find entries which had + a collision with the removed entries. + + There are two reserved values for the bins. One denotes an + empty bin, another one denotes a bin for a deleted entry. + + o The length of the bin array is at least two times more than the + entry array length. This keeps the table load factor healthy. + The trigger of rebuilding the table is always a case when we can + not insert an entry anymore at the entries bound. We could + change the entries bound too in case of deletion but than we need + a special code to count bins with corresponding deleted entries + and reset the bin values when there are too many bins + corresponding deleted entries + + Table rebuilding is done by creation of a new entry array and + bins of an appropriate size. We also try to reuse the arrays + in some cases by compacting the array and removing deleted + entries. + + o To save memory very small tables have no allocated arrays + bins. We use a linear search for an access by a key. + + o To save more memory we use 8-, 16-, 32- and 64- bit indexes in + bins depending on the current hash table size. + + o The implementation takes into account that the table can be + rebuilt during hashing or comparison functions. It can happen if + the functions are implemented in Ruby and a thread switch occurs + during their execution. + + This implementation speeds up the Ruby hash table benchmarks in + average by more 40% on Intel Haswell CPU. + +*/ + +#ifdef NOT_RUBY +#include "regint.h" +#include "st.h" +#else +#include "internal.h" +#include "internal/bits.h" +#include "internal/hash.h" +#include "internal/sanitizers.h" +#endif + +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#include +#include + +#ifdef __GNUC__ +#define PREFETCH(addr, write_p) __builtin_prefetch(addr, write_p) +#define EXPECT(expr, val) __builtin_expect(expr, val) +#define ATTRIBUTE_UNUSED __attribute__((unused)) +#else +#define PREFETCH(addr, write_p) +#define EXPECT(expr, val) (expr) +#define ATTRIBUTE_UNUSED +#endif + +/* The type of hashes. */ +typedef st_index_t st_hash_t; + +struct st_table_entry { + st_hash_t hash; + st_data_t key; + st_data_t record; +}; + +#define type_numhash st_hashtype_num +static const struct st_hash_type st_hashtype_num = { + st_numcmp, + st_numhash, +}; + +static int st_strcmp(st_data_t, st_data_t); +static st_index_t strhash(st_data_t); +static const struct st_hash_type type_strhash = { + st_strcmp, + strhash, +}; + +static int st_locale_insensitive_strcasecmp_i(st_data_t lhs, st_data_t rhs); +static st_index_t strcasehash(st_data_t); +static const struct st_hash_type type_strcasehash = { + st_locale_insensitive_strcasecmp_i, + strcasehash, +}; + +/* Value used to catch uninitialized entries/bins during debugging. + There is a possibility for a false alarm, but its probability is + extremely small. */ +#define ST_INIT_VAL 0xafafafafafafafaf +#define ST_INIT_VAL_BYTE 0xafa + +#ifdef RUBY +#undef malloc +#undef realloc +#undef calloc +#undef free +#define malloc ruby_xmalloc +#define calloc ruby_xcalloc +#define realloc ruby_xrealloc +#define free ruby_xfree +#endif + +#define EQUAL(tab,x,y) ((x) == (y) || (*(tab)->type->compare)((x),(y)) == 0) +#define PTR_EQUAL(tab, ptr, hash_val, key_) \ + ((ptr)->hash == (hash_val) && EQUAL((tab), (key_), (ptr)->key)) + +/* As PTR_EQUAL only its result is returned in RES. REBUILT_P is set + up to TRUE if the table is rebuilt during the comparison. */ +#define DO_PTR_EQUAL_CHECK(tab, ptr, hash_val, key, res, rebuilt_p) \ + do { \ + unsigned int _old_rebuilds_num = (tab)->rebuilds_num; \ + res = PTR_EQUAL(tab, ptr, hash_val, key); \ + rebuilt_p = _old_rebuilds_num != (tab)->rebuilds_num; \ + } while (FALSE) + +/* Features of a table. */ +struct st_features { + /* Power of 2 used for number of allocated entries. */ + unsigned char entry_power; + /* Power of 2 used for number of allocated bins. Depending on the + table size, the number of bins is 2-4 times more than the + number of entries. */ + unsigned char bin_power; + /* Enumeration of sizes of bins (8-bit, 16-bit etc). */ + unsigned char size_ind; + /* Bins are packed in words of type st_index_t. The following is + a size of bins counted by words. */ + st_index_t bins_words; +}; + +/* Features of all possible size tables. */ +#if SIZEOF_ST_INDEX_T == 8 +#define MAX_POWER2 62 +static const struct st_features features[] = { + {0, 1, 0, 0x0}, + {1, 2, 0, 0x1}, + {2, 3, 0, 0x1}, + {3, 4, 0, 0x2}, + {4, 5, 0, 0x4}, + {5, 6, 0, 0x8}, + {6, 7, 0, 0x10}, + {7, 8, 0, 0x20}, + {8, 9, 1, 0x80}, + {9, 10, 1, 0x100}, + {10, 11, 1, 0x200}, + {11, 12, 1, 0x400}, + {12, 13, 1, 0x800}, + {13, 14, 1, 0x1000}, + {14, 15, 1, 0x2000}, + {15, 16, 1, 0x4000}, + {16, 17, 2, 0x10000}, + {17, 18, 2, 0x20000}, + {18, 19, 2, 0x40000}, + {19, 20, 2, 0x80000}, + {20, 21, 2, 0x100000}, + {21, 22, 2, 0x200000}, + {22, 23, 2, 0x400000}, + {23, 24, 2, 0x800000}, + {24, 25, 2, 0x1000000}, + {25, 26, 2, 0x2000000}, + {26, 27, 2, 0x4000000}, + {27, 28, 2, 0x8000000}, + {28, 29, 2, 0x10000000}, + {29, 30, 2, 0x20000000}, + {30, 31, 2, 0x40000000}, + {31, 32, 2, 0x80000000}, + {32, 33, 3, 0x200000000}, + {33, 34, 3, 0x400000000}, + {34, 35, 3, 0x800000000}, + {35, 36, 3, 0x1000000000}, + {36, 37, 3, 0x2000000000}, + {37, 38, 3, 0x4000000000}, + {38, 39, 3, 0x8000000000}, + {39, 40, 3, 0x10000000000}, + {40, 41, 3, 0x20000000000}, + {41, 42, 3, 0x40000000000}, + {42, 43, 3, 0x80000000000}, + {43, 44, 3, 0x100000000000}, + {44, 45, 3, 0x200000000000}, + {45, 46, 3, 0x400000000000}, + {46, 47, 3, 0x800000000000}, + {47, 48, 3, 0x1000000000000}, + {48, 49, 3, 0x2000000000000}, + {49, 50, 3, 0x4000000000000}, + {50, 51, 3, 0x8000000000000}, + {51, 52, 3, 0x10000000000000}, + {52, 53, 3, 0x20000000000000}, + {53, 54, 3, 0x40000000000000}, + {54, 55, 3, 0x80000000000000}, + {55, 56, 3, 0x100000000000000}, + {56, 57, 3, 0x200000000000000}, + {57, 58, 3, 0x400000000000000}, + {58, 59, 3, 0x800000000000000}, + {59, 60, 3, 0x1000000000000000}, + {60, 61, 3, 0x2000000000000000}, + {61, 62, 3, 0x4000000000000000}, + {62, 63, 3, 0x8000000000000000}, +}; + +#else +#define MAX_POWER2 30 + +static const struct st_features features[] = { + {0, 1, 0, 0x1}, + {1, 2, 0, 0x1}, + {2, 3, 0, 0x2}, + {3, 4, 0, 0x4}, + {4, 5, 0, 0x8}, + {5, 6, 0, 0x10}, + {6, 7, 0, 0x20}, + {7, 8, 0, 0x40}, + {8, 9, 1, 0x100}, + {9, 10, 1, 0x200}, + {10, 11, 1, 0x400}, + {11, 12, 1, 0x800}, + {12, 13, 1, 0x1000}, + {13, 14, 1, 0x2000}, + {14, 15, 1, 0x4000}, + {15, 16, 1, 0x8000}, + {16, 17, 2, 0x20000}, + {17, 18, 2, 0x40000}, + {18, 19, 2, 0x80000}, + {19, 20, 2, 0x100000}, + {20, 21, 2, 0x200000}, + {21, 22, 2, 0x400000}, + {22, 23, 2, 0x800000}, + {23, 24, 2, 0x1000000}, + {24, 25, 2, 0x2000000}, + {25, 26, 2, 0x4000000}, + {26, 27, 2, 0x8000000}, + {27, 28, 2, 0x10000000}, + {28, 29, 2, 0x20000000}, + {29, 30, 2, 0x40000000}, + {30, 31, 2, 0x80000000}, +}; + +#endif + +/* The reserved hash value and its substitution. */ +#define RESERVED_HASH_VAL (~(st_hash_t) 0) +#define RESERVED_HASH_SUBSTITUTION_VAL ((st_hash_t) 0) + +/* Return hash value of KEY for table TAB. */ +static inline st_hash_t +do_hash(st_data_t key, st_table *tab) +{ + st_hash_t hash = (st_hash_t)(tab->type->hash)(key); + + /* RESERVED_HASH_VAL is used for a deleted entry. Map it into + another value. Such mapping should be extremely rare. */ + return hash == RESERVED_HASH_VAL ? RESERVED_HASH_SUBSTITUTION_VAL : hash; +} + +/* Power of 2 defining the minimal number of allocated entries. */ +#define MINIMAL_POWER2 2 + +#if MINIMAL_POWER2 < 2 +#error "MINIMAL_POWER2 should be >= 2" +#endif + +/* If the power2 of the allocated `entries` is less than the following + value, don't allocate bins and use a linear search. */ +#define MAX_POWER2_FOR_TABLES_WITHOUT_BINS 4 + +/* Return smallest n >= MINIMAL_POWER2 such 2^n > SIZE. */ +static int +get_power2(st_index_t size) +{ + unsigned int n = ST_INDEX_BITS - nlz_intptr(size); + if (n <= MAX_POWER2) + return n < MINIMAL_POWER2 ? MINIMAL_POWER2 : n; +#ifndef NOT_RUBY + /* Ran out of the table entries */ + rb_raise(rb_eRuntimeError, "st_table too big"); +#endif + /* should raise exception */ + return -1; +} + +/* Return value of N-th bin in array BINS of table with bins size + index S. */ +static inline st_index_t +get_bin(st_index_t *bins, int s, st_index_t n) +{ + return (s == 0 ? ((unsigned char *) bins)[n] + : s == 1 ? ((unsigned short *) bins)[n] + : s == 2 ? ((unsigned int *) bins)[n] + : ((st_index_t *) bins)[n]); +} + +/* Set up N-th bin in array BINS of table with bins size index S to + value V. */ +static inline void +set_bin(st_index_t *bins, int s, st_index_t n, st_index_t v) +{ + if (s == 0) ((unsigned char *) bins)[n] = (unsigned char) v; + else if (s == 1) ((unsigned short *) bins)[n] = (unsigned short) v; + else if (s == 2) ((unsigned int *) bins)[n] = (unsigned int) v; + else ((st_index_t *) bins)[n] = v; +} + +/* These macros define reserved values for empty table bin and table + bin which contains a deleted entry. We will never use such values + for an entry index in bins. */ +#define EMPTY_BIN 0 +#define DELETED_BIN 1 +/* Base of a real entry index in the bins. */ +#define ENTRY_BASE 2 + +/* Mark I-th bin of table TAB as empty, in other words not + corresponding to any entry. */ +#define MARK_BIN_EMPTY(tab, i) (set_bin((tab)->bins, get_size_ind(tab), i, EMPTY_BIN)) + +/* Values used for not found entry and bin with given + characteristics. */ +#define UNDEFINED_ENTRY_IND (~(st_index_t) 0) +#define UNDEFINED_BIN_IND (~(st_index_t) 0) + +/* Entry and bin values returned when we found a table rebuild during + the search. */ +#define REBUILT_TABLE_ENTRY_IND (~(st_index_t) 1) +#define REBUILT_TABLE_BIN_IND (~(st_index_t) 1) + +/* Mark I-th bin of table TAB as corresponding to a deleted table + entry. Update number of entries in the table and number of bins + corresponding to deleted entries. */ +#define MARK_BIN_DELETED(tab, i) \ + do { \ + set_bin((tab)->bins, get_size_ind(tab), i, DELETED_BIN); \ + } while (0) + +/* Macros to check that value B is used empty bins and bins + corresponding deleted entries. */ +#define EMPTY_BIN_P(b) ((b) == EMPTY_BIN) +#define DELETED_BIN_P(b) ((b) == DELETED_BIN) +#define EMPTY_OR_DELETED_BIN_P(b) ((b) <= DELETED_BIN) + +/* Macros to check empty bins and bins corresponding to deleted + entries. Bins are given by their index I in table TAB. */ +#define IND_EMPTY_BIN_P(tab, i) (EMPTY_BIN_P(get_bin((tab)->bins, get_size_ind(tab), i))) +#define IND_DELETED_BIN_P(tab, i) (DELETED_BIN_P(get_bin((tab)->bins, get_size_ind(tab), i))) +#define IND_EMPTY_OR_DELETED_BIN_P(tab, i) (EMPTY_OR_DELETED_BIN_P(get_bin((tab)->bins, get_size_ind(tab), i))) + +/* Macros for marking and checking deleted entries given by their + pointer E_PTR. */ +#define MARK_ENTRY_DELETED(e_ptr) ((e_ptr)->hash = RESERVED_HASH_VAL) +#define DELETED_ENTRY_P(e_ptr) ((e_ptr)->hash == RESERVED_HASH_VAL) + +/* Return bin size index of table TAB. */ +static inline unsigned int +get_size_ind(const st_table *tab) +{ + return tab->size_ind; +} + +/* Return the number of allocated bins of table TAB. */ +static inline st_index_t +get_bins_num(const st_table *tab) +{ + return ((st_index_t) 1)<bin_power; +} + +/* Return mask for a bin index in table TAB. */ +static inline st_index_t +bins_mask(const st_table *tab) +{ + return get_bins_num(tab) - 1; +} + +/* Return the index of table TAB bin corresponding to + HASH_VALUE. */ +static inline st_index_t +hash_bin(st_hash_t hash_value, st_table *tab) +{ + return hash_value & bins_mask(tab); +} + +/* Return the number of allocated entries of table TAB. */ +static inline st_index_t +get_allocated_entries(const st_table *tab) +{ + return ((st_index_t) 1)<entry_power; +} + +/* Return size of the allocated bins of table TAB. */ +static inline st_index_t +bins_size(const st_table *tab) +{ + return features[tab->entry_power].bins_words * sizeof (st_index_t); +} + +/* Mark all bins of table TAB as empty. */ +static void +initialize_bins(st_table *tab) +{ + memset(tab->bins, 0, bins_size(tab)); +} + +/* Make table TAB empty. */ +static void +make_tab_empty(st_table *tab) +{ + tab->num_entries = 0; + tab->entries_start = tab->entries_bound = 0; + if (tab->bins != NULL) + initialize_bins(tab); +} + +#ifdef HASH_LOG +#ifdef HAVE_UNISTD_H +#include +#endif +static struct { + int all, total, num, str, strcase; +} collision; + +/* Flag switching off output of package statistics at the end of + program. */ +static int init_st = 0; + +/* Output overall number of table searches and collisions into a + temporary file. */ +static void +stat_col(void) +{ + char fname[10+sizeof(long)*3]; + FILE *f; + if (!collision.total) return; + f = fopen((snprintf(fname, sizeof(fname), "/tmp/col%ld", (long)getpid()), fname), "w"); + if (f == NULL) + return; + fprintf(f, "collision: %d / %d (%6.2f)\n", collision.all, collision.total, + ((double)collision.all / (collision.total)) * 100); + fprintf(f, "num: %d, str: %d, strcase: %d\n", collision.num, collision.str, collision.strcase); + fclose(f); +} +#endif + +/* Create and return table with TYPE which can hold at least SIZE + entries. The real number of entries which the table can hold is + the nearest power of two for SIZE. */ +st_table * +st_init_table_with_size(const struct st_hash_type *type, st_index_t size) +{ + st_table *tab; + int n; + +#ifdef HASH_LOG +#if HASH_LOG+0 < 0 + { + const char *e = getenv("ST_HASH_LOG"); + if (!e || !*e) init_st = 1; + } +#endif + if (init_st == 0) { + init_st = 1; + atexit(stat_col); + } +#endif + + n = get_power2(size); +#ifndef RUBY + if (n < 0) + return NULL; +#endif + tab = (st_table *) malloc(sizeof (st_table)); +#ifndef RUBY + if (tab == NULL) + return NULL; +#endif + tab->type = type; + tab->entry_power = n; + tab->bin_power = features[n].bin_power; + tab->size_ind = features[n].size_ind; + if (n <= MAX_POWER2_FOR_TABLES_WITHOUT_BINS) + tab->bins = NULL; + else { + tab->bins = (st_index_t *) malloc(bins_size(tab)); +#ifndef RUBY + if (tab->bins == NULL) { + free(tab); + return NULL; + } +#endif + } + tab->entries = (st_table_entry *) malloc(get_allocated_entries(tab) + * sizeof(st_table_entry)); +#ifndef RUBY + if (tab->entries == NULL) { + st_free_table(tab); + return NULL; + } +#endif + make_tab_empty(tab); + tab->rebuilds_num = 0; + return tab; +} + +/* Create and return table with TYPE which can hold a minimal number + of entries (see comments for get_power2). */ +st_table * +st_init_table(const struct st_hash_type *type) +{ + return st_init_table_with_size(type, 0); +} + +/* Create and return table which can hold a minimal number of + numbers. */ +st_table * +st_init_numtable(void) +{ + return st_init_table(&type_numhash); +} + +/* Create and return table which can hold SIZE numbers. */ +st_table * +st_init_numtable_with_size(st_index_t size) +{ + return st_init_table_with_size(&type_numhash, size); +} + +/* Create and return table which can hold a minimal number of + strings. */ +st_table * +st_init_strtable(void) +{ + return st_init_table(&type_strhash); +} + +/* Create and return table which can hold SIZE strings. */ +st_table * +st_init_strtable_with_size(st_index_t size) +{ + return st_init_table_with_size(&type_strhash, size); +} + +/* Create and return table which can hold a minimal number of strings + whose character case is ignored. */ +st_table * +st_init_strcasetable(void) +{ + return st_init_table(&type_strcasehash); +} + +/* Create and return table which can hold SIZE strings whose character + case is ignored. */ +st_table * +st_init_strcasetable_with_size(st_index_t size) +{ + return st_init_table_with_size(&type_strcasehash, size); +} + +/* Make table TAB empty. */ +void +st_clear(st_table *tab) +{ + make_tab_empty(tab); + tab->rebuilds_num++; +} + +/* Free table TAB space. */ +void +st_free_table(st_table *tab) +{ + if (tab->bins != NULL) + free(tab->bins); + free(tab->entries); + free(tab); +} + +/* Return byte size of memory allocated for table TAB. */ +size_t +st_memsize(const st_table *tab) +{ + return(sizeof(st_table) + + (tab->bins == NULL ? 0 : bins_size(tab)) + + get_allocated_entries(tab) * sizeof(st_table_entry)); +} + +static st_index_t +find_table_entry_ind(st_table *tab, st_hash_t hash_value, st_data_t key); + +static st_index_t +find_table_bin_ind(st_table *tab, st_hash_t hash_value, st_data_t key); + +static st_index_t +find_table_bin_ind_direct(st_table *table, st_hash_t hash_value, st_data_t key); + +static st_index_t +find_table_bin_ptr_and_reserve(st_table *tab, st_hash_t *hash_value, + st_data_t key, st_index_t *bin_ind); + +#ifdef HASH_LOG +static void +count_collision(const struct st_hash_type *type) +{ + collision.all++; + if (type == &type_numhash) { + collision.num++; + } + else if (type == &type_strhash) { + collision.strcase++; + } + else if (type == &type_strcasehash) { + collision.str++; + } +} + +#define COLLISION (collision_check ? count_collision(tab->type) : (void)0) +#define FOUND_BIN (collision_check ? collision.total++ : (void)0) +#define collision_check 0 +#else +#define COLLISION +#define FOUND_BIN +#endif + +/* If the number of entries in the table is at least REBUILD_THRESHOLD + times less than the entry array length, decrease the table + size. */ +#define REBUILD_THRESHOLD 4 + +#if REBUILD_THRESHOLD < 2 +#error "REBUILD_THRESHOLD should be >= 2" +#endif + +/* Rebuild table TAB. Rebuilding removes all deleted bins and entries + and can change size of the table entries and bins arrays. + Rebuilding is implemented by creation of a new table or by + compaction of the existing one. */ +static void +rebuild_table(st_table *tab) +{ + st_index_t i, ni; + unsigned int size_ind; + st_table *new_tab; + st_table_entry *new_entries; + st_table_entry *curr_entry_ptr; + st_index_t *bins; + st_index_t bin_ind; + + if ((2 * tab->num_entries <= get_allocated_entries(tab) + && REBUILD_THRESHOLD * tab->num_entries > get_allocated_entries(tab)) + || tab->num_entries < (1 << MINIMAL_POWER2)) { + /* Compaction: */ + tab->num_entries = 0; + if (tab->bins != NULL) + initialize_bins(tab); + new_tab = tab; + new_entries = tab->entries; + } + else { + /* This allocation could trigger GC and compaction. If tab is the + * gen_iv_tbl, then tab could have changed in size due to objects being + * freed and/or moved. Do not store attributes of tab before this line. */ + new_tab = st_init_table_with_size(tab->type, + 2 * tab->num_entries - 1); + new_entries = new_tab->entries; + } + + ni = 0; + bins = new_tab->bins; + size_ind = get_size_ind(new_tab); + st_index_t bound = tab->entries_bound; + st_table_entry *entries = tab->entries; + + for (i = tab->entries_start; i < bound; i++) { + curr_entry_ptr = &entries[i]; + PREFETCH(entries + i + 1, 0); + if (EXPECT(DELETED_ENTRY_P(curr_entry_ptr), 0)) + continue; + if (&new_entries[ni] != curr_entry_ptr) + new_entries[ni] = *curr_entry_ptr; + if (EXPECT(bins != NULL, 1)) { + bin_ind = find_table_bin_ind_direct(new_tab, curr_entry_ptr->hash, + curr_entry_ptr->key); + set_bin(bins, size_ind, bin_ind, ni + ENTRY_BASE); + } + new_tab->num_entries++; + ni++; + } + if (new_tab != tab) { + tab->entry_power = new_tab->entry_power; + tab->bin_power = new_tab->bin_power; + tab->size_ind = new_tab->size_ind; + if (tab->bins != NULL) + free(tab->bins); + tab->bins = new_tab->bins; + free(tab->entries); + tab->entries = new_tab->entries; + free(new_tab); + } + tab->entries_start = 0; + tab->entries_bound = tab->num_entries; + tab->rebuilds_num++; +} + +/* Return the next secondary hash index for table TAB using previous + index IND and PERTERB. Finally modulo of the function becomes a + full *cycle linear congruential generator*, in other words it + guarantees traversing all table bins in extreme case. + + According the Hull-Dobell theorem a generator + "Xnext = (a*Xprev + c) mod m" is a full cycle generator if and only if + o m and c are relatively prime + o a-1 is divisible by all prime factors of m + o a-1 is divisible by 4 if m is divisible by 4. + + For our case a is 5, c is 1, and m is a power of two. */ +static inline st_index_t +secondary_hash(st_index_t ind, st_table *tab, st_index_t *perterb) +{ + *perterb >>= 11; + ind = (ind << 2) + ind + *perterb + 1; + return hash_bin(ind, tab); +} + +/* Find an entry with HASH_VALUE and KEY in TABLE using a linear + search. Return the index of the found entry in array `entries`. + If it is not found, return UNDEFINED_ENTRY_IND. If the table was + rebuilt during the search, return REBUILT_TABLE_ENTRY_IND. */ +static inline st_index_t +find_entry(st_table *tab, st_hash_t hash_value, st_data_t key) +{ + int eq_p, rebuilt_p; + st_index_t i, bound; + st_table_entry *entries; + + bound = tab->entries_bound; + entries = tab->entries; + for (i = tab->entries_start; i < bound; i++) { + DO_PTR_EQUAL_CHECK(tab, &entries[i], hash_value, key, eq_p, rebuilt_p); + if (EXPECT(rebuilt_p, 0)) + return REBUILT_TABLE_ENTRY_IND; + if (eq_p) + return i; + } + return UNDEFINED_ENTRY_IND; +} + +/* Use the quadratic probing. The method has a better data locality + but more collisions than the current approach. In average it + results in a bit slower search. */ +/*#define QUADRATIC_PROBE*/ + +/* Return index of entry with HASH_VALUE and KEY in table TAB. If + there is no such entry, return UNDEFINED_ENTRY_IND. If the table + was rebuilt during the search, return REBUILT_TABLE_ENTRY_IND. */ +static st_index_t +find_table_entry_ind(st_table *tab, st_hash_t hash_value, st_data_t key) +{ + int eq_p, rebuilt_p; + st_index_t ind; +#ifdef QUADRATIC_PROBE + st_index_t d; +#else + st_index_t peterb; +#endif + st_index_t bin; + st_table_entry *entries = tab->entries; + + ind = hash_bin(hash_value, tab); +#ifdef QUADRATIC_PROBE + d = 1; +#else + peterb = hash_value; +#endif + FOUND_BIN; + for (;;) { + bin = get_bin(tab->bins, get_size_ind(tab), ind); + if (! EMPTY_OR_DELETED_BIN_P(bin)) { + DO_PTR_EQUAL_CHECK(tab, &entries[bin - ENTRY_BASE], hash_value, key, eq_p, rebuilt_p); + if (EXPECT(rebuilt_p, 0)) + return REBUILT_TABLE_ENTRY_IND; + if (eq_p) + break; + } + else if (EMPTY_BIN_P(bin)) + return UNDEFINED_ENTRY_IND; +#ifdef QUADRATIC_PROBE + ind = hash_bin(ind + d, tab); + d++; +#else + ind = secondary_hash(ind, tab, &peterb); +#endif + COLLISION; + } + return bin; +} + +/* Find and return index of table TAB bin corresponding to an entry + with HASH_VALUE and KEY. If there is no such bin, return + UNDEFINED_BIN_IND. If the table was rebuilt during the search, + return REBUILT_TABLE_BIN_IND. */ +static st_index_t +find_table_bin_ind(st_table *tab, st_hash_t hash_value, st_data_t key) +{ + int eq_p, rebuilt_p; + st_index_t ind; +#ifdef QUADRATIC_PROBE + st_index_t d; +#else + st_index_t peterb; +#endif + st_index_t bin; + st_table_entry *entries = tab->entries; + + ind = hash_bin(hash_value, tab); +#ifdef QUADRATIC_PROBE + d = 1; +#else + peterb = hash_value; +#endif + FOUND_BIN; + for (;;) { + bin = get_bin(tab->bins, get_size_ind(tab), ind); + if (! EMPTY_OR_DELETED_BIN_P(bin)) { + DO_PTR_EQUAL_CHECK(tab, &entries[bin - ENTRY_BASE], hash_value, key, eq_p, rebuilt_p); + if (EXPECT(rebuilt_p, 0)) + return REBUILT_TABLE_BIN_IND; + if (eq_p) + break; + } + else if (EMPTY_BIN_P(bin)) + return UNDEFINED_BIN_IND; +#ifdef QUADRATIC_PROBE + ind = hash_bin(ind + d, tab); + d++; +#else + ind = secondary_hash(ind, tab, &peterb); +#endif + COLLISION; + } + return ind; +} + +/* Find and return index of table TAB bin corresponding to an entry + with HASH_VALUE and KEY. The entry should be in the table + already. */ +static st_index_t +find_table_bin_ind_direct(st_table *tab, st_hash_t hash_value, st_data_t key) +{ + st_index_t ind; +#ifdef QUADRATIC_PROBE + st_index_t d; +#else + st_index_t peterb; +#endif + st_index_t bin; + + ind = hash_bin(hash_value, tab); +#ifdef QUADRATIC_PROBE + d = 1; +#else + peterb = hash_value; +#endif + FOUND_BIN; + for (;;) { + bin = get_bin(tab->bins, get_size_ind(tab), ind); + if (EMPTY_OR_DELETED_BIN_P(bin)) + return ind; +#ifdef QUADRATIC_PROBE + ind = hash_bin(ind + d, tab); + d++; +#else + ind = secondary_hash(ind, tab, &peterb); +#endif + COLLISION; + } +} + +/* Return index of table TAB bin for HASH_VALUE and KEY through + BIN_IND and the pointed value as the function result. Reserve the + bin for inclusion of the corresponding entry into the table if it + is not there yet. We always find such bin as bins array length is + bigger entries array. Although we can reuse a deleted bin, the + result bin value is always empty if the table has no entry with + KEY. Return the entries array index of the found entry or + UNDEFINED_ENTRY_IND if it is not found. If the table was rebuilt + during the search, return REBUILT_TABLE_ENTRY_IND. */ +static st_index_t +find_table_bin_ptr_and_reserve(st_table *tab, st_hash_t *hash_value, + st_data_t key, st_index_t *bin_ind) +{ + int eq_p, rebuilt_p; + st_index_t ind; + st_hash_t curr_hash_value = *hash_value; +#ifdef QUADRATIC_PROBE + st_index_t d; +#else + st_index_t peterb; +#endif + st_index_t entry_index; + st_index_t first_deleted_bin_ind; + st_table_entry *entries; + + ind = hash_bin(curr_hash_value, tab); +#ifdef QUADRATIC_PROBE + d = 1; +#else + peterb = curr_hash_value; +#endif + FOUND_BIN; + first_deleted_bin_ind = UNDEFINED_BIN_IND; + entries = tab->entries; + for (;;) { + entry_index = get_bin(tab->bins, get_size_ind(tab), ind); + if (EMPTY_BIN_P(entry_index)) { + tab->num_entries++; + entry_index = UNDEFINED_ENTRY_IND; + if (first_deleted_bin_ind != UNDEFINED_BIN_IND) { + /* We can reuse bin of a deleted entry. */ + ind = first_deleted_bin_ind; + MARK_BIN_EMPTY(tab, ind); + } + break; + } + else if (! DELETED_BIN_P(entry_index)) { + DO_PTR_EQUAL_CHECK(tab, &entries[entry_index - ENTRY_BASE], curr_hash_value, key, eq_p, rebuilt_p); + if (EXPECT(rebuilt_p, 0)) + return REBUILT_TABLE_ENTRY_IND; + if (eq_p) + break; + } + else if (first_deleted_bin_ind == UNDEFINED_BIN_IND) + first_deleted_bin_ind = ind; +#ifdef QUADRATIC_PROBE + ind = hash_bin(ind + d, tab); + d++; +#else + ind = secondary_hash(ind, tab, &peterb); +#endif + COLLISION; + } + *bin_ind = ind; + return entry_index; +} + +/* Find an entry with KEY in table TAB. Return non-zero if we found + it. Set up *RECORD to the found entry record. */ +int +st_lookup(st_table *tab, st_data_t key, st_data_t *value) +{ + st_index_t bin; + st_hash_t hash = do_hash(key, tab); + + retry: + if (tab->bins == NULL) { + bin = find_entry(tab, hash, key); + if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0)) + goto retry; + if (bin == UNDEFINED_ENTRY_IND) + return 0; + } + else { + bin = find_table_entry_ind(tab, hash, key); + if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0)) + goto retry; + if (bin == UNDEFINED_ENTRY_IND) + return 0; + bin -= ENTRY_BASE; + } + if (value != 0) + *value = tab->entries[bin].record; + return 1; +} + +/* Find an entry with KEY in table TAB. Return non-zero if we found + it. Set up *RESULT to the found table entry key. */ +int +st_get_key(st_table *tab, st_data_t key, st_data_t *result) +{ + st_index_t bin; + st_hash_t hash = do_hash(key, tab); + + retry: + if (tab->bins == NULL) { + bin = find_entry(tab, hash, key); + if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0)) + goto retry; + if (bin == UNDEFINED_ENTRY_IND) + return 0; + } + else { + bin = find_table_entry_ind(tab, hash, key); + if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0)) + goto retry; + if (bin == UNDEFINED_ENTRY_IND) + return 0; + bin -= ENTRY_BASE; + } + if (result != 0) + *result = tab->entries[bin].key; + return 1; +} + +/* Check the table and rebuild it if it is necessary. */ +static inline void +rebuild_table_if_necessary (st_table *tab) +{ + st_index_t bound = tab->entries_bound; + + if (bound == get_allocated_entries(tab)) + rebuild_table(tab); +} + +/* Insert (KEY, VALUE) into table TAB and return zero. If there is + already entry with KEY in the table, return nonzero and update + the value of the found entry. */ +int +st_insert(st_table *tab, st_data_t key, st_data_t value) +{ + st_table_entry *entry; + st_index_t bin; + st_index_t ind; + st_hash_t hash_value; + st_index_t bin_ind; + int new_p; + + hash_value = do_hash(key, tab); + retry: + rebuild_table_if_necessary(tab); + if (tab->bins == NULL) { + bin = find_entry(tab, hash_value, key); + if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0)) + goto retry; + new_p = bin == UNDEFINED_ENTRY_IND; + if (new_p) + tab->num_entries++; + bin_ind = UNDEFINED_BIN_IND; + } + else { + bin = find_table_bin_ptr_and_reserve(tab, &hash_value, + key, &bin_ind); + if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0)) + goto retry; + new_p = bin == UNDEFINED_ENTRY_IND; + bin -= ENTRY_BASE; + } + if (new_p) { + ind = tab->entries_bound++; + entry = &tab->entries[ind]; + entry->hash = hash_value; + entry->key = key; + entry->record = value; + if (bin_ind != UNDEFINED_BIN_IND) + set_bin(tab->bins, get_size_ind(tab), bin_ind, ind + ENTRY_BASE); + return 0; + } + tab->entries[bin].record = value; + return 1; +} + +/* Insert (KEY, VALUE, HASH) into table TAB. The table should not have + entry with KEY before the insertion. */ +static inline void +st_add_direct_with_hash(st_table *tab, + st_data_t key, st_data_t value, st_hash_t hash) +{ + st_table_entry *entry; + st_index_t ind; + st_index_t bin_ind; + + rebuild_table_if_necessary(tab); + ind = tab->entries_bound++; + entry = &tab->entries[ind]; + entry->hash = hash; + entry->key = key; + entry->record = value; + tab->num_entries++; + if (tab->bins != NULL) { + bin_ind = find_table_bin_ind_direct(tab, hash, key); + set_bin(tab->bins, get_size_ind(tab), bin_ind, ind + ENTRY_BASE); + } +} + +/* Insert (KEY, VALUE) into table TAB. The table should not have + entry with KEY before the insertion. */ +void +st_add_direct(st_table *tab, st_data_t key, st_data_t value) +{ + st_hash_t hash_value; + + hash_value = do_hash(key, tab); + st_add_direct_with_hash(tab, key, value, hash_value); +} + +/* Insert (FUNC(KEY), VALUE) into table TAB and return zero. If + there is already entry with KEY in the table, return nonzero and + update the value of the found entry. */ +int +st_insert2(st_table *tab, st_data_t key, st_data_t value, + st_data_t (*func)(st_data_t)) +{ + st_table_entry *entry; + st_index_t bin; + st_index_t ind; + st_hash_t hash_value; + st_index_t bin_ind; + int new_p; + + hash_value = do_hash(key, tab); + retry: + rebuild_table_if_necessary (tab); + if (tab->bins == NULL) { + bin = find_entry(tab, hash_value, key); + if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0)) + goto retry; + new_p = bin == UNDEFINED_ENTRY_IND; + if (new_p) + tab->num_entries++; + bin_ind = UNDEFINED_BIN_IND; + } + else { + bin = find_table_bin_ptr_and_reserve(tab, &hash_value, + key, &bin_ind); + if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0)) + goto retry; + new_p = bin == UNDEFINED_ENTRY_IND; + bin -= ENTRY_BASE; + } + if (new_p) { + key = (*func)(key); + ind = tab->entries_bound++; + entry = &tab->entries[ind]; + entry->hash = hash_value; + entry->key = key; + entry->record = value; + if (bin_ind != UNDEFINED_BIN_IND) + set_bin(tab->bins, get_size_ind(tab), bin_ind, ind + ENTRY_BASE); + return 0; + } + tab->entries[bin].record = value; + return 1; +} + +/* Create and return a copy of table OLD_TAB. */ +st_table * +st_copy(st_table *old_tab) +{ + st_table *new_tab; + + new_tab = (st_table *) malloc(sizeof(st_table)); +#ifndef RUBY + if (new_tab == NULL) + return NULL; +#endif + *new_tab = *old_tab; + if (old_tab->bins == NULL) + new_tab->bins = NULL; + else { + new_tab->bins = (st_index_t *) malloc(bins_size(old_tab)); +#ifndef RUBY + if (new_tab->bins == NULL) { + free(new_tab); + return NULL; + } +#endif + } + new_tab->entries = (st_table_entry *) malloc(get_allocated_entries(old_tab) + * sizeof(st_table_entry)); +#ifndef RUBY + if (new_tab->entries == NULL) { + st_free_table(new_tab); + return NULL; + } +#endif + MEMCPY(new_tab->entries, old_tab->entries, st_table_entry, + get_allocated_entries(old_tab)); + if (old_tab->bins != NULL) + MEMCPY(new_tab->bins, old_tab->bins, char, bins_size(old_tab)); + return new_tab; +} + +/* Update the entries start of table TAB after removing an entry + with index N in the array entries. */ +static inline void +update_range_for_deleted(st_table *tab, st_index_t n) +{ + /* Do not update entries_bound here. Otherwise, we can fill all + bins by deleted entry value before rebuilding the table. */ + if (tab->entries_start == n) { + st_index_t start = n + 1; + st_index_t bound = tab->entries_bound; + st_table_entry *entries = tab->entries; + while (start < bound && DELETED_ENTRY_P(&entries[start])) start++; + tab->entries_start = start; + } +} + +/* Delete entry with KEY from table TAB, set up *VALUE (unless + VALUE is zero) from deleted table entry, and return non-zero. If + there is no entry with KEY in the table, clear *VALUE (unless VALUE + is zero), and return zero. */ +static int +st_general_delete(st_table *tab, st_data_t *key, st_data_t *value) +{ + st_table_entry *entry; + st_index_t bin; + st_index_t bin_ind; + st_hash_t hash; + + hash = do_hash(*key, tab); + retry: + if (tab->bins == NULL) { + bin = find_entry(tab, hash, *key); + if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0)) + goto retry; + if (bin == UNDEFINED_ENTRY_IND) { + if (value != 0) *value = 0; + return 0; + } + } + else { + bin_ind = find_table_bin_ind(tab, hash, *key); + if (EXPECT(bin_ind == REBUILT_TABLE_BIN_IND, 0)) + goto retry; + if (bin_ind == UNDEFINED_BIN_IND) { + if (value != 0) *value = 0; + return 0; + } + bin = get_bin(tab->bins, get_size_ind(tab), bin_ind) - ENTRY_BASE; + MARK_BIN_DELETED(tab, bin_ind); + } + entry = &tab->entries[bin]; + *key = entry->key; + if (value != 0) *value = entry->record; + MARK_ENTRY_DELETED(entry); + tab->num_entries--; + update_range_for_deleted(tab, bin); + return 1; +} + +int +st_delete(st_table *tab, st_data_t *key, st_data_t *value) +{ + return st_general_delete(tab, key, value); +} + +/* The function and other functions with suffix '_safe' or '_check' + are originated from the previous implementation of the hash tables. + It was necessary for correct deleting entries during traversing + tables. The current implementation permits deletion during + traversing without a specific way to do this. */ +int +st_delete_safe(st_table *tab, st_data_t *key, st_data_t *value, + st_data_t never ATTRIBUTE_UNUSED) +{ + return st_general_delete(tab, key, value); +} + +/* If table TAB is empty, clear *VALUE (unless VALUE is zero), and + return zero. Otherwise, remove the first entry in the table. + Return its key through KEY and its record through VALUE (unless + VALUE is zero). */ +int +st_shift(st_table *tab, st_data_t *key, st_data_t *value) +{ + st_index_t i, bound; + st_index_t bin; + st_table_entry *entries, *curr_entry_ptr; + st_index_t bin_ind; + + entries = tab->entries; + bound = tab->entries_bound; + for (i = tab->entries_start; i < bound; i++) { + curr_entry_ptr = &entries[i]; + if (! DELETED_ENTRY_P(curr_entry_ptr)) { + st_hash_t entry_hash = curr_entry_ptr->hash; + st_data_t entry_key = curr_entry_ptr->key; + + if (value != 0) *value = curr_entry_ptr->record; + *key = entry_key; + retry: + if (tab->bins == NULL) { + bin = find_entry(tab, entry_hash, entry_key); + if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0)) { + entries = tab->entries; + goto retry; + } + curr_entry_ptr = &entries[bin]; + } + else { + bin_ind = find_table_bin_ind(tab, entry_hash, entry_key); + if (EXPECT(bin_ind == REBUILT_TABLE_BIN_IND, 0)) { + entries = tab->entries; + goto retry; + } + curr_entry_ptr = &entries[get_bin(tab->bins, get_size_ind(tab), bin_ind) + - ENTRY_BASE]; + MARK_BIN_DELETED(tab, bin_ind); + } + MARK_ENTRY_DELETED(curr_entry_ptr); + tab->num_entries--; + update_range_for_deleted(tab, i); + return 1; + } + } + if (value != 0) *value = 0; + return 0; +} + +/* See comments for function st_delete_safe. */ +void +st_cleanup_safe(st_table *tab ATTRIBUTE_UNUSED, + st_data_t never ATTRIBUTE_UNUSED) +{ +} + +/* Find entry with KEY in table TAB, call FUNC with pointers to copies + of the key and the value of the found entry, and non-zero as the + 3rd argument. If the entry is not found, call FUNC with a pointer + to KEY, a pointer to zero, and a zero argument. If the call + returns ST_CONTINUE, the table will have an entry with key and + value returned by FUNC through the 1st and 2nd parameters. If the + call of FUNC returns ST_DELETE, the table will not have entry with + KEY. The function returns flag of that the entry with KEY was in + the table before the call. */ +int +st_update(st_table *tab, st_data_t key, + st_update_callback_func *func, st_data_t arg) +{ + st_table_entry *entry = NULL; /* to avoid uninitialized value warning */ + st_index_t bin = 0; /* Ditto */ + st_table_entry *entries; + st_index_t bin_ind; + st_data_t value = 0, old_key; + int retval, existing; + st_hash_t hash = do_hash(key, tab); + + retry: + entries = tab->entries; + if (tab->bins == NULL) { + bin = find_entry(tab, hash, key); + if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0)) + goto retry; + existing = bin != UNDEFINED_ENTRY_IND; + entry = &entries[bin]; + bin_ind = UNDEFINED_BIN_IND; + } + else { + bin_ind = find_table_bin_ind(tab, hash, key); + if (EXPECT(bin_ind == REBUILT_TABLE_BIN_IND, 0)) + goto retry; + existing = bin_ind != UNDEFINED_BIN_IND; + if (existing) { + bin = get_bin(tab->bins, get_size_ind(tab), bin_ind) - ENTRY_BASE; + entry = &entries[bin]; + } + } + if (existing) { + key = entry->key; + value = entry->record; + } + old_key = key; + retval = (*func)(&key, &value, arg, existing); + switch (retval) { + case ST_CONTINUE: + if (! existing) { + st_add_direct_with_hash(tab, key, value, hash); + break; + } + if (old_key != key) { + entry->key = key; + } + entry->record = value; + break; + case ST_DELETE: + if (existing) { + if (bin_ind != UNDEFINED_BIN_IND) + MARK_BIN_DELETED(tab, bin_ind); + MARK_ENTRY_DELETED(entry); + tab->num_entries--; + update_range_for_deleted(tab, bin); + } + break; + } + return existing; +} + +/* Traverse all entries in table TAB calling FUNC with current entry + key and value and zero. If the call returns ST_STOP, stop + traversing. If the call returns ST_DELETE, delete the current + entry from the table. In case of ST_CHECK or ST_CONTINUE, continue + traversing. The function returns zero unless an error is found. + CHECK_P is flag of st_foreach_check call. The behavior is a bit + different for ST_CHECK and when the current element is removed + during traversing. */ +static inline int +st_general_foreach(st_table *tab, st_foreach_check_callback_func *func, st_update_callback_func *replace, st_data_t arg, + int check_p) +{ + st_index_t bin; + st_index_t bin_ind; + st_table_entry *entries, *curr_entry_ptr; + enum st_retval retval; + st_index_t i, rebuilds_num; + st_hash_t hash; + st_data_t key; + int error_p, packed_p = tab->bins == NULL; + + entries = tab->entries; + /* The bound can change inside the loop even without rebuilding + the table, e.g. by an entry insertion. */ + for (i = tab->entries_start; i < tab->entries_bound; i++) { + curr_entry_ptr = &entries[i]; + if (EXPECT(DELETED_ENTRY_P(curr_entry_ptr), 0)) + continue; + key = curr_entry_ptr->key; + rebuilds_num = tab->rebuilds_num; + hash = curr_entry_ptr->hash; + retval = (*func)(key, curr_entry_ptr->record, arg, 0); + + if (retval == ST_REPLACE && replace) { + st_data_t value; + value = curr_entry_ptr->record; + retval = (*replace)(&key, &value, arg, TRUE); + curr_entry_ptr->key = key; + curr_entry_ptr->record = value; + } + + if (rebuilds_num != tab->rebuilds_num) { + retry: + entries = tab->entries; + packed_p = tab->bins == NULL; + if (packed_p) { + i = find_entry(tab, hash, key); + if (EXPECT(i == REBUILT_TABLE_ENTRY_IND, 0)) + goto retry; + error_p = i == UNDEFINED_ENTRY_IND; + } + else { + i = find_table_entry_ind(tab, hash, key); + if (EXPECT(i == REBUILT_TABLE_ENTRY_IND, 0)) + goto retry; + error_p = i == UNDEFINED_ENTRY_IND; + i -= ENTRY_BASE; + } + if (error_p && check_p) { + /* call func with error notice */ + retval = (*func)(0, 0, arg, 1); + return 1; + } + curr_entry_ptr = &entries[i]; + } + switch (retval) { + case ST_REPLACE: + break; + case ST_CONTINUE: + break; + case ST_CHECK: + if (check_p) + break; + case ST_STOP: + return 0; + case ST_DELETE: { + st_data_t key = curr_entry_ptr->key; + + again: + if (packed_p) { + bin = find_entry(tab, hash, key); + if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0)) + goto again; + if (bin == UNDEFINED_ENTRY_IND) + break; + } + else { + bin_ind = find_table_bin_ind(tab, hash, key); + if (EXPECT(bin_ind == REBUILT_TABLE_BIN_IND, 0)) + goto again; + if (bin_ind == UNDEFINED_BIN_IND) + break; + bin = get_bin(tab->bins, get_size_ind(tab), bin_ind) - ENTRY_BASE; + MARK_BIN_DELETED(tab, bin_ind); + } + curr_entry_ptr = &entries[bin]; + MARK_ENTRY_DELETED(curr_entry_ptr); + tab->num_entries--; + update_range_for_deleted(tab, bin); + break; + } + } + } + return 0; +} + +int +st_foreach_with_replace(st_table *tab, st_foreach_check_callback_func *func, st_update_callback_func *replace, st_data_t arg) +{ + return st_general_foreach(tab, func, replace, arg, TRUE); +} + +struct functor { + st_foreach_callback_func *func; + st_data_t arg; +}; + +static int +apply_functor(st_data_t k, st_data_t v, st_data_t d, int _) +{ + const struct functor *f = (void *)d; + return f->func(k, v, f->arg); +} + +int +st_foreach(st_table *tab, st_foreach_callback_func *func, st_data_t arg) +{ + const struct functor f = { func, arg }; + return st_general_foreach(tab, apply_functor, 0, (st_data_t)&f, FALSE); +} + +/* See comments for function st_delete_safe. */ +int +st_foreach_check(st_table *tab, st_foreach_check_callback_func *func, st_data_t arg, + st_data_t never ATTRIBUTE_UNUSED) +{ + return st_general_foreach(tab, func, 0, arg, TRUE); +} + +/* Set up array KEYS by at most SIZE keys of head table TAB entries. + Return the number of keys set up in array KEYS. */ +static inline st_index_t +st_general_keys(st_table *tab, st_data_t *keys, st_index_t size) +{ + st_index_t i, bound; + st_data_t key, *keys_start, *keys_end; + st_table_entry *curr_entry_ptr, *entries = tab->entries; + + bound = tab->entries_bound; + keys_start = keys; + keys_end = keys + size; + for (i = tab->entries_start; i < bound; i++) { + if (keys == keys_end) + break; + curr_entry_ptr = &entries[i]; + key = curr_entry_ptr->key; + if (! DELETED_ENTRY_P(curr_entry_ptr)) + *keys++ = key; + } + + return keys - keys_start; +} + +st_index_t +st_keys(st_table *tab, st_data_t *keys, st_index_t size) +{ + return st_general_keys(tab, keys, size); +} + +/* See comments for function st_delete_safe. */ +st_index_t +st_keys_check(st_table *tab, st_data_t *keys, st_index_t size, + st_data_t never ATTRIBUTE_UNUSED) +{ + return st_general_keys(tab, keys, size); +} + +/* Set up array VALUES by at most SIZE values of head table TAB + entries. Return the number of values set up in array VALUES. */ +static inline st_index_t +st_general_values(st_table *tab, st_data_t *values, st_index_t size) +{ + st_index_t i, bound; + st_data_t *values_start, *values_end; + st_table_entry *curr_entry_ptr, *entries = tab->entries; + + values_start = values; + values_end = values + size; + bound = tab->entries_bound; + for (i = tab->entries_start; i < bound; i++) { + if (values == values_end) + break; + curr_entry_ptr = &entries[i]; + if (! DELETED_ENTRY_P(curr_entry_ptr)) + *values++ = curr_entry_ptr->record; + } + + return values - values_start; +} + +st_index_t +st_values(st_table *tab, st_data_t *values, st_index_t size) +{ + return st_general_values(tab, values, size); +} + +/* See comments for function st_delete_safe. */ +st_index_t +st_values_check(st_table *tab, st_data_t *values, st_index_t size, + st_data_t never ATTRIBUTE_UNUSED) +{ + return st_general_values(tab, values, size); +} + +#define FNV1_32A_INIT 0x811c9dc5 + +/* + * 32 bit magic FNV-1a prime + */ +#define FNV_32_PRIME 0x01000193 + +/* __POWERPC__ added to accommodate Darwin case. */ +#ifndef UNALIGNED_WORD_ACCESS +# if defined(__i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_AMD64) || \ + defined(__powerpc64__) || defined(__POWERPC__) || defined(__aarch64__) || \ + defined(__mc68020__) +# define UNALIGNED_WORD_ACCESS 1 +# endif +#endif +#ifndef UNALIGNED_WORD_ACCESS +# define UNALIGNED_WORD_ACCESS 0 +#endif + +/* This hash function is quite simplified MurmurHash3 + * Simplification is legal, cause most of magic still happens in finalizator. + * And finalizator is almost the same as in MurmurHash3 */ +#define BIG_CONSTANT(x,y) ((st_index_t)(x)<<32|(st_index_t)(y)) +#define ROTL(x,n) ((x)<<(n)|(x)>>(SIZEOF_ST_INDEX_T*CHAR_BIT-(n))) + +#if ST_INDEX_BITS <= 32 +#define C1 (st_index_t)0xcc9e2d51 +#define C2 (st_index_t)0x1b873593 +#else +#define C1 BIG_CONSTANT(0x87c37b91,0x114253d5); +#define C2 BIG_CONSTANT(0x4cf5ad43,0x2745937f); +#endif +NO_SANITIZE("unsigned-integer-overflow", static inline st_index_t murmur_step(st_index_t h, st_index_t k)); +NO_SANITIZE("unsigned-integer-overflow", static inline st_index_t murmur_finish(st_index_t h)); +NO_SANITIZE("unsigned-integer-overflow", extern st_index_t st_hash(const void *ptr, size_t len, st_index_t h)); + +static inline st_index_t +murmur_step(st_index_t h, st_index_t k) +{ +#if ST_INDEX_BITS <= 32 +#define r1 (17) +#define r2 (11) +#else +#define r1 (33) +#define r2 (24) +#endif + k *= C1; + h ^= ROTL(k, r1); + h *= C2; + h = ROTL(h, r2); + return h; +} +#undef r1 +#undef r2 + +static inline st_index_t +murmur_finish(st_index_t h) +{ +#if ST_INDEX_BITS <= 32 +#define r1 (16) +#define r2 (13) +#define r3 (16) + const st_index_t c1 = 0x85ebca6b; + const st_index_t c2 = 0xc2b2ae35; +#else +/* values are taken from Mix13 on http://zimbry.blogspot.ru/2011/09/better-bit-mixing-improving-on.html */ +#define r1 (30) +#define r2 (27) +#define r3 (31) + const st_index_t c1 = BIG_CONSTANT(0xbf58476d,0x1ce4e5b9); + const st_index_t c2 = BIG_CONSTANT(0x94d049bb,0x133111eb); +#endif +#if ST_INDEX_BITS > 64 + h ^= h >> 64; + h *= c2; + h ^= h >> 65; +#endif + h ^= h >> r1; + h *= c1; + h ^= h >> r2; + h *= c2; + h ^= h >> r3; + return h; +} +#undef r1 +#undef r2 +#undef r3 + +st_index_t +st_hash(const void *ptr, size_t len, st_index_t h) +{ + const char *data = ptr; + st_index_t t = 0; + size_t l = len; + +#define data_at(n) (st_index_t)((unsigned char)data[(n)]) +#define UNALIGNED_ADD_4 UNALIGNED_ADD(2); UNALIGNED_ADD(1); UNALIGNED_ADD(0) +#if SIZEOF_ST_INDEX_T > 4 +#define UNALIGNED_ADD_8 UNALIGNED_ADD(6); UNALIGNED_ADD(5); UNALIGNED_ADD(4); UNALIGNED_ADD(3); UNALIGNED_ADD_4 +#if SIZEOF_ST_INDEX_T > 8 +#define UNALIGNED_ADD_16 UNALIGNED_ADD(14); UNALIGNED_ADD(13); UNALIGNED_ADD(12); UNALIGNED_ADD(11); \ + UNALIGNED_ADD(10); UNALIGNED_ADD(9); UNALIGNED_ADD(8); UNALIGNED_ADD(7); UNALIGNED_ADD_8 +#define UNALIGNED_ADD_ALL UNALIGNED_ADD_16 +#endif +#define UNALIGNED_ADD_ALL UNALIGNED_ADD_8 +#else +#define UNALIGNED_ADD_ALL UNALIGNED_ADD_4 +#endif +#undef SKIP_TAIL + if (len >= sizeof(st_index_t)) { +#if !UNALIGNED_WORD_ACCESS + int align = (int)((st_data_t)data % sizeof(st_index_t)); + if (align) { + st_index_t d = 0; + int sl, sr, pack; + + switch (align) { +#ifdef WORDS_BIGENDIAN +# define UNALIGNED_ADD(n) case SIZEOF_ST_INDEX_T - (n) - 1: \ + t |= data_at(n) << CHAR_BIT*(SIZEOF_ST_INDEX_T - (n) - 2) +#else +# define UNALIGNED_ADD(n) case SIZEOF_ST_INDEX_T - (n) - 1: \ + t |= data_at(n) << CHAR_BIT*(n) +#endif + UNALIGNED_ADD_ALL; +#undef UNALIGNED_ADD + } + +#ifdef WORDS_BIGENDIAN + t >>= (CHAR_BIT * align) - CHAR_BIT; +#else + t <<= (CHAR_BIT * align); +#endif + + data += sizeof(st_index_t)-align; + len -= sizeof(st_index_t)-align; + + sl = CHAR_BIT * (SIZEOF_ST_INDEX_T-align); + sr = CHAR_BIT * align; + + while (len >= sizeof(st_index_t)) { + d = *(st_index_t *)data; +#ifdef WORDS_BIGENDIAN + t = (t << sr) | (d >> sl); +#else + t = (t >> sr) | (d << sl); +#endif + h = murmur_step(h, t); + t = d; + data += sizeof(st_index_t); + len -= sizeof(st_index_t); + } + + pack = len < (size_t)align ? (int)len : align; + d = 0; + switch (pack) { +#ifdef WORDS_BIGENDIAN +# define UNALIGNED_ADD(n) case (n) + 1: \ + d |= data_at(n) << CHAR_BIT*(SIZEOF_ST_INDEX_T - (n) - 1) +#else +# define UNALIGNED_ADD(n) case (n) + 1: \ + d |= data_at(n) << CHAR_BIT*(n) +#endif + UNALIGNED_ADD_ALL; +#undef UNALIGNED_ADD + } +#ifdef WORDS_BIGENDIAN + t = (t << sr) | (d >> sl); +#else + t = (t >> sr) | (d << sl); +#endif + + if (len < (size_t)align) goto skip_tail; +# define SKIP_TAIL 1 + h = murmur_step(h, t); + data += pack; + len -= pack; + } + else +#endif +#ifdef HAVE_BUILTIN___BUILTIN_ASSUME_ALIGNED +#define aligned_data __builtin_assume_aligned(data, sizeof(st_index_t)) +#else +#define aligned_data data +#endif + { + do { + h = murmur_step(h, *(st_index_t *)aligned_data); + data += sizeof(st_index_t); + len -= sizeof(st_index_t); + } while (len >= sizeof(st_index_t)); + } + } + + t = 0; + switch (len) { +#if UNALIGNED_WORD_ACCESS && SIZEOF_ST_INDEX_T <= 8 && CHAR_BIT == 8 + /* in this case byteorder doesn't really matter */ +#if SIZEOF_ST_INDEX_T > 4 + case 7: t |= data_at(6) << 48; + case 6: t |= data_at(5) << 40; + case 5: t |= data_at(4) << 32; + case 4: + t |= (st_index_t)*(uint32_t*)aligned_data; + goto skip_tail; +# define SKIP_TAIL 1 +#endif + case 3: t |= data_at(2) << 16; + case 2: t |= data_at(1) << 8; + case 1: t |= data_at(0); +#else +#ifdef WORDS_BIGENDIAN +# define UNALIGNED_ADD(n) case (n) + 1: \ + t |= data_at(n) << CHAR_BIT*(SIZEOF_ST_INDEX_T - (n) - 1) +#else +# define UNALIGNED_ADD(n) case (n) + 1: \ + t |= data_at(n) << CHAR_BIT*(n) +#endif + UNALIGNED_ADD_ALL; +#undef UNALIGNED_ADD +#endif +#ifdef SKIP_TAIL + skip_tail: +#endif + h ^= t; h -= ROTL(t, 7); + h *= C2; + } + h ^= l; +#undef aligned_data + + return murmur_finish(h); +} + +st_index_t +st_hash_uint32(st_index_t h, uint32_t i) +{ + return murmur_step(h, i); +} + +NO_SANITIZE("unsigned-integer-overflow", extern st_index_t st_hash_uint(st_index_t h, st_index_t i)); +st_index_t +st_hash_uint(st_index_t h, st_index_t i) +{ + i += h; +/* no matter if it is BigEndian or LittleEndian, + * we hash just integers */ +#if SIZEOF_ST_INDEX_T*CHAR_BIT > 8*8 + h = murmur_step(h, i >> 8*8); +#endif + h = murmur_step(h, i); + return h; +} + +st_index_t +st_hash_end(st_index_t h) +{ + h = murmur_finish(h); + return h; +} + +#undef st_hash_start +st_index_t +rb_st_hash_start(st_index_t h) +{ + return h; +} + +static st_index_t +strhash(st_data_t arg) +{ + register const char *string = (const char *)arg; + return st_hash(string, strlen(string), FNV1_32A_INIT); +} + +int +st_locale_insensitive_strcasecmp(const char *s1, const char *s2) +{ + char c1, c2; + + while (1) { + c1 = *s1++; + c2 = *s2++; + if (c1 == '\0' || c2 == '\0') { + if (c1 != '\0') return 1; + if (c2 != '\0') return -1; + return 0; + } + if (('A' <= c1) && (c1 <= 'Z')) c1 += 'a' - 'A'; + if (('A' <= c2) && (c2 <= 'Z')) c2 += 'a' - 'A'; + if (c1 != c2) { + if (c1 > c2) + return 1; + else + return -1; + } + } +} + +int +st_locale_insensitive_strncasecmp(const char *s1, const char *s2, size_t n) +{ + char c1, c2; + size_t i; + + for (i = 0; i < n; i++) { + c1 = *s1++; + c2 = *s2++; + if (c1 == '\0' || c2 == '\0') { + if (c1 != '\0') return 1; + if (c2 != '\0') return -1; + return 0; + } + if (('A' <= c1) && (c1 <= 'Z')) c1 += 'a' - 'A'; + if (('A' <= c2) && (c2 <= 'Z')) c2 += 'a' - 'A'; + if (c1 != c2) { + if (c1 > c2) + return 1; + else + return -1; + } + } + return 0; +} + +static int +st_strcmp(st_data_t lhs, st_data_t rhs) +{ + const char *s1 = (char *)lhs; + const char *s2 = (char *)rhs; + return strcmp(s1, s2); +} + +static int +st_locale_insensitive_strcasecmp_i(st_data_t lhs, st_data_t rhs) +{ + const char *s1 = (char *)lhs; + const char *s2 = (char *)rhs; + return st_locale_insensitive_strcasecmp(s1, s2); +} + +NO_SANITIZE("unsigned-integer-overflow", PUREFUNC(static st_index_t strcasehash(st_data_t))); +static st_index_t +strcasehash(st_data_t arg) +{ + register const char *string = (const char *)arg; + register st_index_t hval = FNV1_32A_INIT; + + /* + * FNV-1a hash each octet in the buffer + */ + while (*string) { + unsigned int c = (unsigned char)*string++; + if ((unsigned int)(c - 'A') <= ('Z' - 'A')) c += 'a' - 'A'; + hval ^= c; + + /* multiply by the 32 bit FNV magic prime mod 2^32 */ + hval *= FNV_32_PRIME; + } + return hval; +} + +int +st_numcmp(st_data_t x, st_data_t y) +{ + return x != y; +} + +st_index_t +st_numhash(st_data_t n) +{ + enum {s1 = 11, s2 = 3}; + return (st_index_t)((n>>s1|(n<>s2)); +} + +/* Expand TAB to be suitable for holding SIZ entries in total. + Pre-existing entries remain not deleted inside of TAB, but its bins + are cleared to expect future reconstruction. See rehash below. */ +static void +st_expand_table(st_table *tab, st_index_t siz) +{ + st_table *tmp; + st_index_t n; + + if (siz <= get_allocated_entries(tab)) + return; /* enough room already */ + + tmp = st_init_table_with_size(tab->type, siz); + n = get_allocated_entries(tab); + MEMCPY(tmp->entries, tab->entries, st_table_entry, n); + free(tab->entries); + if (tab->bins != NULL) + free(tab->bins); + if (tmp->bins != NULL) + free(tmp->bins); + tab->entry_power = tmp->entry_power; + tab->bin_power = tmp->bin_power; + tab->size_ind = tmp->size_ind; + tab->entries = tmp->entries; + tab->bins = NULL; + tab->rebuilds_num++; + free(tmp); +} + +/* Rehash using linear search. Return TRUE if we found that the table + was rebuilt. */ +static int +st_rehash_linear(st_table *tab) +{ + int eq_p, rebuilt_p; + st_index_t i, j; + st_table_entry *p, *q; + if (tab->bins) { + free(tab->bins); + tab->bins = NULL; + } + for (i = tab->entries_start; i < tab->entries_bound; i++) { + p = &tab->entries[i]; + if (DELETED_ENTRY_P(p)) + continue; + for (j = i + 1; j < tab->entries_bound; j++) { + q = &tab->entries[j]; + if (DELETED_ENTRY_P(q)) + continue; + DO_PTR_EQUAL_CHECK(tab, p, q->hash, q->key, eq_p, rebuilt_p); + if (EXPECT(rebuilt_p, 0)) + return TRUE; + if (eq_p) { + *p = *q; + MARK_ENTRY_DELETED(q); + tab->num_entries--; + update_range_for_deleted(tab, j); + } + } + } + return FALSE; +} + +/* Rehash using index. Return TRUE if we found that the table was + rebuilt. */ +static int +st_rehash_indexed(st_table *tab) +{ + int eq_p, rebuilt_p; + st_index_t i; + st_index_t const n = bins_size(tab); + unsigned int const size_ind = get_size_ind(tab); + st_index_t *bins = realloc(tab->bins, n); + tab->bins = bins; + initialize_bins(tab); + for (i = tab->entries_start; i < tab->entries_bound; i++) { + st_table_entry *p = &tab->entries[i]; + st_index_t ind; +#ifdef QUADRATIC_PROBE + st_index_t d = 1; +#else + st_index_t peterb = p->hash; +#endif + + if (DELETED_ENTRY_P(p)) + continue; + + ind = hash_bin(p->hash, tab); + for (;;) { + st_index_t bin = get_bin(bins, size_ind, ind); + if (EMPTY_OR_DELETED_BIN_P(bin)) { + /* ok, new room */ + set_bin(bins, size_ind, ind, i + ENTRY_BASE); + break; + } + else { + st_table_entry *q = &tab->entries[bin - ENTRY_BASE]; + DO_PTR_EQUAL_CHECK(tab, q, p->hash, p->key, eq_p, rebuilt_p); + if (EXPECT(rebuilt_p, 0)) + return TRUE; + if (eq_p) { + /* duplicated key; delete it */ + q->record = p->record; + MARK_ENTRY_DELETED(p); + tab->num_entries--; + update_range_for_deleted(tab, bin); + break; + } + else { + /* hash collision; skip it */ +#ifdef QUADRATIC_PROBE + ind = hash_bin(ind + d, tab); + d++; +#else + ind = secondary_hash(ind, tab, &peterb); +#endif + } + } + } + } + return FALSE; +} + +/* Reconstruct TAB's bins according to TAB's entries. This function + permits conflicting keys inside of entries. No errors are reported + then. All but one of them are discarded silently. */ +static void +st_rehash(st_table *tab) +{ + int rebuilt_p; + + do { + if (tab->bin_power <= MAX_POWER2_FOR_TABLES_WITHOUT_BINS) + rebuilt_p = st_rehash_linear(tab); + else + rebuilt_p = st_rehash_indexed(tab); + } while (rebuilt_p); +} + +#ifdef RUBY +static st_data_t +st_stringify(VALUE key) +{ + return (rb_obj_class(key) == rb_cString && !RB_OBJ_FROZEN(key)) ? + rb_hash_key_str(key) : key; +} + +static void +st_insert_single(st_table *tab, VALUE hash, VALUE key, VALUE val) +{ + st_data_t k = st_stringify(key); + st_table_entry e; + e.hash = do_hash(k, tab); + e.key = k; + e.record = val; + + tab->entries[tab->entries_bound++] = e; + tab->num_entries++; + RB_OBJ_WRITTEN(hash, Qundef, k); + RB_OBJ_WRITTEN(hash, Qundef, val); +} + +static void +st_insert_linear(st_table *tab, long argc, const VALUE *argv, VALUE hash) +{ + long i; + + for (i = 0; i < argc; /* */) { + st_data_t k = st_stringify(argv[i++]); + st_data_t v = argv[i++]; + st_insert(tab, k, v); + RB_OBJ_WRITTEN(hash, Qundef, k); + RB_OBJ_WRITTEN(hash, Qundef, v); + } +} + +static void +st_insert_generic(st_table *tab, long argc, const VALUE *argv, VALUE hash) +{ + long i; + + /* push elems */ + for (i = 0; i < argc; /* */) { + VALUE key = argv[i++]; + VALUE val = argv[i++]; + st_insert_single(tab, hash, key, val); + } + + /* reindex */ + st_rehash(tab); +} + +// to iterate iv_index_tbl +st_data_t +rb_st_nth_key(st_table *tab, st_index_t index) +{ + if (LIKELY(tab->entries_start == 0 && + tab->num_entries == tab->entries_bound && + index < tab->num_entries)) { + return tab->entries[index].key; + } + else { + rb_bug("unreachable"); + } +} + +#endif diff --git a/src/main/c/cext/args.c b/src/main/c/cext/args.c index 66de965e0ba7..04783e017c88 100644 --- a/src/main/c/cext/args.c +++ b/src/main/c/cext/args.c @@ -11,6 +11,8 @@ #include #include +#include // isdigit + // Parsing Ruby arguments from C functions static VALUE rb_keyword_error_new(const char *error, VALUE keys) { @@ -31,11 +33,13 @@ static VALUE rb_keyword_error_new(const char *error, VALUE keys) { return rb_exc_new_str(rb_eArgError, error_message); } -NORETURN(static void rb_keyword_error(const char *error, VALUE keys)) { +RBIMPL_ATTR_NORETURN() +static void rb_keyword_error(const char *error, VALUE keys) { rb_exc_raise(rb_keyword_error_new(error, keys)); } -NORETURN(static void unknown_keyword_error(VALUE hash, const ID *table, int keywords)) { +RBIMPL_ATTR_NORETURN() +static void unknown_keyword_error(VALUE hash, const ID *table, int keywords) { int i; for (i = 0; i < keywords; i++) { VALUE key = table[i]; @@ -146,3 +150,7 @@ void rb_tr_scan_args_kw_parse(const char *format, struct rb_tr_scan_args_parse_d rb_raise(rb_eArgError, "bad rb_scan_args format"); } } + +bool rb_tr_scan_args_test_kwargs(VALUE kwargs, VALUE raise_error) { + return polyglot_as_boolean(RUBY_CEXT_INVOKE_NO_WRAP("test_kwargs", kwargs, raise_error)); +} diff --git a/src/main/c/cext/array.c b/src/main/c/cext/array.c index aad8c8c1589e..a0a8d45e8a2d 100644 --- a/src/main/c/cext/array.c +++ b/src/main/c/cext/array.c @@ -32,7 +32,7 @@ VALUE *RARRAY_PTR_IMPL(VALUE array) { return (VALUE *) polyglot_as_i64_array(RUBY_CEXT_INVOKE_NO_WRAP("RARRAY_PTR", array)); } -VALUE rb_ary_new() { +VALUE rb_ary_new(void) { return RUBY_CEXT_INVOKE("rb_ary_new"); } @@ -40,12 +40,12 @@ VALUE rb_ary_new_capa(long capacity) { return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_ary_new_capa", capacity)); } -VALUE rb_ary_new_from_args(long n, ...) { - va_list args; - va_start(args, n); - VALUE array = rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_ary_new_from_values", &args)); - va_end(args); - return array; +VALUE rb_tr_ary_new_from_args_va_list(long n, va_list args) { + VALUE values[n]; + for (int i = 0; i < n; i++) { + values[i] = va_arg(args, VALUE); + } + return rb_ary_new_from_values(n, values); } VALUE rb_ary_new_from_values(long n, const VALUE *values) { @@ -152,7 +152,8 @@ VALUE rb_ary_rotate(VALUE array, long n) { return Qnil; } -VALUE rb_ary_tmp_new(long capa) { +// NOTE: #define rb_ary_tmp_new rb_ary_hidden_new in array.h +VALUE rb_ary_hidden_new(long capa) { return rb_ary_new_capa(capa); } diff --git a/src/main/c/cext/call.c b/src/main/c/cext/call.c index 21529ae7cb29..fe2394ffaf7a 100644 --- a/src/main/c/cext/call.c +++ b/src/main/c/cext/call.c @@ -11,7 +11,8 @@ // Calling Ruby methods and blocks from C -NORETURN(void rb_error_arity(int argc, int min, int max)) { +RBIMPL_ATTR_NORETURN() +void rb_error_arity(int argc, int min, int max) { rb_exc_raise(rb_exc_new3(rb_eArgError, rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_arity_error_string", argc, min, max)))); } @@ -126,14 +127,8 @@ VALUE rb_yield_splat(VALUE values) { } } -VALUE rb_yield_values(int n, ...) { - VALUE values = rb_ary_new_capa(n); - va_list args; - va_start(args, n); - for (int i = 0; i < n; i++) { - rb_ary_store(values, i, (VALUE) polyglot_get_array_element(&args, i)); - } - va_end(args); +VALUE rb_tr_yield_values_va_list(int n, va_list args) { + VALUE values = rb_tr_ary_new_from_args_va_list(n, args); return rb_yield_splat(values); } @@ -171,6 +166,7 @@ static void call_unblock_function(void *data) { void* rb_thread_call_without_gvl(gvl_call *function, void *data1, rb_unblock_function_t *unblock_function, void *data2) { // wrap functions to handle native functions + // TODO is it needed if we .bind() in Ruby code? struct gvl_call_data call_struct = { function, data1 }; struct unblock_function_data unblock_struct = { unblock_function, data2 }; @@ -210,7 +206,7 @@ void rb_iter_break(void) { void rb_iter_break_value(VALUE value) { RUBY_CEXT_INVOKE_NO_WRAP("rb_iter_break_value", value); - rb_tr_error("rb_iter_break_value should not return"); + UNREACHABLE; } const char *rb_sourcefile(void) { diff --git a/src/main/c/cext/cext_constants.c b/src/main/c/cext/cext_constants.c index c2fb25c4c39d..db81bd5f652b 100644 --- a/src/main/c/cext/cext_constants.c +++ b/src/main/c/cext/cext_constants.c @@ -8,7 +8,7 @@ * GNU Lesser General Public License version 2.1. */ -// From ./tool/generate-cext-constants.rb +// From tool/generate-cext-constants.rb #include @@ -89,83 +89,85 @@ VALUE rb_mWaitReadable; VALUE rb_mWaitWritable; VALUE rb_eZeroDivError; VALUE rb_eFatal; +VALUE rb_argv0; -void rb_tr_init_global_constants(void) { - rb_cArray = RUBY_CEXT_INVOKE("rb_cArray"); - rb_cBasicObject = RUBY_CEXT_INVOKE("rb_cBasicObject"); - rb_cBinding = RUBY_CEXT_INVOKE("rb_cBinding"); - rb_cClass = RUBY_CEXT_INVOKE("rb_cClass"); - rb_mComparable = RUBY_CEXT_INVOKE("rb_mComparable"); - rb_cComplex = RUBY_CEXT_INVOKE("rb_cComplex"); - rb_cDir = RUBY_CEXT_INVOKE("rb_cDir"); - rb_cEncoding = RUBY_CEXT_INVOKE("rb_cEncoding"); - rb_mEnumerable = RUBY_CEXT_INVOKE("rb_mEnumerable"); - rb_cEnumerator = RUBY_CEXT_INVOKE("rb_cEnumerator"); - rb_cFalseClass = RUBY_CEXT_INVOKE("rb_cFalseClass"); - rb_cFile = RUBY_CEXT_INVOKE("rb_cFile"); - rb_mFileTest = RUBY_CEXT_INVOKE("rb_mFileTest"); - rb_cStat = RUBY_CEXT_INVOKE("rb_cStat"); - rb_cFloat = RUBY_CEXT_INVOKE("rb_cFloat"); - rb_mGC = RUBY_CEXT_INVOKE("rb_mGC"); - rb_cHash = RUBY_CEXT_INVOKE("rb_cHash"); - rb_cInteger = RUBY_CEXT_INVOKE("rb_cInteger"); - rb_cIO = RUBY_CEXT_INVOKE("rb_cIO"); - rb_mKernel = RUBY_CEXT_INVOKE("rb_mKernel"); - rb_cMatch = RUBY_CEXT_INVOKE("rb_cMatch"); - rb_mMath = RUBY_CEXT_INVOKE("rb_mMath"); - rb_cMethod = RUBY_CEXT_INVOKE("rb_cMethod"); - rb_cModule = RUBY_CEXT_INVOKE("rb_cModule"); - rb_cNilClass = RUBY_CEXT_INVOKE("rb_cNilClass"); - rb_cNumeric = RUBY_CEXT_INVOKE("rb_cNumeric"); - rb_cObject = RUBY_CEXT_INVOKE("rb_cObject"); - rb_cProc = RUBY_CEXT_INVOKE("rb_cProc"); - rb_mProcess = RUBY_CEXT_INVOKE("rb_mProcess"); - rb_cRandom = RUBY_CEXT_INVOKE("rb_cRandom"); - rb_cRange = RUBY_CEXT_INVOKE("rb_cRange"); - rb_cRational = RUBY_CEXT_INVOKE("rb_cRational"); - rb_cRegexp = RUBY_CEXT_INVOKE("rb_cRegexp"); - rb_cString = RUBY_CEXT_INVOKE("rb_cString"); - rb_cStruct = RUBY_CEXT_INVOKE("rb_cStruct"); - rb_cSymbol = RUBY_CEXT_INVOKE("rb_cSymbol"); - rb_cTime = RUBY_CEXT_INVOKE("rb_cTime"); - rb_cThread = RUBY_CEXT_INVOKE("rb_cThread"); - rb_cTrueClass = RUBY_CEXT_INVOKE("rb_cTrueClass"); - rb_cUnboundMethod = RUBY_CEXT_INVOKE("rb_cUnboundMethod"); - rb_eArgError = RUBY_CEXT_INVOKE("rb_eArgError"); - rb_eEncodingError = RUBY_CEXT_INVOKE("rb_eEncodingError"); - rb_eEOFError = RUBY_CEXT_INVOKE("rb_eEOFError"); - rb_mErrno = RUBY_CEXT_INVOKE("rb_mErrno"); - rb_eException = RUBY_CEXT_INVOKE("rb_eException"); - rb_eFloatDomainError = RUBY_CEXT_INVOKE("rb_eFloatDomainError"); - rb_eFrozenError = RUBY_CEXT_INVOKE("rb_eFrozenError"); - rb_eIndexError = RUBY_CEXT_INVOKE("rb_eIndexError"); - rb_eInterrupt = RUBY_CEXT_INVOKE("rb_eInterrupt"); - rb_eIOError = RUBY_CEXT_INVOKE("rb_eIOError"); - rb_eKeyError = RUBY_CEXT_INVOKE("rb_eKeyError"); - rb_eLoadError = RUBY_CEXT_INVOKE("rb_eLoadError"); - rb_eLocalJumpError = RUBY_CEXT_INVOKE("rb_eLocalJumpError"); - rb_eMathDomainError = RUBY_CEXT_INVOKE("rb_eMathDomainError"); - rb_eEncCompatError = RUBY_CEXT_INVOKE("rb_eEncCompatError"); - rb_eNameError = RUBY_CEXT_INVOKE("rb_eNameError"); - rb_eNoMemError = RUBY_CEXT_INVOKE("rb_eNoMemError"); - rb_eNoMethodError = RUBY_CEXT_INVOKE("rb_eNoMethodError"); - rb_eNotImpError = RUBY_CEXT_INVOKE("rb_eNotImpError"); - rb_eRangeError = RUBY_CEXT_INVOKE("rb_eRangeError"); - rb_eRegexpError = RUBY_CEXT_INVOKE("rb_eRegexpError"); - rb_eRuntimeError = RUBY_CEXT_INVOKE("rb_eRuntimeError"); - rb_eScriptError = RUBY_CEXT_INVOKE("rb_eScriptError"); - rb_eSecurityError = RUBY_CEXT_INVOKE("rb_eSecurityError"); - rb_eSignal = RUBY_CEXT_INVOKE("rb_eSignal"); - rb_eStandardError = RUBY_CEXT_INVOKE("rb_eStandardError"); - rb_eStopIteration = RUBY_CEXT_INVOKE("rb_eStopIteration"); - rb_eSyntaxError = RUBY_CEXT_INVOKE("rb_eSyntaxError"); - rb_eSystemCallError = RUBY_CEXT_INVOKE("rb_eSystemCallError"); - rb_eSystemExit = RUBY_CEXT_INVOKE("rb_eSystemExit"); - rb_eSysStackError = RUBY_CEXT_INVOKE("rb_eSysStackError"); - rb_eTypeError = RUBY_CEXT_INVOKE("rb_eTypeError"); - rb_eThreadError = RUBY_CEXT_INVOKE("rb_eThreadError"); - rb_mWaitReadable = RUBY_CEXT_INVOKE("rb_mWaitReadable"); - rb_mWaitWritable = RUBY_CEXT_INVOKE("rb_mWaitWritable"); - rb_eZeroDivError = RUBY_CEXT_INVOKE("rb_eZeroDivError"); - rb_eFatal = RUBY_CEXT_INVOKE("rb_eFatal"); +void rb_tr_init_global_constants(VALUE (*get_constant)(const char*)) { + rb_cArray = get_constant("rb_cArray"); + rb_cBasicObject = get_constant("rb_cBasicObject"); + rb_cBinding = get_constant("rb_cBinding"); + rb_cClass = get_constant("rb_cClass"); + rb_mComparable = get_constant("rb_mComparable"); + rb_cComplex = get_constant("rb_cComplex"); + rb_cDir = get_constant("rb_cDir"); + rb_cEncoding = get_constant("rb_cEncoding"); + rb_mEnumerable = get_constant("rb_mEnumerable"); + rb_cEnumerator = get_constant("rb_cEnumerator"); + rb_cFalseClass = get_constant("rb_cFalseClass"); + rb_cFile = get_constant("rb_cFile"); + rb_mFileTest = get_constant("rb_mFileTest"); + rb_cStat = get_constant("rb_cStat"); + rb_cFloat = get_constant("rb_cFloat"); + rb_mGC = get_constant("rb_mGC"); + rb_cHash = get_constant("rb_cHash"); + rb_cInteger = get_constant("rb_cInteger"); + rb_cIO = get_constant("rb_cIO"); + rb_mKernel = get_constant("rb_mKernel"); + rb_cMatch = get_constant("rb_cMatch"); + rb_mMath = get_constant("rb_mMath"); + rb_cMethod = get_constant("rb_cMethod"); + rb_cModule = get_constant("rb_cModule"); + rb_cNilClass = get_constant("rb_cNilClass"); + rb_cNumeric = get_constant("rb_cNumeric"); + rb_cObject = get_constant("rb_cObject"); + rb_cProc = get_constant("rb_cProc"); + rb_mProcess = get_constant("rb_mProcess"); + rb_cRandom = get_constant("rb_cRandom"); + rb_cRange = get_constant("rb_cRange"); + rb_cRational = get_constant("rb_cRational"); + rb_cRegexp = get_constant("rb_cRegexp"); + rb_cString = get_constant("rb_cString"); + rb_cStruct = get_constant("rb_cStruct"); + rb_cSymbol = get_constant("rb_cSymbol"); + rb_cTime = get_constant("rb_cTime"); + rb_cThread = get_constant("rb_cThread"); + rb_cTrueClass = get_constant("rb_cTrueClass"); + rb_cUnboundMethod = get_constant("rb_cUnboundMethod"); + rb_eArgError = get_constant("rb_eArgError"); + rb_eEncodingError = get_constant("rb_eEncodingError"); + rb_eEOFError = get_constant("rb_eEOFError"); + rb_mErrno = get_constant("rb_mErrno"); + rb_eException = get_constant("rb_eException"); + rb_eFloatDomainError = get_constant("rb_eFloatDomainError"); + rb_eFrozenError = get_constant("rb_eFrozenError"); + rb_eIndexError = get_constant("rb_eIndexError"); + rb_eInterrupt = get_constant("rb_eInterrupt"); + rb_eIOError = get_constant("rb_eIOError"); + rb_eKeyError = get_constant("rb_eKeyError"); + rb_eLoadError = get_constant("rb_eLoadError"); + rb_eLocalJumpError = get_constant("rb_eLocalJumpError"); + rb_eMathDomainError = get_constant("rb_eMathDomainError"); + rb_eEncCompatError = get_constant("rb_eEncCompatError"); + rb_eNameError = get_constant("rb_eNameError"); + rb_eNoMemError = get_constant("rb_eNoMemError"); + rb_eNoMethodError = get_constant("rb_eNoMethodError"); + rb_eNotImpError = get_constant("rb_eNotImpError"); + rb_eRangeError = get_constant("rb_eRangeError"); + rb_eRegexpError = get_constant("rb_eRegexpError"); + rb_eRuntimeError = get_constant("rb_eRuntimeError"); + rb_eScriptError = get_constant("rb_eScriptError"); + rb_eSecurityError = get_constant("rb_eSecurityError"); + rb_eSignal = get_constant("rb_eSignal"); + rb_eStandardError = get_constant("rb_eStandardError"); + rb_eStopIteration = get_constant("rb_eStopIteration"); + rb_eSyntaxError = get_constant("rb_eSyntaxError"); + rb_eSystemCallError = get_constant("rb_eSystemCallError"); + rb_eSystemExit = get_constant("rb_eSystemExit"); + rb_eSysStackError = get_constant("rb_eSysStackError"); + rb_eTypeError = get_constant("rb_eTypeError"); + rb_eThreadError = get_constant("rb_eThreadError"); + rb_mWaitReadable = get_constant("rb_mWaitReadable"); + rb_mWaitWritable = get_constant("rb_mWaitWritable"); + rb_eZeroDivError = get_constant("rb_eZeroDivError"); + rb_eFatal = get_constant("rb_eFatal"); + rb_argv0 = get_constant("rb_argv0"); } diff --git a/src/main/c/cext/data.c b/src/main/c/cext/data.c index b53eb1ce311f..c16c2677e140 100644 --- a/src/main/c/cext/data.c +++ b/src/main/c/cext/data.c @@ -11,14 +11,104 @@ // RData and RTypedData, rb_data_*, rb_typeddata_* -static RUBY_DATA_FUNC rb_tr_free_function(RUBY_DATA_FUNC dfree) { - return (dfree == (RUBY_DATA_FUNC)RUBY_DEFAULT_FREE) ? ruby_xfree : dfree; +// struct RData + +struct RData* rb_tr_rdata_create(RUBY_DATA_FUNC dmark, RUBY_DATA_FUNC dfree, void *data) { + struct RData* result = calloc(1, sizeof(struct RData)); + result->dmark = dmark; + result->dfree = dfree; + result->data = data; + return result; +} + +struct RTypedData* rb_tr_rtypeddata_create(const rb_data_type_t *data_type, void *data) { + struct RTypedData* result = calloc(1, sizeof(struct RTypedData)); + result->type = data_type; + result->typed_flag = 1; + result->data = data; + return result; +} + +void rb_tr_rdata_run_marker(struct RData* rdata) { + void* data = rdata->data; + RUBY_DATA_FUNC dmark = rdata->dmark; + if (data != NULL && dmark != NULL) { + dmark(data); + } +} + +void rb_tr_rtypeddata_run_marker(struct RTypedData* rtypeddata) { + void* data = rtypeddata->data; + RUBY_DATA_FUNC dmark = rtypeddata->type->function.dmark; + if (data != NULL && dmark != NULL) { + dmark(data); + } +} + +size_t rb_tr_rtypeddata_run_memsizer(struct RTypedData* rtypeddata) { + void* data = rtypeddata->data; + size_t (*dsize)(const void *) = rtypeddata->type->function.dsize; + if (data != NULL && dsize != NULL) { + return dsize(data); + } else { + return 0; + } +} + +void rb_tr_rdata_run_finalizer(struct RData* rdata) { + void* data = rdata->data; + RUBY_DATA_FUNC dfree = rdata->dfree; + if (data != NULL) { + if (dfree == RUBY_DEFAULT_FREE) { + ruby_xfree(data); + } else if (dfree != NULL) { + dfree(data); + } + } + // Also free the struct RData + free(rdata); +} + +void rb_tr_rtypeddata_run_finalizer(struct RTypedData* rtypeddata) { + void* data = rtypeddata->data; + RUBY_DATA_FUNC dfree = rtypeddata->type->function.dfree; + if (data != NULL) { + if (dfree == RUBY_DEFAULT_FREE) { + ruby_xfree(data); + } else if (dfree != NULL) { + dfree(data); + } + } // Also free the struct RTypedData + free(rtypeddata); +} + +struct RData* rb_tr_rdata(VALUE object) { + struct RData* rdata = polyglot_invoke(RUBY_CEXT, "RDATA", rb_tr_unwrap(object)); + if (rdata->dmark) { + polyglot_invoke(RUBY_CEXT, "mark_object_on_call_exit", rb_tr_unwrap(object)); + } + return rdata; +} + +struct RTypedData* rb_tr_rtypeddata(VALUE object) { + struct RTypedData* rtypeddata = polyglot_invoke(RUBY_CEXT, "RTYPEDDATA", rb_tr_unwrap(object)); + if (rtypeddata->type->function.dmark) { + polyglot_invoke(RUBY_CEXT, "mark_object_on_call_exit", rb_tr_unwrap(object)); + } + return rtypeddata; +} + +bool rb_tr_rtypeddata_p(VALUE obj) { + return polyglot_as_boolean(RUBY_CEXT_INVOKE_NO_WRAP("RTYPEDDATA_P", obj)); } #undef rb_data_object_wrap VALUE rb_data_object_wrap(VALUE klass, void *data, RUBY_DATA_FUNC dmark, RUBY_DATA_FUNC dfree) { return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_data_object_wrap", - rb_tr_unwrap(klass), data, dmark, rb_tr_free_function(dfree) )); + rb_tr_unwrap(klass), + data, + dmark, + dfree)); } VALUE rb_data_object_zalloc(VALUE klass, size_t size, RUBY_DATA_FUNC dmark, RUBY_DATA_FUNC dfree) { @@ -30,7 +120,12 @@ VALUE rb_data_object_zalloc(VALUE klass, size_t size, RUBY_DATA_FUNC dmark, RUBY VALUE rb_data_typed_object_wrap(VALUE ruby_class, void *data, const rb_data_type_t *data_type) { return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_data_typed_object_wrap", - rb_tr_unwrap(ruby_class), data, data_type, data_type->function.dmark, rb_tr_free_function(data_type->function.dfree), data_type->function.dsize)); + rb_tr_unwrap(ruby_class), + data, + data_type, + data_type->function.dmark, + data_type->function.dfree, + data_type->function.dsize)); } VALUE rb_data_typed_object_zalloc(VALUE ruby_class, size_t size, const rb_data_type_t *data_type) { @@ -46,7 +141,7 @@ VALUE rb_data_typed_object_make(VALUE ruby_class, const rb_data_type_t *type, vo void *rb_check_typeddata(VALUE value, const rb_data_type_t *data_type) { struct RTypedData* typed_data = RTYPEDDATA(value); // NOTE: this function is used on every access to typed data so it should remain fast. - // RB_TYPE_P(value, T_DATA) is already checked by `RTYPEDDATA(value)`, see Truffle::CExt.RDATA(). + // RB_TYPE_P(value, T_DATA) is already checked by `RTYPEDDATA(value)`, see Truffle::CExt.RTYPEDDATA(). // RTYPEDDATA_P(value) is already checked implicitly by `typed_data->type` which would return `nil` if not `RTYPEDDATA_P`. if (!rb_typeddata_inherited_p(typed_data->type, data_type)) { rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected %s)", rb_obj_class(value), data_type->wrap_struct_name); diff --git a/src/main/c/cext/define.c b/src/main/c/cext/define.c index 12d2f617d0e2..d6cc8a1bdd86 100644 --- a/src/main/c/cext/define.c +++ b/src/main/c/cext/define.c @@ -12,7 +12,8 @@ // Defining classes, modules and methods, rb_define_* VALUE rb_f_notimplement(int argc, const VALUE *argv, VALUE obj, VALUE marker) { - rb_tr_error("rb_f_notimplement"); + RUBY_CEXT_INVOKE("rb_f_notimplement"); + UNREACHABLE; } VALUE rb_define_class(const char *name, VALUE superclass) { @@ -41,7 +42,7 @@ void rb_include_module(VALUE module, VALUE to_include) { #undef rb_define_method void rb_define_method(VALUE module, const char *name, VALUE (*function)(ANYARGS), int argc) { - if (function == rb_f_notimplement) { + if (function == rb_tr_rb_f_notimplement) { RUBY_CEXT_INVOKE("rb_define_method_undefined", module, rb_str_new_cstr(name)); } else { rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_define_method", rb_tr_unwrap(module), rb_tr_unwrap(rb_str_new_cstr(name)), function, argc)); diff --git a/src/main/c/cext/encoding.c b/src/main/c/cext/encoding.c index b8f5216e2668..1475cab0e656 100644 --- a/src/main/c/cext/encoding.c +++ b/src/main/c/cext/encoding.c @@ -111,6 +111,11 @@ void rb_enc_set_index(VALUE obj, int idx) { polyglot_invoke(RUBY_CEXT, "rb_enc_set_index", rb_tr_unwrap(obj), idx); } +int rb_enc_alias(const char *alias, const char *orig) { + VALUE enc = RUBY_CEXT_INVOKE("rb_enc_alias", rb_str_new_cstr(alias), rb_str_new_cstr(orig)); + return rb_enc_get_index(enc); +} + rb_encoding* rb_ascii8bit_encoding(void) { return rb_to_encoding(RUBY_CEXT_INVOKE("ascii8bit_encoding")); } @@ -366,14 +371,6 @@ VALUE rb_enc_str_new_static(const char *ptr, long len, rb_encoding *enc) { return string; } -void rb_enc_raise(rb_encoding *enc, VALUE exc, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - VALUE mesg = rb_enc_vsprintf(enc, fmt, args); - va_end(args); - rb_exc_raise(rb_exc_new_str(exc, mesg)); -} - #define castchar(from) (char)((from) & 0xff) int rb_uv_to_utf8(char buf[6], unsigned long uv) { @@ -471,7 +468,7 @@ static int char_to_option(int c) { return val; } -extern int rb_char_to_option_kcode(int c, int *option, int *kcode) { +int rb_char_to_option_kcode(int c, int *option, int *kcode) { *option = 0; switch (c) { diff --git a/src/main/c/cext/exception.c b/src/main/c/cext/exception.c index cd58cac2696c..27f4cc7e7b55 100644 --- a/src/main/c/cext/exception.c +++ b/src/main/c/cext/exception.c @@ -27,7 +27,11 @@ VALUE rb_exc_new_str(VALUE exception_class, VALUE message) { void rb_exc_raise(VALUE exception) { RUBY_CEXT_INVOKE_NO_WRAP("rb_exc_raise", exception); - rb_tr_error("rb_exc_raise should not return"); + UNREACHABLE; +} + +void rb_exc_set_message(VALUE exc, VALUE message) { + RUBY_CEXT_INVOKE_NO_WRAP("rb_exc_set_message", exc, message); } static void rb_protect_write_status(int *status, int value) { @@ -46,7 +50,7 @@ void rb_jump_tag(int status) { if (status) { polyglot_invoke(RUBY_CEXT, "rb_jump_tag", status); } - rb_tr_error("rb_jump_tag should not return"); + UNREACHABLE; } void rb_set_errinfo(VALUE error) { @@ -59,7 +63,7 @@ VALUE rb_errinfo(void) { void rb_syserr_fail(int eno, const char *message) { polyglot_invoke(RUBY_CEXT, "rb_syserr_fail", eno, rb_tr_unwrap(rb_str_new_cstr(message == NULL ? "" : message))); - rb_tr_error("rb_syserr_fail should not return"); + UNREACHABLE; } void rb_sys_fail(const char *message) { @@ -99,32 +103,24 @@ void rb_sys_fail_str(VALUE mesg) { VALUE (*cext_rb_ensure)(VALUE (*b_proc)(VALUE), void* data1, VALUE (*e_proc)(VALUE), void* data2); -VALUE rb_ensure(VALUE (*b_proc)(ANYARGS), VALUE data1, VALUE (*e_proc)(ANYARGS), VALUE data2) { +VALUE rb_ensure(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*e_proc)(VALUE), VALUE data2) { return cext_rb_ensure(b_proc, data1, e_proc, data2); } VALUE (*cext_rb_rescue)(VALUE (*b_proc)(VALUE data), void* data1, VALUE (*r_proc)(VALUE data, VALUE e), void* data2); -VALUE rb_rescue(VALUE (*b_proc)(ANYARGS), VALUE data1, VALUE (*r_proc)(ANYARGS), VALUE data2) { +VALUE rb_rescue(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*r_proc)(VALUE, VALUE), VALUE data2) { return cext_rb_rescue(b_proc, data1, r_proc, data2); } VALUE (*cext_rb_rescue2)(VALUE (*b_proc)(VALUE data), void* data1, VALUE (*r_proc)(VALUE data, VALUE e), void* data2, void* rescued); -VALUE rb_rescue2(VALUE (*b_proc)(ANYARGS), VALUE data1, VALUE (*r_proc)(ANYARGS), VALUE data2, ...) { +VALUE rb_tr_rescue2_va_list(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*r_proc)(VALUE, VALUE), VALUE data2, va_list args) { VALUE rescued = rb_ary_new(); - va_list args; - va_start(args, data2); - int total = polyglot_get_array_size(&args); - for (int n = 0; n < total; n++) { - VALUE arg = polyglot_get_array_element(&args, n); - if (arg == (VALUE)0) { /* A 0 marks the end of the arguments so we break here rather than adding it to the array.*/ - break; - } - + VALUE arg; + while ((arg = va_arg(args, VALUE)) != (VALUE)0) { rb_ary_push(rescued, arg); } - va_end(args); return cext_rb_rescue2(b_proc, data1, r_proc, data2, rb_tr_unwrap(rescued)); } @@ -138,28 +134,33 @@ void rb_throw(const char *tag, VALUE val) { void rb_throw_obj(VALUE tag, VALUE value) { RUBY_INVOKE_NO_WRAP(rb_mKernel, "throw", tag, value ? value : Qnil); - rb_tr_error("rb_throw_obj should not return"); + UNREACHABLE; } VALUE rb_catch(const char *tag, VALUE (*func)(ANYARGS), VALUE data) { return rb_catch_obj(rb_intern(tag), func, data); } -VALUE rb_catch_obj(VALUE t, VALUE (*func)(ANYARGS), VALUE data) { - return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_catch_obj", rb_tr_unwrap(t), func, rb_tr_unwrap(data))); +VALUE rb_catch_obj(VALUE tag, rb_block_call_func_t func, VALUE data) { + return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_catch_obj", rb_tr_unwrap(tag), func, rb_tr_unwrap(data))); } void rb_memerror(void) { RUBY_CEXT_INVOKE_NO_WRAP("rb_memerror"); - rb_tr_error("rb_memerror should not return"); + UNREACHABLE; } -NORETURN(void rb_eof_error(void)) { +RBIMPL_ATTR_NORETURN() +void rb_eof_error(void) { rb_raise(rb_eEOFError, "end of file reached"); } -void rb_bug(const char *fmt, ...) { - rb_tr_error("rb_bug not yet implemented"); +void rb_tr_bug_va_list(const char *fmt, va_list args) { + rb_tr_not_implemented("rb_bug"); +} + +void rb_tr_fatal_va_list(const char *fmt, va_list args) { + rb_tr_not_implemented("rb_fatal"); } VALUE rb_make_exception(int argc, const VALUE *argv) { diff --git a/src/main/c/cext/extconf.rb b/src/main/c/cext/extconf.rb index cb2d2df191d8..065a0c8292f6 100644 --- a/src/main/c/cext/extconf.rb +++ b/src/main/c/cext/extconf.rb @@ -27,12 +27,15 @@ module Truffle::Platform # -DRUBY_EXPORT is added in MRI's configure.in. $CFLAGS << " -DRUBY_EXPORT" -# Add internal files in include path, st.c needs some of them +# Add internal files in include path, lib/cext/include/internal_all.h needs them $INCFLAGS << ' -I$(top_srcdir)' +# libtruffleruby is executed on Sulong +$LIBS << ' -lgraalvm-llvm' + # libruby depends on librt on Linux, and C extensions like date rely on that because they then # automatically depend on librt (e.g., for clock_gettime). -$LIBS << '-lrt' if Truffle::Platform.linux? +$LIBS << ' -lrt' if Truffle::Platform.linux? if Truffle::Platform.darwin? # Set the install_name of libtruffleruby on macOS, so mkmf executables linking to it diff --git a/src/main/c/cext/fd.c b/src/main/c/cext/fd.c index f215ab9e6017..cb0de41cc9aa 100644 --- a/src/main/c/cext/fd.c +++ b/src/main/c/cext/fd.c @@ -45,8 +45,7 @@ void rb_fd_zero(rb_fdset_t *fds) { } } -static void -rb_fd_resize(int n, rb_fdset_t *fds) { +static void rb_fd_resize(int n, rb_fdset_t *fds) { size_t m = howmany(n + 1, NFDBITS) * sizeof(fd_mask); size_t o = howmany(fds->maxfd, NFDBITS) * sizeof(fd_mask); diff --git a/src/main/c/cext/fiber.c b/src/main/c/cext/fiber.c index cc3ecbf00d04..446dbbd59b03 100644 --- a/src/main/c/cext/fiber.c +++ b/src/main/c/cext/fiber.c @@ -11,12 +11,12 @@ // Fiber, rb_fiber_* -VALUE rb_fiber_current() { +VALUE rb_fiber_current(void) { return RUBY_CEXT_INVOKE("rb_fiber_current"); } VALUE rb_fiber_alive_p(VALUE fiber) { - return rb_funcallv(fiber, rb_intern("alive?"), 0, 0); + return RUBY_INVOKE(fiber, "alive?"); } VALUE rb_fiber_resume(VALUE fib, int argc, const VALUE *argv) { @@ -36,6 +36,10 @@ VALUE rb_fiber_yield_kw(int argc, const VALUE *argv, int kw_splat) { return rb_funcallv_kw(rb_cFiber, rb_intern("yield"), argc, argv, kw_splat); } -VALUE rb_fiber_new(VALUE (*function)(ANYARGS), VALUE value) { +VALUE rb_fiber_new(rb_block_call_func_t function, VALUE value) { return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_fiber_new", function, rb_tr_unwrap(value))); } + +VALUE rb_fiber_raise(VALUE fiber, int argc, const VALUE *argv) { + return rb_funcallv(fiber, rb_intern("raise"), argc, argv); +} diff --git a/src/main/c/cext/flags.c b/src/main/c/cext/flags.c new file mode 100644 index 000000000000..2ad9afc7201b --- /dev/null +++ b/src/main/c/cext/flags.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. This + * code is released under a tri EPL/GPL/LGPL license. You can use it, + * redistribute it and/or modify it under the terms of the: + * + * Eclipse Public License version 2.0, or + * GNU General Public License version 2, or + * GNU Lesser General Public License version 2.1. + */ +#include + +// struct RBasic flags, RB_FL_* + +unsigned long rb_tr_flags(VALUE object) { + return polyglot_as_i32(RUBY_CEXT_INVOKE_NO_WRAP("rb_tr_flags", object)); +} + +void rb_tr_set_flags(VALUE object, unsigned long flags) { + polyglot_invoke(RUBY_CEXT, "rb_tr_set_flags", rb_tr_unwrap(object), flags); +} diff --git a/src/main/c/cext/float.c b/src/main/c/cext/float.c index c7fd4122ce27..0f6b8f2cde1f 100644 --- a/src/main/c/cext/float.c +++ b/src/main/c/cext/float.c @@ -12,6 +12,10 @@ // Float, rb_float_* +bool rb_tr_float_type_p(VALUE obj) { + return polyglot_as_boolean(RUBY_CEXT_INVOKE_NO_WRAP("RB_FLOAT_TYPE_P", obj)); +} + #undef rb_float_new VALUE rb_float_new(double value) { return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_float_new", value)); diff --git a/src/main/c/cext/gc.c b/src/main/c/cext/gc.c index 74e83c63dbae..92e16f3120ac 100644 --- a/src/main/c/cext/gc.c +++ b/src/main/c/cext/gc.c @@ -44,11 +44,11 @@ void rb_gc_mark_maybe(VALUE ptr) { } } -VALUE rb_gc_enable() { +VALUE rb_gc_enable(void) { return RUBY_CEXT_INVOKE("rb_gc_enable"); } -VALUE rb_gc_disable() { +VALUE rb_gc_disable(void) { return RUBY_CEXT_INVOKE("rb_gc_disable"); } @@ -72,3 +72,7 @@ void* rb_tr_read_VALUE_pointer(VALUE *pointer) { VALUE value = *pointer; return rb_tr_unwrap(value); } + +int rb_during_gc(void) { + return 0; +} diff --git a/src/main/c/cext/globals.c b/src/main/c/cext/globals.c index 55c55c773b93..28158f0cd77d 100644 --- a/src/main/c/cext/globals.c +++ b/src/main/c/cext/globals.c @@ -28,7 +28,8 @@ void rb_gvar_var_setter(VALUE val, ID id, VALUE *data) { *data = val; } -void rb_define_hooked_variable(const char *name, VALUE *var, VALUE (*getter)(ANYARGS), void (*setter)(ANYARGS)) { +void rb_define_hooked_variable(const char *name, VALUE *var, rb_gvar_getter_t *getter, rb_gvar_setter_t *setter) { +// void rb_define_hooked_variable(const char *name, VALUE *var, VALUE (*getter)(ANYARGS), void (*setter)(ANYARGS)) { if (!getter) { getter = rb_gvar_var_getter; } @@ -99,3 +100,38 @@ VALUE *rb_ruby_debug_ptr(void) { rb_tr_ruby_debug_ptr = RUBY_CEXT_INVOKE("rb_ruby_debug_ptr"); return &rb_tr_ruby_debug_ptr; } + +// These refer Ruby global variables and their value can change, +// so we use macros instead of C global variables like MRI, which would be complicated to update. + +VALUE rb_tr_stdin(void) { + return RUBY_CEXT_INVOKE("rb_stdin"); +} + +VALUE rb_tr_stdout(void) { + return RUBY_CEXT_INVOKE("rb_stdout"); +} + +VALUE rb_tr_stderr(void) { + return RUBY_CEXT_INVOKE("rb_stderr"); +} + +VALUE rb_tr_fs(void) { + return RUBY_CEXT_INVOKE("rb_fs"); +} + +VALUE rb_tr_output_fs(void) { + return RUBY_CEXT_INVOKE("rb_output_fs"); +} + +VALUE rb_tr_rs(void) { + return RUBY_CEXT_INVOKE("rb_rs"); +} + +VALUE rb_tr_output_rs(void) { + return RUBY_CEXT_INVOKE("rb_output_rs"); +} + +VALUE rb_tr_default_rs(void) { + return RUBY_CEXT_INVOKE("rb_default_rs"); +} diff --git a/src/main/c/cext/hash.c b/src/main/c/cext/hash.c index 4ce273fe74d3..837b1b6939eb 100644 --- a/src/main/c/cext/hash.c +++ b/src/main/c/cext/hash.c @@ -20,7 +20,7 @@ VALUE rb_hash(VALUE obj) { return RUBY_CEXT_INVOKE("rb_hash", obj); } -VALUE rb_hash_new() { +VALUE rb_hash_new(void) { return RUBY_CEXT_INVOKE("rb_hash_new"); } @@ -28,7 +28,7 @@ VALUE rb_hash_new_capa(long capacity) { return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_hash_new_capa", capacity)); } -VALUE rb_ident_hash_new() { +VALUE rb_ident_hash_new(void) { return RUBY_CEXT_INVOKE("rb_ident_hash_new"); } @@ -68,10 +68,6 @@ VALUE rb_hash_keys(VALUE hash) { return RUBY_INVOKE(hash, "keys"); } -VALUE rb_hash_key_str(VALUE hash) { - rb_tr_error("rb_hash_key_str not yet implemented"); -} - st_index_t rb_memhash(const void *data, long length) { // Not a proper hash - just something that produces a stable result for now @@ -100,8 +96,8 @@ VALUE rb_hash_delete_if(VALUE hash) { } } -void rb_hash_foreach(VALUE hash, int (*func)(ANYARGS), VALUE farg) { - polyglot_invoke(RUBY_CEXT, "rb_hash_foreach", rb_tr_unwrap(hash), func, (void*)farg); +void rb_hash_foreach(VALUE hash, int (*func)(VALUE key, VALUE val, VALUE arg), VALUE arg) { + polyglot_invoke(RUBY_CEXT, "rb_hash_foreach", rb_tr_unwrap(hash), func, (void*)arg); } VALUE rb_hash_size(VALUE hash) { diff --git a/src/main/c/cext/integer.c b/src/main/c/cext/integer.c index 143c25f6bf8d..dbaf5e34724c 100644 --- a/src/main/c/cext/integer.c +++ b/src/main/c/cext/integer.c @@ -12,6 +12,10 @@ // Integer, rb_integer_*, rb_*int*, rb_big_* +bool rb_tr_integer_type_p(VALUE obj) { + return polyglot_as_boolean(RUBY_CEXT_INVOKE_NO_WRAP("rb_integer_type_p", obj)); +} + VALUE rb_Integer(VALUE value) { return RUBY_CEXT_INVOKE("rb_Integer", value); } @@ -116,7 +120,7 @@ VALUE rb_int_positive_pow(long x, unsigned long y) { // Needed to gem install cbor VALUE rb_integer_unpack(const void *words, size_t numwords, size_t wordsize, size_t nails, int flags) { - rb_tr_error("rb_integer_unpack not implemented"); + rb_tr_not_implemented("rb_integer_unpack"); } size_t rb_absint_size(VALUE value, int *nlz_bits_ret) { diff --git a/src/main/c/cext/io.c b/src/main/c/cext/io.c index 028641d52eaa..82e7d84b92b9 100644 --- a/src/main/c/cext/io.c +++ b/src/main/c/cext/io.c @@ -14,14 +14,69 @@ // IO, rb_io_* +typedef struct { + struct RFile rfile; + rb_io_t io_struct; +} RFile_and_rb_io_t; + +static inline int rb_tr_io_raw_descriptor(VALUE io) { + return polyglot_as_i32(RUBY_CEXT_INVOKE_NO_WRAP("rb_io_descriptor", io)); +} + int rb_io_descriptor(VALUE io) { - int fd = polyglot_as_i32(RUBY_CEXT_INVOKE_NO_WRAP("rb_io_descriptor", io)); + int fd = rb_tr_io_raw_descriptor(io); if (fd < 0) { rb_raise(rb_eIOError, "closed stream"); } return fd; } +int rb_io_mode(VALUE io) { + return polyglot_as_i32(RUBY_CEXT_INVOKE_NO_WRAP("rb_io_mode", io)); +} + +VALUE rb_io_path(VALUE io) { + return RUBY_CEXT_INVOKE("rb_io_path", io); +} + +static RFile_and_rb_io_t* get_RFile_and_rb_io_t(VALUE io) { + RFile_and_rb_io_t* pointer = RUBY_CEXT_INVOKE_NO_WRAP("rb_tr_io_pointer", io); + if (!polyglot_is_null(pointer)) { + return pointer; + } + + pointer = polyglot_invoke(RUBY_CEXT, "new_memory_pointer", sizeof(RFile_and_rb_io_t)); + rb_io_t* fptr = &pointer->io_struct; + fptr->self = io; + fptr->fd = rb_tr_io_raw_descriptor(io); + fptr->mode = rb_io_mode(io); + fptr->pathv = rb_io_path(io); + VALUE write_io = rb_io_get_write_io(io); + fptr->tied_io_for_writing = (write_io != io) ? write_io : Qfalse; + + pointer->rfile.fptr = fptr; + + polyglot_invoke(RUBY_CEXT, "rb_tr_io_attach_pointer", rb_tr_unwrap(io), pointer); + + return pointer; +} + +rb_io_t* rb_tr_io_get_rb_io_t(VALUE io) { + return &get_RFile_and_rb_io_t(io)->io_struct; +} + +struct RFile* rb_tr_io_get_rfile(VALUE io) { + return &get_RFile_and_rb_io_t(io)->rfile; +} + +static inline int rb_tr_readable(int mode) { + return mode & FMODE_READABLE; +} + +static inline int rb_tr_writable(int mode) { + return mode & FMODE_WRITABLE; +} + void rb_io_check_writable(rb_io_t *io) { if (!rb_tr_writable(io->mode)) { rb_raise(rb_eIOError, "not opened for writing"); @@ -209,6 +264,10 @@ VALUE rb_io_close(VALUE io) { return RUBY_INVOKE(io, "close"); } +VALUE rb_io_gets(VALUE io) { + return RUBY_INVOKE(io, "gets"); +} + VALUE rb_io_print(int argc, const VALUE *argv, VALUE out) { return RUBY_CEXT_INVOKE("rb_io_print", out, rb_ary_new4(argc, argv)); } @@ -229,6 +288,14 @@ VALUE rb_io_binmode(VALUE io) { return RUBY_INVOKE(io, "binmode"); } +VALUE rb_io_flush(VALUE io) { + return RUBY_INVOKE(io, "flush"); +} + +VALUE rb_io_get_write_io(VALUE io) { + return RUBY_CEXT_INVOKE("rb_io_get_write_io", io); +} + int rb_thread_fd_writable(int fd) { return polyglot_as_i32(polyglot_invoke(RUBY_CEXT, "rb_thread_fd_writable", fd)); } @@ -253,14 +320,6 @@ VALUE rb_get_path(VALUE object) { return RUBY_INVOKE(rb_cFile, "path", object); } -int rb_tr_readable(int mode) { - return mode & FMODE_READABLE; -} - -int rb_tr_writable(int mode) { - return mode & FMODE_WRITABLE; -} - int rb_io_extract_encoding_option(VALUE opt, rb_encoding **enc_p, rb_encoding **enc2_p, int *fmode_p) { // TODO (pitr-ch 12-Jun-2017): review, just approximate implementation VALUE encoding = rb_cEncoding; @@ -293,7 +352,7 @@ void rb_io_set_nonblock(rb_io_t *fptr) { // For 'gem install curb' FILE *rb_io_stdio_file(rb_io_t *fptr) { - rb_tr_error("rb_io_stdio_file not yet implemented"); + rb_tr_not_implemented("rb_io_stdio_file"); } VALUE rb_lastline_get(void) { @@ -304,3 +363,6 @@ void rb_lastline_set(VALUE str) { RUBY_CEXT_INVOKE_NO_WRAP("rb_lastline_set", str); } +VALUE rb_io_getbyte(VALUE io) { + return RUBY_INVOKE(io, "getbyte"); +} diff --git a/src/main/c/cext/ivar.c b/src/main/c/cext/ivar.c index 34d0e85677be..67f645ef9cbd 100644 --- a/src/main/c/cext/ivar.c +++ b/src/main/c/cext/ivar.c @@ -49,7 +49,7 @@ VALUE rb_ivar_lookup(VALUE object, const char *name, VALUE default_value) { } // Needed to gem install oj -void rb_ivar_foreach(VALUE obj, int (*func)(ANYARGS), st_data_t arg) { +void rb_ivar_foreach(VALUE obj, int (*func)(ID name, VALUE val, st_data_t arg), st_data_t arg) { polyglot_invoke(RUBY_CEXT, "rb_ivar_foreach", rb_tr_unwrap(obj), func, (void*)arg); } diff --git a/src/main/c/cext/numeric.c b/src/main/c/cext/numeric.c index bf56a2956c96..cb02ae431a38 100644 --- a/src/main/c/cext/numeric.c +++ b/src/main/c/cext/numeric.c @@ -172,6 +172,10 @@ VALUE rb_uint2inum(uintptr_t n) { return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_ulong2num", n)); } +VALUE rb_uint2big(uintptr_t i) { + return rb_uint2inum(i); +} + VALUE rb_ll2inum(LONG_LONG n) { /* Long and long long are both 64-bits with clang x86-64. */ return LONG2NUM(n); diff --git a/src/main/c/cext/object.c b/src/main/c/cext/object.c index c91df1ac09d4..777644af9025 100644 --- a/src/main/c/cext/object.c +++ b/src/main/c/cext/object.c @@ -18,6 +18,13 @@ enum ruby_value_type rb_type(VALUE value) { return RBIMPL_CAST((enum ruby_value_type) int_type); } +struct RBasicWithFlags { + VALUE flags; + struct RBasic _basic; +}; + +#define RB_BUILTIN_TYPE_NATIVE(x) RBIMPL_CAST((enum ruby_value_type) (((struct RBasicWithFlags*)(x))->flags & RUBY_T_MASK)) + bool RB_TYPE_P(VALUE value, enum ruby_value_type type) { if (value == Qundef) { return 0; @@ -108,7 +115,7 @@ VALUE rb_obj_reveal(VALUE obj, VALUE klass) { } VALUE rb_obj_clone(VALUE obj) { - return rb_funcall(obj, rb_intern("clone"), 0); + return RUBY_INVOKE(obj, "clone"); } VALUE rb_obj_dup(VALUE object) { diff --git a/src/main/c/cext/printf.c b/src/main/c/cext/printf.c index 445e3db0cd22..d3cec00b84d7 100644 --- a/src/main/c/cext/printf.c +++ b/src/main/c/cext/printf.c @@ -46,9 +46,7 @@ VALUE rb_tr_vsprintf_new_cstr(char *cstr) { } } -#undef rb_enc_sprintf #undef rb_enc_vsprintf -#undef rb_sprintf #undef rb_vsprintf VALUE rb_enc_vsprintf(rb_encoding *enc, const char *format, va_list args) { @@ -69,28 +67,6 @@ VALUE rb_enc_vsprintf(rb_encoding *enc, const char *format, va_list args) { return rb_enc_associate(string, enc); } -VALUE rb_enc_sprintf(rb_encoding *enc, const char *format, ...) { - VALUE result; - va_list ap; - - va_start(ap, format); - result = rb_enc_vsprintf(enc, format, ap); - va_end(ap); - - return result; -} - -VALUE rb_sprintf(const char *format, ...) { - VALUE result; - va_list ap; - - va_start(ap, format); - result = rb_vsprintf(format, ap); - va_end(ap); - - return result; -} - VALUE rb_vsprintf(const char *format, va_list args) { return rb_enc_vsprintf(rb_ascii8bit_encoding(), format, args); } @@ -194,13 +170,7 @@ VALUE rb_tr_get_sprintf_args(va_list args, VALUE types) { val = va_arg(args, VALUE); break; default: - { - char *err_str; - if (asprintf(&err_str, "unhandled rb_sprintf arg type %d", type) > 0) { - rb_tr_error(err_str); - free(err_str); - } - } + rb_raise(rb_eArgError, "unhandled rb_sprintf arg type %d", type); } rb_ary_push(ary, val); } @@ -213,11 +183,3 @@ VALUE rb_str_vcatf(VALUE str, const char *fmt, va_list args) { rb_str_concat(str, result); return str; } - -VALUE rb_str_catf(VALUE str, const char *format, ...) { - va_list ap; - va_start(ap, format); - str = rb_str_vcatf(str, format, ap); - va_end(ap); - return str; -} diff --git a/src/main/c/cext/ruby.c b/src/main/c/cext/ruby.c index 8b829a43c39f..fa6c22ff0ba8 100644 --- a/src/main/c/cext/ruby.c +++ b/src/main/c/cext/ruby.c @@ -25,8 +25,17 @@ void* (*rb_tr_id2sym)(ID id); ID (*rb_tr_sym2id)(VALUE val); bool (*rb_tr_is_native_object)(VALUE value); void* (*rb_tr_force_native)(VALUE obj); +VALUE (*rb_tr_rb_f_notimplement)(int argc, const VALUE *argv, VALUE obj, VALUE marker); -VALUE rb_argv0; +void set_rb_tr_rb_f_notimplement(VALUE (*function)(int argc, const VALUE *argv, VALUE obj, VALUE marker)) { + rb_tr_rb_f_notimplement = function; +} + +void rb_tr_init_global_constants(VALUE (*get_constant)(const char*)); + +static VALUE sulong_get_constant(const char* name) { + return RUBY_CEXT_INVOKE(name); +} void rb_tr_init_exception(void); @@ -40,10 +49,10 @@ void rb_tr_init(void *ruby_cext) { rb_tr_wrap = polyglot_invoke(rb_tr_cext, "rb_tr_wrap_function"); rb_tr_longwrap = polyglot_invoke(rb_tr_cext, "rb_tr_wrap_function"); rb_tr_force_native = polyglot_invoke(rb_tr_cext, "rb_tr_force_native_function"); + set_rb_tr_rb_f_notimplement(rb_f_notimplement); // the Sulong definition of rb_f_notimplement polyglot_invoke(rb_tr_cext, "cext_start_new_handle_block"); rb_tr_init_exception(); - rb_tr_init_global_constants(); - rb_argv0 = rb_gv_get("$0"); + rb_tr_init_global_constants(sulong_get_constant); polyglot_invoke(rb_tr_cext, "cext_start_new_handle_block"); } diff --git a/src/main/c/cext/st.c b/src/main/c/cext/st.c index 0efada75cfec..3d71c7b946c0 100644 --- a/src/main/c/cext/st.c +++ b/src/main/c/cext/st.c @@ -1,4 +1,7 @@ -#include +#include + +RBIMPL_WARNING_IGNORED(-Wunused-function) +RBIMPL_WARNING_IGNORED(-Wattributes) /* This is a public domain general purpose hash table package originally written by Peter Moore @ UCB. diff --git a/src/main/c/cext/string.c b/src/main/c/cext/string.c index 1ae651410dca..a84cb9ad0076 100644 --- a/src/main/c/cext/string.c +++ b/src/main/c/cext/string.c @@ -23,24 +23,32 @@ char* ruby_strdup(const char *str) { return tmp; } -VALUE rb_string_value(VALUE *value_pointer) { - return rb_tr_string_value(value_pointer); +VALUE rb_string_value(volatile VALUE *ptr) { + VALUE value = *ptr; + if (!RB_TYPE_P(value, T_STRING)) { + value = rb_str_to_str(value); + *ptr = value; + } + return value; } -char *rb_string_value_ptr(VALUE *value_pointer) { - return rb_tr_string_value_ptr(value_pointer); +char *rb_string_value_ptr(volatile VALUE *ptr) { + VALUE string = rb_string_value(ptr); + return RSTRING_PTR(string); } -char *rb_string_value_cstr(VALUE *value_pointer) { - return rb_tr_string_value_cstr(value_pointer); +char *rb_string_value_cstr(volatile VALUE *ptr) { + VALUE string = rb_string_value(ptr); + RUBY_CEXT_INVOKE("rb_string_value_cstr_check", string); + return RSTRING_PTR(string); } -char *RSTRING_PTR_IMPL(VALUE string) { - return NATIVE_RSTRING_PTR(string); +char* rb_tr_rstring_ptr(VALUE string) { + return (char*) polyglot_as_i64(RUBY_CEXT_INVOKE_NO_WRAP("RSTRING_PTR", string)); } -char *RSTRING_END_IMPL(VALUE string) { - return NATIVE_RSTRING_PTR(string) + RSTRING_LEN(string); +char* rb_tr_rstring_end(VALUE string) { + return rb_tr_rstring_ptr(string) + RSTRING_LEN(string); } int rb_tr_str_len(VALUE string) { diff --git a/src/main/c/cext/strlcpy.c b/src/main/c/cext/strlcpy.c new file mode 100644 index 000000000000..4d39e3b328b9 --- /dev/null +++ b/src/main/c/cext/strlcpy.c @@ -0,0 +1,51 @@ +/* $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $ */ + +/* + * Copyright (c) 1998, 2015 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ruby/missing.h" +#include +#include + +#ifndef HAVE_STRLCPY +/* + * Copy string src to buffer dst of size dsize. At most dsize-1 + * chars will be copied. Always NUL terminates (unless dsize == 0). + * Returns strlen(src); if retval >= dsize, truncation occurred. + */ +size_t strlcpy(char *dst, const char *src, size_t dsize) { + const char *osrc = src; + size_t nleft = dsize; + + /* Copy as many bytes as will fit. */ + if (nleft != 0) { + while (--nleft != 0) { + if ((*dst++ = *src++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src. */ + if (nleft == 0) { + if (dsize != 0) + *dst = '\0'; /* NUL-terminate dst */ + while (*src++) + ; + } + + return(src - osrc - 1); /* count does not include NUL */ +} +#endif diff --git a/src/main/c/cext/struct.c b/src/main/c/cext/struct.c index c5f19ed4cce4..038f736f3b31 100644 --- a/src/main/c/cext/struct.c +++ b/src/main/c/cext/struct.c @@ -23,48 +23,39 @@ VALUE rb_struct_getmember(VALUE s, ID member) { return RUBY_CEXT_INVOKE("rb_struct_getmember", s, ID2SYM(member)); } -VALUE rb_struct_define(const char *name, ...) { +VALUE rb_tr_struct_define_va_list(const char *name, va_list args) { VALUE rb_name = name == NULL ? Qnil : rb_str_new_cstr(name); VALUE ary = rb_ary_new(); int i = 0; - char *arg = NULL; - va_list args; - va_start(args, name); - while ((arg = (char *)polyglot_get_array_element(&args, i)) != NULL) { + char *arg; + while ((arg = va_arg(args, char*)) != NULL) { rb_ary_push(ary, rb_str_new_cstr(arg)); i++; } - va_end(args); return RUBY_CEXT_INVOKE("rb_struct_define_no_splat", rb_name, ary); } -VALUE rb_struct_define_under(VALUE outer, const char *name, ...) { +VALUE rb_tr_struct_define_under_va_list(VALUE space, const char *name, va_list args) { VALUE rb_name = rb_str_new_cstr(name); VALUE ary = rb_ary_new(); int i = 0; - char *arg = NULL; - va_list args; - va_start(args, name); - while ((arg = (char *)polyglot_get_array_element(&args, i)) != NULL) { + char *arg; + while ((arg = va_arg(args, char*)) != NULL) { rb_ary_push(ary, rb_str_new_cstr(arg)); i++; } - va_end(args); - return RUBY_CEXT_INVOKE("rb_struct_define_under_no_splat", outer, rb_name, ary); + return RUBY_CEXT_INVOKE("rb_struct_define_under_no_splat", space, rb_name, ary); } -VALUE rb_struct_new(VALUE klass, ...) { +VALUE rb_tr_struct_new_va_list(VALUE klass, va_list args) { int members = polyglot_as_i32(RUBY_CEXT_INVOKE_NO_WRAP("rb_struct_size", klass)); - VALUE ary = rb_ary_new(); + VALUE ary = rb_ary_new_capa(members); int i = 0; - va_list args; - va_start(args, klass); while (i < members) { - VALUE arg = polyglot_get_array_element(&args, i); + VALUE arg = va_arg(args, VALUE); rb_ary_push(ary, arg); i++; } - va_end(args); return RUBY_CEXT_INVOKE("rb_struct_new_no_splat", klass, ary); } diff --git a/src/main/c/cext/symbol.c b/src/main/c/cext/symbol.c index 0d73a4c2d5ca..3beb448f42ad 100644 --- a/src/main/c/cext/symbol.c +++ b/src/main/c/cext/symbol.c @@ -14,6 +14,10 @@ // Symbol and ID, rb_sym*, rb_id* +bool rb_tr_symbol_p(VALUE obj) { + return polyglot_as_boolean(RUBY_CEXT_INVOKE_NO_WRAP("SYMBOL_P", obj)); +} + static VALUE string_for_symbol(VALUE name) { if (!RB_TYPE_P(name, T_STRING)) { VALUE tmp = rb_check_string_type(name); @@ -94,9 +98,9 @@ VALUE rb_sym_to_s(VALUE sym) { return RUBY_INVOKE(sym, "to_s"); } -#undef rb_sym2id +// TODO: rb_tr_sym2id() has a single call site since native cexts, the one below, so the inline cache in it is global. ID rb_sym2id(VALUE sym) { - return rb_tr_sym2id(sym);; + return rb_tr_sym2id(sym); } #undef rb_id2sym @@ -155,7 +159,7 @@ static int rb_sym_constant_char_p(const char *name, long nlen, rb_encoding *enc) static const UChar cname[] = "titlecaseletter"; static const UChar *const end = cname + sizeof(cname) - 1; #ifdef TRUFFLERUBY - rb_tr_error("ONIGENC_PROPERTY_NAME_TO_CTYPE not yet implemented"); + rb_tr_not_implemented("ONIGENC_PROPERTY_NAME_TO_CTYPE"); #else ctype_titlecase = ONIGENC_PROPERTY_NAME_TO_CTYPE(enc, cname, end); #endif diff --git a/src/main/c/cext/thread.c b/src/main/c/cext/thread.c index ef25798d80af..3e1b8c5b79c0 100644 --- a/src/main/c/cext/thread.c +++ b/src/main/c/cext/thread.c @@ -46,7 +46,7 @@ VALUE rb_thread_wakeup(VALUE thread) { return RUBY_INVOKE(thread, "wakeup"); } -VALUE rb_thread_create(VALUE (*fn)(ANYARGS), void *arg) { +VALUE rb_thread_create(VALUE (*fn)(void *g), void *arg) { return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_thread_create", fn, arg)); } @@ -54,7 +54,7 @@ void rb_thread_schedule(void) { RUBY_INVOKE_NO_WRAP(rb_cThread, "pass"); } -rb_nativethread_id_t rb_nativethread_self() { +rb_nativethread_id_t rb_nativethread_self(void) { return RUBY_CEXT_INVOKE("rb_nativethread_self"); } diff --git a/src/main/c/cext/time.c b/src/main/c/cext/time.c index af5747f703aa..4653ad81c7eb 100644 --- a/src/main/c/cext/time.c +++ b/src/main/c/cext/time.c @@ -23,36 +23,24 @@ VALUE rb_time_num_new(VALUE timev, VALUE off) { return RUBY_CEXT_INVOKE("rb_time_num_new", timev, off); } -struct timeval rb_time_interval(VALUE time_val) { +void rb_tr_time_interval(VALUE time_val, struct timeval *result) { RUBY_CEXT_INVOKE_NO_WRAP("rb_time_interval_acceptable", time_val); - struct timeval result; - VALUE time = rb_time_num_new(time_val, Qnil); - result.tv_sec = polyglot_as_i64(RUBY_INVOKE_NO_WRAP(time, "tv_sec")); - result.tv_usec = polyglot_as_i64(RUBY_INVOKE_NO_WRAP(time, "tv_usec")); - - return result; + result->tv_sec = polyglot_as_i64(RUBY_INVOKE_NO_WRAP(time, "tv_sec")); + result->tv_usec = polyglot_as_i64(RUBY_INVOKE_NO_WRAP(time, "tv_usec")); } -struct timeval rb_time_timeval(VALUE time_val) { - struct timeval result; - +void rb_tr_time_timeval(VALUE time_val, struct timeval *result) { VALUE time = rb_time_num_new(time_val, Qnil); - result.tv_sec = polyglot_as_i64(RUBY_INVOKE_NO_WRAP(time, "tv_sec")); - result.tv_usec = polyglot_as_i64(RUBY_INVOKE_NO_WRAP(time, "tv_usec")); - - return result; + result->tv_sec = polyglot_as_i64(RUBY_INVOKE_NO_WRAP(time, "tv_sec")); + result->tv_usec = polyglot_as_i64(RUBY_INVOKE_NO_WRAP(time, "tv_usec")); } -struct timespec rb_time_timespec(VALUE time_val) { - struct timespec result; - +void rb_tr_time_timespec(VALUE time_val, struct timespec *result) { VALUE time = rb_time_num_new(time_val, Qnil); - result.tv_sec = polyglot_as_i64(RUBY_INVOKE_NO_WRAP(time, "tv_sec")); - result.tv_nsec = polyglot_as_i64(RUBY_INVOKE_NO_WRAP(time, "tv_nsec")); - - return result; + result->tv_sec = polyglot_as_i64(RUBY_INVOKE_NO_WRAP(time, "tv_sec")); + result->tv_nsec = polyglot_as_i64(RUBY_INVOKE_NO_WRAP(time, "tv_nsec")); } VALUE rb_time_timespec_new(const struct timespec *ts, int offset) { diff --git a/src/main/c/cext/truffleruby-impl.h b/src/main/c/cext/truffleruby-impl.h index 372d942ab78c..575189b0198f 100644 --- a/src/main/c/cext/truffleruby-impl.h +++ b/src/main/c/cext/truffleruby-impl.h @@ -16,16 +16,81 @@ #include +#include + +// For polyglot_from_VALUE_array() +POLYGLOT_DECLARE_TYPE(VALUE) + // Private helper macros #define rb_boolean(c) ((c) ? Qtrue : Qfalse) +// Support + +extern void* rb_tr_cext; +#define RUBY_CEXT rb_tr_cext + +// For debugging + +void rb_tr_log_warning(const char *message); +#define rb_tr_debug(args...) polyglot_invoke(RUBY_CEXT, "rb_tr_debug", args) +int rb_tr_obj_equal(VALUE first, VALUE second); +VALUE rb_java_class_of(VALUE val); +VALUE rb_java_to_string(VALUE val); + // Private functions +extern void* (*rb_tr_unwrap)(VALUE obj); +extern VALUE (*rb_tr_wrap)(void *obj); +extern VALUE (*rb_tr_longwrap)(long obj); +extern void* (*rb_tr_id2sym)(ID obj); +extern ID (*rb_tr_sym2id)(VALUE sym); +extern void* (*rb_tr_force_native)(VALUE obj); extern bool (*rb_tr_is_native_object)(VALUE value); +extern VALUE (*rb_tr_rb_f_notimplement)(int argc, const VALUE *argv, VALUE obj, VALUE marker); // Create a native MutableTruffleString from ptr and len without copying. // The returned RubyString is only valid as long as ptr is valid (typically only as long as the caller is on the stack), // so this must be only used as an argument to an internal Truffle::CExt method which does not return or store // the RubyString but only run some operation on it. VALUE rb_tr_temporary_native_string(const char *ptr, long len, rb_encoding *enc); + +// Invoking ruby methods. + +// These macros implement ways to call the methods on Truffle::CExt +// (RUBY_CEXT_INVOKE) and other ruby objects (RUBY_INVOKE) and handle +// all the unwrapping of arguments. They also come in _NO_WRAP +// variants which will not attempt to wrap the result. This is +// important if it is not an actual ruby object being returned as an +// error will be raised when attempting to wrap such objects. + +// Internal macros for the implementation +#define RUBY_INVOKE_IMPL_0(recv, name) polyglot_invoke(recv, name) +#define RUBY_INVOKE_IMPL_1(recv, name, V1) polyglot_invoke(recv, name, rb_tr_unwrap(V1)) +#define RUBY_INVOKE_IMPL_2(recv, name, V1, V2) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2)) +#define RUBY_INVOKE_IMPL_3(recv, name, V1, V2, V3) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3)) +#define RUBY_INVOKE_IMPL_4(recv, name, V1, V2, V3, V4) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4)) +#define RUBY_INVOKE_IMPL_5(recv, name, V1, V2, V3, V4, V5) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5)) +#define RUBY_INVOKE_IMPL_6(recv, name, V1, V2, V3, V4, V5, V6) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6)) +#define RUBY_INVOKE_IMPL_7(recv, name, V1, V2, V3, V4, V5, V6, V7) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7)) +#define RUBY_INVOKE_IMPL_8(recv, name, V1, V2, V3, V4, V5, V6, V7, V8) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8)) +#define RUBY_INVOKE_IMPL_9(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9)) +#define RUBY_INVOKE_IMPL_10(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10)) +#define RUBY_INVOKE_IMPL_11(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11)) +#define RUBY_INVOKE_IMPL_12(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12)) +#define RUBY_INVOKE_IMPL_13(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13)) +#define RUBY_INVOKE_IMPL_14(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14)) +#define RUBY_INVOKE_IMPL_15(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15)) +#define RUBY_INVOKE_IMPL_16(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15), rb_tr_unwrap(V16)) +#define RUBY_INVOKE_IMPL_17(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15), rb_tr_unwrap(V16), rb_tr_unwrap(V17)) +#define RUBY_INVOKE_IMPL_18(recv, name, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18) polyglot_invoke(recv, name, rb_tr_unwrap(V1), rb_tr_unwrap(V2), rb_tr_unwrap(V3), rb_tr_unwrap(V4), rb_tr_unwrap(V5), rb_tr_unwrap(V6), rb_tr_unwrap(V7), rb_tr_unwrap(V8), rb_tr_unwrap(V9), rb_tr_unwrap(V10), rb_tr_unwrap(V11), rb_tr_unwrap(V12), rb_tr_unwrap(V13), rb_tr_unwrap(V14), rb_tr_unwrap(V15), rb_tr_unwrap(V16), rb_tr_unwrap(V17), rb_tr_unwrap(V18)) +#define INVOKE_IMPL(RECV, MESG, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, NAME, ...) NAME +#define RUBY_INVOKE_IMPL_NO_WRAP(RECV, NAME, ...) INVOKE_IMPL(RECV, NAME, ##__VA_ARGS__, RUBY_INVOKE_IMPL_18, RUBY_INVOKE_IMPL_17, RUBY_INVOKE_IMPL_16, RUBY_INVOKE_IMPL_15, RUBY_INVOKE_IMPL_14, RUBY_INVOKE_IMPL_13, RUBY_INVOKE_IMPL_12, RUBY_INVOKE_IMPL_11, RUBY_INVOKE_IMPL_10, RUBY_INVOKE_IMPL_9, RUBY_INVOKE_IMPL_8, RUBY_INVOKE_IMPL_7, RUBY_INVOKE_IMPL_6, RUBY_INVOKE_IMPL_5, RUBY_INVOKE_IMPL_4, RUBY_INVOKE_IMPL_3, RUBY_INVOKE_IMPL_2, RUBY_INVOKE_IMPL_1, RUBY_INVOKE_IMPL_0)(RECV, NAME, ##__VA_ARGS__) +#define RUBY_INVOKE_IMPL(RECV, NAME, ...) rb_tr_wrap(RUBY_INVOKE_IMPL_NO_WRAP(RECV, NAME, ##__VA_ARGS__)) + +// Public macros used in this header and ruby.c +#define RUBY_INVOKE(RECV, NAME, ...) RUBY_INVOKE_IMPL(rb_tr_unwrap(RECV), NAME, ##__VA_ARGS__) +#define RUBY_INVOKE_NO_WRAP(RECV, NAME, ...) RUBY_INVOKE_IMPL_NO_WRAP(rb_tr_unwrap(RECV), NAME, ##__VA_ARGS__) + +#define RUBY_CEXT_INVOKE(NAME, ...) RUBY_INVOKE_IMPL(RUBY_CEXT, NAME, ##__VA_ARGS__) +#define RUBY_CEXT_INVOKE_NO_WRAP(NAME, ...) RUBY_INVOKE_IMPL_NO_WRAP(RUBY_CEXT, NAME, ##__VA_ARGS__) diff --git a/src/main/c/cext/truffleruby.c b/src/main/c/cext/truffleruby.c index 155c81852a5b..2e207d3e016b 100644 --- a/src/main/c/cext/truffleruby.c +++ b/src/main/c/cext/truffleruby.c @@ -11,9 +11,10 @@ // Non-standard additional functions, rb_tr_* -void rb_tr_error(const char *message) { - RUBY_CEXT_INVOKE_NO_WRAP("rb_tr_error", rb_str_new_cstr(message)); - abort(); +void rb_tr_not_implemented(const char *function_name) { + fprintf(stderr, "The C API function %s is not implemented yet on TruffleRuby\n", function_name); + RUBY_CEXT_INVOKE_NO_WRAP("rb_tr_not_implemented", rb_str_new_cstr(function_name)); + UNREACHABLE; } void rb_tr_log_warning(const char *message) { @@ -30,23 +31,22 @@ VALUE rb_java_to_string(VALUE obj) { // BasicObject#equal? int rb_tr_obj_equal(VALUE first, VALUE second) { - return RTEST(rb_funcall(first, rb_intern("equal?"), 1, second)); + return RTEST(RUBY_INVOKE(first, "equal?", second)); } -int rb_tr_flags(VALUE value) { - int flags = 0; - if (OBJ_FROZEN(value)) { - flags |= RUBY_FL_FREEZE; - } - if (RARRAY_LEN(rb_obj_instance_variables(value)) > 0) { - flags |= RUBY_FL_EXIVAR; - } - // TODO BJF Nov-11-2017 Implement more flags - return flags; +void rb_tr_warn_va_list(const char *fmt, va_list args) { + RUBY_INVOKE(rb_mKernel, "warn", rb_vsprintf(fmt, args)); } -void rb_tr_add_flags(VALUE value, int flags) { - if (flags & RUBY_FL_FREEZE) { - rb_obj_freeze(value); - } +VALUE rb_tr_zlib_crc_table(void) { + return RUBY_CEXT_INVOKE("zlib_get_crc_table"); +} + +VALUE rb_tr_cext_lock_owned_p(void) { + return RUBY_CEXT_INVOKE("cext_lock_owned?"); +} + +// Used for internal testing +VALUE rb_tr_invoke(VALUE recv, const char* meth) { + return RUBY_INVOKE(recv, meth); } diff --git a/src/main/c/psych/psych_to_ruby.c b/src/main/c/psych/psych_to_ruby.c index f258681dcc4f..195d60aede53 100644 --- a/src/main/c/psych/psych_to_ruby.c +++ b/src/main/c/psych/psych_to_ruby.c @@ -11,7 +11,7 @@ static VALUE build_exception(VALUE self, VALUE klass, VALUE mesg) VALUE e = rb_obj_alloc(klass); #ifdef TRUFFLERUBY - RUBY_CEXT_INVOKE("rb_exception_set_message", e, mesg); + rb_exc_set_message(e, mesg); #else rb_iv_set(e, "mesg", mesg); #endif diff --git a/src/main/c/ripper/parse.c b/src/main/c/ripper/parse.c index 426400c5acef..ea2db015472c 100644 --- a/src/main/c/ripper/parse.c +++ b/src/main/c/ripper/parse.c @@ -1077,7 +1077,7 @@ void rb_strterm_mark(VALUE obj) { rb_strterm_t *strterm = (rb_strterm_t*)obj; - if (RBASIC(obj)->flags & STRTERM_HEREDOC) { + if (RBASIC_FLAGS(obj) & STRTERM_HEREDOC) { rb_strterm_heredoc_t *heredoc = &strterm->u.heredoc; rb_gc_mark(heredoc->lastline); } @@ -21633,7 +21633,11 @@ const_decl_path(struct parser_params *p, NODE **dest) return n; } +#ifdef TRUFFLERUBY +#define rb_mRubyVMFrozenCore Qnil +#else extern VALUE rb_mRubyVMFrozenCore; +#endif static NODE * make_shareable_node(struct parser_params *p, NODE *value, bool copy, const YYLTYPE *loc) diff --git a/src/main/c/ripper/ripper.c b/src/main/c/ripper/ripper.c index 4cab83a2c39d..62668382ae28 100644 --- a/src/main/c/ripper/ripper.c +++ b/src/main/c/ripper/ripper.c @@ -22879,7 +22879,11 @@ const_decl_path(struct parser_params *p, NODE **dest) return n; } +#ifdef TRUFFLERUBY +#define rb_mRubyVMFrozenCore Qnil +#else extern VALUE rb_mRubyVMFrozenCore; +#endif static NODE * make_shareable_node(struct parser_params *p, NODE *value, bool copy, const YYLTYPE *loc) diff --git a/src/main/c/zlib/zlib.c b/src/main/c/zlib/zlib.c index 286208389913..75a22e19ca40 100644 --- a/src/main/c/zlib/zlib.c +++ b/src/main/c/zlib/zlib.c @@ -537,7 +537,7 @@ rb_zlib_crc32_combine(VALUE klass, VALUE crc1, VALUE crc2, VALUE len2) */ #ifdef TRUFFLERUBY static VALUE rb_zlib_crc_table(VALUE obj) { - return RUBY_CEXT_INVOKE("zlib_get_crc_table"); + return rb_tr_zlib_crc_table(); } #else static VALUE diff --git a/src/main/java/org/truffleruby/cext/CExtNodes.java b/src/main/java/org/truffleruby/cext/CExtNodes.java index 759f24afa922..5f96cd38c5e5 100644 --- a/src/main/java/org/truffleruby/cext/CExtNodes.java +++ b/src/main/java/org/truffleruby/cext/CExtNodes.java @@ -82,7 +82,6 @@ import org.truffleruby.core.string.ImmutableRubyString; import org.truffleruby.language.LazyWarningNode; import org.truffleruby.language.LexicalScope; -import org.truffleruby.language.Nil; import org.truffleruby.language.RubyBaseNode; import org.truffleruby.language.RubyDynamicObject; import org.truffleruby.language.RubyGuards; @@ -2072,71 +2071,4 @@ RubyArray zlibGetCRCTable() { } } - @Primitive(name = "data_holder_create") - public abstract static class DataHolderCreate extends PrimitiveArrayArgumentsNode { - - @Specialization - DataHolder create(Object address, Object marker, Object free) { - return new DataHolder(address, marker, free); - } - } - - @Primitive(name = "data_holder_get_data") - public abstract static class DataHolderGetData extends PrimitiveArrayArgumentsNode { - - @Specialization - Object getData(DataHolder data) { - return data.getPointer(); - } - } - - @Primitive(name = "data_holder_set_data") - public abstract static class DataHolderSetData extends PrimitiveArrayArgumentsNode { - - @Specialization - Object setData(DataHolder data, Object address) { - data.setPointer(address); - return nil; - } - } - - @Primitive(name = "data_holder_get_marker") - public abstract static class DataHolderGetMarker extends PrimitiveArrayArgumentsNode { - - @Specialization - Object getMarker(DataHolder data) { - return data.getMarker(); - } - - @Specialization // For Truffle::CExt#run_marker - Object getMarker(Nil data) { - return data; - } - } - - @Primitive(name = "data_holder_get_free") - public abstract static class DataHolderGetFree extends PrimitiveArrayArgumentsNode { - @Specialization - Object getFree(DataHolder data) { - return data.getFree(); - } - } - - @Primitive(name = "data_holder_set_free") - public abstract static class DataHolderSetFree extends PrimitiveArrayArgumentsNode { - @Specialization - Object setFree(DataHolder data, Object dfree) { - data.setFree(dfree); - return dfree; - } - } - - @Primitive(name = "data_holder_is_holder?") - public abstract static class DataHolderIsHolder extends PrimitiveArrayArgumentsNode { - - @Specialization - boolean setData(Object dataHolder) { - return dataHolder instanceof DataHolder; - } - } } diff --git a/src/main/java/org/truffleruby/cext/DataHolder.java b/src/main/java/org/truffleruby/cext/DataHolder.java deleted file mode 100644 index 26f38ed15eef..000000000000 --- a/src/main/java/org/truffleruby/cext/DataHolder.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2018, 2023 Oracle and/or its affiliates. All rights reserved. This - * code is released under a tri EPL/GPL/LGPL license. You can use it, - * redistribute it and/or modify it under the terms of the: - * - * Eclipse Public License version 2.0, or - * GNU General Public License version 2, or - * GNU Lesser General Public License version 2.1. - */ -package org.truffleruby.cext; - -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.interop.InteropLibrary; -import com.oracle.truffle.api.interop.TruffleObject; -import com.oracle.truffle.api.interop.UnsupportedMessageException; -import com.oracle.truffle.api.library.ExportLibrary; -import com.oracle.truffle.api.library.ExportMessage; - -import org.truffleruby.RubyLanguage; -import org.truffleruby.interop.TranslateInteropExceptionNode; -import org.truffleruby.language.RubyGuards; - -/** This object represents a struct pointer in C held by a Ruby object. */ -@ExportLibrary(InteropLibrary.class) -public final class DataHolder implements TruffleObject { - - private Object pointer; - private final Object marker; - private Object free; - - public DataHolder(Object pointer, Object marker, Object free) { - // All foreign objects, so we don't need to share them for SharedObjects - assert RubyGuards.isForeignObject(pointer); - assert RubyGuards.isForeignObject(marker); - assert RubyGuards.isForeignObject(free); - this.pointer = pointer; - this.marker = marker; - this.free = free; - } - - public Object getPointer() { - return pointer; - } - - public void setPointer(Object pointer) { - assert RubyGuards.isForeignObject(pointer); - this.pointer = pointer; - } - - public Object getMarker() { - return marker; - } - - public Object getFree() { - return free; - } - - public void setFree(Object free) { - assert RubyGuards.isForeignObject(free); - this.free = free; - } - - @ExportMessage - protected boolean hasLanguage() { - return true; - } - - @ExportMessage - protected Class getLanguage() { - return RubyLanguage.class; - } - - @TruffleBoundary - @Override - public String toString() { - return pointer.toString(); - } - - @TruffleBoundary - @ExportMessage - protected String toDisplayString(boolean allowSideEffects) { - final InteropLibrary interop = InteropLibrary.getUncached(); - try { - return "DATA_HOLDER: " + interop.asString(interop.toDisplayString(pointer, allowSideEffects)); - } catch (UnsupportedMessageException e) { - throw TranslateInteropExceptionNode.executeUncached(e); - } - } -} diff --git a/src/main/java/org/truffleruby/cext/ValueWrapper.java b/src/main/java/org/truffleruby/cext/ValueWrapper.java index 7d2f3bb65457..526db8b4b07d 100644 --- a/src/main/java/org/truffleruby/cext/ValueWrapper.java +++ b/src/main/java/org/truffleruby/cext/ValueWrapper.java @@ -149,7 +149,11 @@ static Object readMember(ValueWrapper wrapper, String member, @Cached @Exclusive InlinedBranchProfile errorProfile, @Bind("$node") Node node) throws UnknownIdentifierException { if ("value".equals(member)) { - return wrapper.object; + if (wrapper.object != null) { + return wrapper.object; + } else { + return ValueWrapperManager.untagTaggedLong(wrapper.handle); + } } else { errorProfile.enter(node); throw UnknownIdentifierException.create(member); diff --git a/src/main/java/org/truffleruby/core/DataObjectFinalizationService.java b/src/main/java/org/truffleruby/core/DataObjectFinalizationService.java index c141f8945169..259ef3b8853f 100644 --- a/src/main/java/org/truffleruby/core/DataObjectFinalizationService.java +++ b/src/main/java/org/truffleruby/core/DataObjectFinalizationService.java @@ -14,7 +14,6 @@ import org.truffleruby.RubyContext; import org.truffleruby.RubyLanguage; -import org.truffleruby.cext.DataHolder; import org.truffleruby.core.MarkingService.ExtensionCallStack; import org.truffleruby.core.mutex.MutexOperations; import org.truffleruby.language.Nil; @@ -40,8 +39,6 @@ public final class DataObjectFinalizationService // We need a base node here, it should extend ruby base root node and implement internal root node. public static final class DataObjectFinalizerRootNode extends RubyBaseRootNode implements InternalRootNode { - @Child private InteropLibrary nullDataPointerNode; - @Child private InteropLibrary nullDataFreeNode; @Child private InteropLibrary callNode; private final ConditionProfile ownedProfile = ConditionProfile.create(); @@ -49,8 +46,6 @@ public DataObjectFinalizerRootNode( RubyLanguage language) { super(language, RubyLanguage.EMPTY_FRAME_DESCRIPTOR, null); - nullDataPointerNode = InteropLibrary.getFactory().createDispatched(1); - nullDataFreeNode = InteropLibrary.getFactory().createDispatched(1); callNode = InteropLibrary.getFactory().createDispatched(1); } @@ -83,16 +78,12 @@ public Object execute(DataObjectFinalizerReference ref) { private void runFinalizer(DataObjectFinalizerReference ref) throws Error { try { if (!getContext().isFinalizing()) { - Object data = ref.dataHolder.getPointer(); - Object callable = ref.dataHolder.getFree(); - if (!nullDataPointerNode.isNull(data) && !nullDataFreeNode.isNull(callable)) { - final ExtensionCallStack stack = getLanguage().getCurrentFiber().extensionCallStack; - stack.push(false, stack.getSpecialVariables(), stack.getBlock()); - try { - callNode.execute(callable, data); - } finally { - stack.pop(); - } + final ExtensionCallStack stack = getLanguage().getCurrentFiber().extensionCallStack; + stack.push(false, stack.getSpecialVariables(), stack.getBlock()); + try { + callNode.execute(ref.finalizerCFunction, ref.dataStruct); + } finally { + stack.pop(); } } } catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) { @@ -112,8 +103,9 @@ public DataObjectFinalizationService(RubyLanguage language, ReferenceProcessor r this(language, referenceProcessor.processingQueue); } - public DataObjectFinalizerReference addFinalizer(RubyContext context, Object object, DataHolder dataHolder) { - final DataObjectFinalizerReference newRef = createRef(object, dataHolder); + public DataObjectFinalizerReference addFinalizer(RubyContext context, Object object, Object finalizerCFunction, + Object dataStruct) { + final DataObjectFinalizerReference newRef = createRef(object, finalizerCFunction, dataStruct); add(newRef); context.getReferenceProcessor().processReferenceQueue(this); @@ -121,12 +113,8 @@ public DataObjectFinalizerReference addFinalizer(RubyContext context, Object obj return newRef; } - public DataObjectFinalizerReference createRef(Object object, DataHolder dataHolder) { - return new DataObjectFinalizerReference(object, processingQueue, this, dataHolder); - } - - public final void drainFinalizationQueue(RubyContext context) { - context.getReferenceProcessor().drainReferenceQueues(); + public DataObjectFinalizerReference createRef(Object object, Object finalizerCFunction, Object dataStruct) { + return new DataObjectFinalizerReference(object, processingQueue, this, finalizerCFunction, dataStruct); } @Override diff --git a/src/main/java/org/truffleruby/core/DataObjectFinalizerReference.java b/src/main/java/org/truffleruby/core/DataObjectFinalizerReference.java index d45d9ee8b8a0..d91bcfc14a4f 100644 --- a/src/main/java/org/truffleruby/core/DataObjectFinalizerReference.java +++ b/src/main/java/org/truffleruby/core/DataObjectFinalizerReference.java @@ -11,20 +11,23 @@ import java.lang.ref.ReferenceQueue; -import org.truffleruby.cext.DataHolder; +import org.truffleruby.core.ReferenceProcessingService.PhantomProcessingReference; -public class DataObjectFinalizerReference +public final class DataObjectFinalizerReference extends - ReferenceProcessingService.PhantomProcessingReference { + PhantomProcessingReference { - public final DataHolder dataHolder; + public final Object finalizerCFunction; + public final Object dataStruct; DataObjectFinalizerReference( Object object, ReferenceQueue queue, DataObjectFinalizationService service, - DataHolder dataHolder) { + Object finalizerCFunction, + Object dataStruct) { super(object, queue, service); - this.dataHolder = dataHolder; + this.finalizerCFunction = finalizerCFunction; + this.dataStruct = dataStruct; } } diff --git a/src/main/java/org/truffleruby/core/encoding/EncodingNodes.java b/src/main/java/org/truffleruby/core/encoding/EncodingNodes.java index 9fe8ee16fbec..429b56a060de 100644 --- a/src/main/java/org/truffleruby/core/encoding/EncodingNodes.java +++ b/src/main/java/org/truffleruby/core/encoding/EncodingNodes.java @@ -37,6 +37,7 @@ import org.truffleruby.core.string.RubyString; import org.truffleruby.core.string.StringGuards; import org.truffleruby.core.string.ImmutableRubyString; +import org.truffleruby.core.symbol.RubySymbol; import org.truffleruby.interop.ToJavaStringNode; import org.truffleruby.language.Nil; import org.truffleruby.language.RubyBaseNode; @@ -510,6 +511,16 @@ Object eachAlias(RubyProc block, } } + @Primitive(name = "encoding_define_alias") + public abstract static class DefineAliasNode extends PrimitiveArrayArgumentsNode { + @TruffleBoundary + @Specialization + RubyEncoding defineAlias(RubyEncoding encoding, RubySymbol aliasName) { + getContext().getEncodingManager().defineAlias(encoding.jcoding, aliasName.getString()); + return encoding; + } + } + @Primitive(name = "encoding_is_unicode") public abstract static class IsUnicodeNode extends PrimitiveArrayArgumentsNode { @Specialization diff --git a/src/main/java/org/truffleruby/core/objectspace/ObjectSpaceNodes.java b/src/main/java/org/truffleruby/core/objectspace/ObjectSpaceNodes.java index 84e0c52eca52..d51d7b981bbb 100644 --- a/src/main/java/org/truffleruby/core/objectspace/ObjectSpaceNodes.java +++ b/src/main/java/org/truffleruby/core/objectspace/ObjectSpaceNodes.java @@ -16,7 +16,6 @@ import org.truffleruby.annotations.CoreModule; import org.truffleruby.annotations.Primitive; import org.truffleruby.builtins.PrimitiveArrayArgumentsNode; -import org.truffleruby.cext.DataHolder; import org.truffleruby.core.DataObjectFinalizerReference; import org.truffleruby.core.FinalizerReference; import org.truffleruby.core.array.RubyArray; @@ -263,20 +262,11 @@ private void defineFinalizer(RubyDynamicObject object, Object finalizer) { public abstract static class DefineDataObjectFinalizerNode extends PrimitiveArrayArgumentsNode { @Specialization - Object defineFinalizer(RubyDynamicObject object, DataHolder dataHolder, - @Cached WriteBarrierNode writeBarrierNode, + Object defineFinalizer(RubyDynamicObject object, Object finalizerCFunction, Object dataStruct, @CachedLibrary(limit = "1") DynamicObjectLibrary objectLibrary) { - if (!getContext().getReferenceProcessor().processOnMainThread()) { - // Share the finalizer, as it might run on a different Thread - if (!getContext().getSharedObjects().isSharing()) { - startSharing(); - } - writeBarrierNode.execute(this, dataHolder); - } - DataObjectFinalizerReference newRef = getContext() .getDataObjectFinalizationService() - .addFinalizer(getContext(), object, dataHolder); + .addFinalizer(getContext(), object, finalizerCFunction, dataStruct); objectLibrary.put(object, Layouts.DATA_OBJECT_FINALIZER_REF_IDENTIFIER, newRef); diff --git a/src/main/java/org/truffleruby/language/TruffleBootNodes.java b/src/main/java/org/truffleruby/language/TruffleBootNodes.java index 12ef1add2764..74c12dca6313 100644 --- a/src/main/java/org/truffleruby/language/TruffleBootNodes.java +++ b/src/main/java/org/truffleruby/language/TruffleBootNodes.java @@ -14,6 +14,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; +import com.oracle.truffle.api.ArrayUtils; import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.nodes.NodeUtil; import com.oracle.truffle.api.strings.TruffleString; @@ -438,15 +439,14 @@ private static Toolchain getToolchain(RubyContext context, Node currentNode) { return toolchain; } - @CoreMethod(names = "basic_abi_version", onSingleton = true) - public abstract static class BasicABIVersionNode extends CoreMethodNode { + @CoreMethod(names = "read_abi_version", onSingleton = true) + public abstract static class ReadABIVersionNode extends CoreMethodNode { - private static final String ABI_VERSION_FILE = "lib/cext/ABI_version.txt"; + private static final String ABI_VERSION_FILE = "lib/cext/include/truffleruby/truffleruby-abi-version.h"; @TruffleBoundary @Specialization - RubyString basicABIVersion( - @Cached TruffleString.FromJavaStringNode fromJavaStringNode) { + RubyString basicABIVersion() { TruffleFile file = getLanguage().getRubyHomeTruffleFile().resolve(ABI_VERSION_FILE); byte[] bytes; try { @@ -455,8 +455,18 @@ RubyString basicABIVersion( throw CompilerDirectives.shouldNotReachHere(e); } - String basicVersion = new String(bytes, StandardCharsets.US_ASCII).strip(); - return createString(fromJavaStringNode, basicVersion, Encodings.UTF_8); + int startQuote = ArrayUtils.indexOf(bytes, 0, bytes.length, (byte) '"'); + if (startQuote < 0) { + throw CompilerDirectives.shouldNotReachHere("Invalid contents for " + ABI_VERSION_FILE); + } + int start = startQuote + 1; + int endQuote = ArrayUtils.indexOf(bytes, start, bytes.length, (byte) '"'); + if (endQuote < 0) { + throw CompilerDirectives.shouldNotReachHere("Invalid contents for " + ABI_VERSION_FILE); + } + + String basicVersion = new String(bytes, start, endQuote - start, StandardCharsets.US_ASCII); + return createString(TruffleString.FromJavaStringNode.getUncached(), basicVersion, Encodings.UTF_8); } } diff --git a/src/main/java/org/truffleruby/language/loader/FeatureLoader.java b/src/main/java/org/truffleruby/language/loader/FeatureLoader.java index 9d68db2fefbd..1dd6f3b19782 100644 --- a/src/main/java/org/truffleruby/language/loader/FeatureLoader.java +++ b/src/main/java/org/truffleruby/language/loader/FeatureLoader.java @@ -11,6 +11,7 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -86,6 +87,8 @@ public final class FeatureLoader { private Source mainScriptSource; private String mainScriptAbsolutePath; + private final List keepLibrariesAlive = Collections.synchronizedList(new ArrayList<>()); + public FeatureLoader(RubyContext context, RubyLanguage language) { this.context = context; this.language = language; @@ -444,18 +447,34 @@ public void ensureCExtImplementationLoaded(String feature, RequireNode requireNo final RubyModule truffleModule = context.getCoreLibrary().truffleModule; final Object truffleCExt = truffleModule.fields.getConstant("CExt").getValue(); + Object libTrampoline = null; + if (!context.getOptions().CEXTS_SULONG) { + var libTrampolinePath = language.getRubyHome() + "/lib/cext/libtrufflerubytrampoline" + + Platform.LIB_SUFFIX; + if (context.getOptions().CEXTS_LOG_LOAD) { + RubyLanguage.LOGGER + .info(() -> String.format("loading libtrufflerubytrampoline %s", libTrampolinePath)); + } + libTrampoline = loadCExtLibrary("libtrufflerubytrampoline", libTrampolinePath, requireNode, false); + } + final String rubyLibPath = language.getRubyHome() + "/lib/cext/libtruffleruby" + Platform.LIB_SUFFIX; final Object library = loadCExtLibRuby(rubyLibPath, feature, requireNode); - final Object initFunction = findFunctionInLibrary(library, "rb_tr_init", rubyLibPath); + var initFunction = findFunctionInLibrary(library, "rb_tr_init", rubyLibPath); - final InteropLibrary interop = InteropLibrary.getFactory().getUncached(); + final InteropLibrary interop = InteropLibrary.getUncached(); language.getCurrentFiber().extensionCallStack.push(false, nil, nil); try { // Truffle::CExt.register_libtruffleruby(libtruffleruby) interop.invokeMember(truffleCExt, "register_libtruffleruby", library); // rb_tr_init(Truffle::CExt) interop.execute(initFunction, truffleCExt); + + if (!context.getOptions().CEXTS_SULONG) { + // Truffle::CExt.init_libtrufflerubytrampoline(libtrampoline) + interop.invokeMember(truffleCExt, "init_libtrufflerubytrampoline", libTrampoline); + } } catch (InteropException e) { throw TranslateInteropExceptionNode.executeUncached(e); } finally { @@ -484,20 +503,31 @@ private Object loadCExtLibRuby(String rubyLibPath, String feature, Node currentN null)); } - return loadCExtLibrary("libtruffleruby", rubyLibPath, currentNode); + return loadCExtLibrary("libtruffleruby", rubyLibPath, currentNode, true); } @TruffleBoundary - public Object loadCExtLibrary(String feature, String path, Node currentNode) { + public Object loadCExtLibrary(String feature, String path, Node currentNode, boolean sulong) { Metrics.printTime("before-load-cext-" + feature); try { final TruffleFile truffleFile = FileLoader.getSafeTruffleFile(language, context, path); FileLoader.ensureReadable(context, truffleFile, currentNode); - final Source source = Source - .newBuilder("nfi", "with llvm load (RTLD_GLOBAL) '" + path + "'", - "load RTLD_GLOBAL with Sulong through NFI") - .build(); + final Source source; + if (sulong) { + source = Source + .newBuilder("nfi", "with llvm load (RTLD_GLOBAL) '" + path + "'", + "load RTLD_GLOBAL with Sulong through NFI") + .build(); + } else { + source = Source + .newBuilder("nfi", "load (RTLD_GLOBAL | RTLD_LAZY) '" + path + "'", "load RTLD_GLOBAL with NFI") + .build(); + } final Object library = context.getEnv().parseInternal(source).call(); + + // It is crucial to keep every native library alive, otherwise NFI will unload it and segfault later + keepLibrariesAlive.add(library); + final Object embeddedABIVersion = getEmbeddedABIVersion(library); DispatchNode.getUncached().call(context.getCoreLibrary().truffleCExtModule, "check_abi_version", embeddedABIVersion, path); @@ -509,24 +539,37 @@ public Object loadCExtLibrary(String feature, String path, Node currentNode) { } private Object getEmbeddedABIVersion(Object library) { - final Object abiVersionFunction; + InteropLibrary interop = InteropLibrary.getUncached(); + + Object abiVersionFunction; try { - abiVersionFunction = InteropLibrary.getFactory().getUncached(library).readMember(library, - "rb_tr_abi_version"); + abiVersionFunction = interop.readMember(library, "rb_tr_abi_version"); } catch (UnknownIdentifierException e) { return nil; } catch (UnsupportedMessageException e) { throw TranslateInteropExceptionNode.executeUncached(e); } - final InteropLibrary abiFunctionInteropLibrary = InteropLibrary.getFactory().getUncached(abiVersionFunction); - final String abiVersion = (String) InteropNodes.execute( + abiVersionFunction = TruffleNFIPlatform.bind(context, abiVersionFunction, "():string"); + + var abiVersionNativeString = InteropNodes.execute( null, abiVersionFunction, ArrayUtils.EMPTY_ARRAY, - abiFunctionInteropLibrary, + interop, TranslateInteropExceptionNodeGen.getUncached()); + long address; + try { + address = interop.asPointer(abiVersionNativeString); + } catch (UnsupportedMessageException e) { + throw CompilerDirectives.shouldNotReachHere(e); + } + + var pointer = new Pointer(context, address); + byte[] bytes = pointer.readZeroTerminatedByteArray(context, interop, 0); + String abiVersion = new String(bytes, StandardCharsets.US_ASCII); + return StringOperations.createUTF8String(context, language, abiVersion); } diff --git a/src/main/java/org/truffleruby/language/loader/RequireNode.java b/src/main/java/org/truffleruby/language/loader/RequireNode.java index 892494999e6f..5caead54b8e1 100644 --- a/src/main/java/org/truffleruby/language/loader/RequireNode.java +++ b/src/main/java/org/truffleruby/language/loader/RequireNode.java @@ -40,6 +40,7 @@ import org.truffleruby.language.methods.DeclarationContext; import org.truffleruby.language.methods.TranslateExceptionNode; import org.truffleruby.parser.ParserContext; +import org.truffleruby.platform.TruffleNFIPlatform; import org.truffleruby.shared.Metrics; import com.oracle.truffle.api.CompilerDirectives; @@ -268,17 +269,19 @@ private void requireCExtension(String feature, String expandedPath, Node current .info(String.format("loading cext module %s (requested as %s)", expandedPath, feature)); } - library = featureLoader.loadCExtLibrary(feature, expandedPath, currentNode); + library = featureLoader.loadCExtLibrary(feature, expandedPath, currentNode, + getContext().getOptions().CEXTS_SULONG); } catch (Exception e) { handleCExtensionException(feature, e); throw e; } final String initFunctionName = "Init_" + getBaseName(expandedPath); - final Object initFunction = featureLoader.findFunctionInLibrary(library, initFunctionName, expandedPath); + var initFunction = featureLoader.findFunctionInLibrary(library, initFunctionName, expandedPath); + initFunction = TruffleNFIPlatform.bind(getContext(), initFunction, "():void"); - final InteropLibrary initFunctionInteropLibrary = InteropLibrary.getFactory().getUncached(initFunction); - if (!initFunctionInteropLibrary.isExecutable(initFunction)) { + final InteropLibrary interop = InteropLibrary.getUncached(); + if (!interop.isExecutable(initFunction)) { throw new RaiseException( getContext(), coreExceptions().loadError(initFunctionName + "() is not executable", expandedPath, currentNode)); @@ -297,7 +300,7 @@ private void requireCExtension(String feature, String expandedPath, Node current null, initFunction, ArrayUtils.EMPTY_ARRAY, - initFunctionInteropLibrary, + interop, TranslateInteropExceptionNodeGen.getUncached()); } finally { try { diff --git a/src/main/java/org/truffleruby/language/objects/shared/WriteBarrierNode.java b/src/main/java/org/truffleruby/language/objects/shared/WriteBarrierNode.java index 9c8debaa6444..3a6741a8e1ba 100644 --- a/src/main/java/org/truffleruby/language/objects/shared/WriteBarrierNode.java +++ b/src/main/java/org/truffleruby/language/objects/shared/WriteBarrierNode.java @@ -12,7 +12,6 @@ import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.dsl.GenerateInline; import com.oracle.truffle.api.nodes.Node; -import org.truffleruby.core.DataObjectFinalizerReference; import org.truffleruby.core.FinalizerReference; import org.truffleruby.language.RubyBaseNode; import org.truffleruby.language.RubyDynamicObject; @@ -102,15 +101,8 @@ static void writeBarrierFinalizer(Node node, FinalizerReference ref, int depth) } } - - @Specialization - @TruffleBoundary - static void writeBarrierDataFinalizer(Node node, DataObjectFinalizerReference ref, int depth) { - SharedObjects.writeBarrier(getLanguage(node), ref.dataHolder); - } - protected static boolean isFinalizer(Object object) { - return object instanceof FinalizerReference || object instanceof DataObjectFinalizerReference; + return object instanceof FinalizerReference; } } diff --git a/src/main/java/org/truffleruby/options/Options.java b/src/main/java/org/truffleruby/options/Options.java index 6fec5990104e..b93031d9a937 100644 --- a/src/main/java/org/truffleruby/options/Options.java +++ b/src/main/java/org/truffleruby/options/Options.java @@ -107,6 +107,8 @@ public final class Options { public final boolean BACKTRACE_ON_NEW_FIBER; /** --cexts=true */ public final boolean CEXTS; + /** --cexts-sulong=false */ + public final boolean CEXTS_SULONG; /** --cexts-lock=true */ public final boolean CEXT_LOCK; /** --cexts-prepend-toolchain-to-path=true */ @@ -252,6 +254,7 @@ public Options(Env env, OptionValues options, LanguageOptions languageOptions) { BACKTRACE_ON_NEW_THREAD = options.get(OptionsCatalog.BACKTRACE_ON_NEW_THREAD_KEY); BACKTRACE_ON_NEW_FIBER = options.get(OptionsCatalog.BACKTRACE_ON_NEW_FIBER_KEY); CEXTS = options.get(OptionsCatalog.CEXTS_KEY); + CEXTS_SULONG = options.get(OptionsCatalog.CEXTS_SULONG_KEY); CEXT_LOCK = options.get(OptionsCatalog.CEXT_LOCK_KEY); CEXTS_PREPEND_TOOLCHAIN_TO_PATH = options.get(OptionsCatalog.CEXTS_PREPEND_TOOLCHAIN_TO_PATH_KEY); CEXTS_KEEP_HANDLES_ALIVE = options.get(OptionsCatalog.CEXTS_KEEP_HANDLES_ALIVE_KEY); @@ -389,6 +392,8 @@ public Object fromDescriptor(OptionDescriptor descriptor) { return BACKTRACE_ON_NEW_FIBER; case "ruby.cexts": return CEXTS; + case "ruby.cexts-sulong": + return CEXTS_SULONG; case "ruby.cexts-lock": return CEXT_LOCK; case "ruby.cexts-prepend-toolchain-to-path": diff --git a/src/main/java/org/truffleruby/platform/TruffleNFIPlatform.java b/src/main/java/org/truffleruby/platform/TruffleNFIPlatform.java index 571ee0455858..cd4b81bd7baa 100644 --- a/src/main/java/org/truffleruby/platform/TruffleNFIPlatform.java +++ b/src/main/java/org/truffleruby/platform/TruffleNFIPlatform.java @@ -51,7 +51,7 @@ public Object lookup(Object library, String name) { } } - private static Object bind(RubyContext context, Object function, String signature) { + public static Object bind(RubyContext context, Object function, String signature) { Object parsedSignature = context .getEnv() .parseInternal(Source.newBuilder("nfi", signature, "native").build()) diff --git a/src/main/ruby/truffleruby/core/post.rb b/src/main/ruby/truffleruby/core/post.rb index bed823501e1f..20c2d2abb0b2 100644 --- a/src/main/ruby/truffleruby/core/post.rb +++ b/src/main/ruby/truffleruby/core/post.rb @@ -97,7 +97,7 @@ def p(*a) # Does not exist but it's used by rubygems to determine index where to insert gem lib directories, as a result # paths supplied by -I will stay before gem lib directories. See Gem.load_path_insert_index in rubygems.rb. # Must be kept in sync with the value of RbConfig::CONFIG['sitelibdir']. - $LOAD_PATH.push "#{ruby_home}/lib/ruby/site_ruby/#{Truffle::GemUtil.abi_version}" + $LOAD_PATH.push "#{ruby_home}/lib/ruby/site_ruby/#{Truffle::GemUtil::ABI_VERSION}" $LOAD_PATH.push "#{ruby_home}/lib/truffle" $LOAD_PATH.push "#{ruby_home}/lib/mri" diff --git a/src/main/ruby/truffleruby/core/truffle/encoding_operations.rb b/src/main/ruby/truffleruby/core/truffle/encoding_operations.rb index fcb0270d5c8f..baa1aeb128f7 100644 --- a/src/main/ruby/truffleruby/core/truffle/encoding_operations.rb +++ b/src/main/ruby/truffleruby/core/truffle/encoding_operations.rb @@ -55,5 +55,11 @@ def self.replicate_encoding(encoding, name) EncodingMap[name.upcase.to_sym] = [nil, new_encoding] new_encoding end + + def self.define_alias(encoding, alias_name) + key = alias_name.upcase.to_sym + EncodingMap[key] = [alias_name, encoding] + Primitive.encoding_define_alias(encoding, key) + end end end diff --git a/src/main/ruby/truffleruby/core/truffle/gem_util.rb b/src/main/ruby/truffleruby/core/truffle/gem_util.rb index a7f0a12eda6c..fd4e4b17bab8 100644 --- a/src/main/ruby/truffleruby/core/truffle/gem_util.rb +++ b/src/main/ruby/truffleruby/core/truffle/gem_util.rb @@ -81,6 +81,8 @@ module Truffle::GemUtil MARKER_NAME = 'truffleruby_gem_dir_marker.txt' + ABI_VERSION = -Truffle::Boot.read_abi_version + def self.upgraded_default_gem?(feature) if i = feature.index('/') first_component = feature[0...i] @@ -145,7 +147,7 @@ def self.compute_gem_path data_home = (ENV['XDG_DATA_HOME'] || "#{user_home}/.local/share") gem_dir = "#{data_home}/gem" end - user_dir = "#{gem_dir}/truffleruby/#{abi_version}" + user_dir = "#{gem_dir}/truffleruby/#{ABI_VERSION}" # From Gem.default_dir, overridden in lib/truffle/rubygems/defaults/truffleruby.rb default_dir = "#{Truffle::Boot.ruby_home}/lib/gems" @@ -172,10 +174,6 @@ def self.compute_gem_path paths.map { |path| expand(path) }.uniq end - def self.abi_version - @abi_version ||= "#{RUBY_VERSION}.#{Truffle::Boot.basic_abi_version}".freeze - end - def self.expand(path) if File.directory?(path) File.realpath(path) diff --git a/src/options.yml b/src/options.yml index ab003c04fc1f..6548b3674275 100644 --- a/src/options.yml +++ b/src/options.yml @@ -127,6 +127,7 @@ EXPERT: # C extension options CEXTS: [cexts, boolean, true, Enable use of C extensions] + CEXTS_SULONG: [cexts-sulong, boolean, false, Run C extensions on Sulong instead of natively] CEXT_LOCK: [cexts-lock, boolean, true, Use a Global Lock when running C extensions] CEXTS_PREPEND_TOOLCHAIN_TO_PATH: [cexts-prepend-toolchain-to-path, boolean, true, Prepend the GraalVM LLVM Toolchain to PATH when installing gems] CEXTS_KEEP_HANDLES_ALIVE: [keep-handles-alive, boolean, false, Keep handles for value wrappers alive forever] diff --git a/src/shared/java/org/truffleruby/shared/options/OptionsCatalog.java b/src/shared/java/org/truffleruby/shared/options/OptionsCatalog.java index 47d28946dcd8..8cc1c98f522c 100644 --- a/src/shared/java/org/truffleruby/shared/options/OptionsCatalog.java +++ b/src/shared/java/org/truffleruby/shared/options/OptionsCatalog.java @@ -71,6 +71,7 @@ public final class OptionsCatalog { public static final OptionKey BACKTRACE_ON_NEW_THREAD_KEY = new OptionKey<>(false); public static final OptionKey BACKTRACE_ON_NEW_FIBER_KEY = new OptionKey<>(false); public static final OptionKey CEXTS_KEY = new OptionKey<>(true); + public static final OptionKey CEXTS_SULONG_KEY = new OptionKey<>(false); public static final OptionKey CEXT_LOCK_KEY = new OptionKey<>(true); public static final OptionKey CEXTS_PREPEND_TOOLCHAIN_TO_PATH_KEY = new OptionKey<>(true); public static final OptionKey CEXTS_KEEP_HANDLES_ALIVE_KEY = new OptionKey<>(false); @@ -573,6 +574,14 @@ public final class OptionsCatalog { .usageSyntax("") .build(); + public static final OptionDescriptor CEXTS_SULONG = OptionDescriptor + .newBuilder(CEXTS_SULONG_KEY, "ruby.cexts-sulong") + .help("Run C extensions on Sulong instead of natively") + .category(OptionCategory.EXPERT) + .stability(OptionStability.EXPERIMENTAL) + .usageSyntax("") + .build(); + public static final OptionDescriptor CEXT_LOCK = OptionDescriptor .newBuilder(CEXT_LOCK_KEY, "ruby.cexts-lock") .help("Use a Global Lock when running C extensions") @@ -1421,6 +1430,8 @@ public static OptionDescriptor fromName(String name) { return BACKTRACE_ON_NEW_FIBER; case "ruby.cexts": return CEXTS; + case "ruby.cexts-sulong": + return CEXTS_SULONG; case "ruby.cexts-lock": return CEXT_LOCK; case "ruby.cexts-prepend-toolchain-to-path": @@ -1665,6 +1676,7 @@ public static OptionDescriptor[] allDescriptors() { BACKTRACE_ON_NEW_THREAD, BACKTRACE_ON_NEW_FIBER, CEXTS, + CEXTS_SULONG, CEXT_LOCK, CEXTS_PREPEND_TOOLCHAIN_TO_PATH, CEXTS_KEEP_HANDLES_ALIVE, diff --git a/tool/generate-cext-constants.rb b/tool/generate-cext-constants.rb index 07d4707992a2..d1e6561ca084 100755 --- a/tool/generate-cext-constants.rb +++ b/tool/generate-cext-constants.rb @@ -89,7 +89,8 @@ IO::WaitReadable, IO::WaitWritable, [ZeroDivisionError, 'ZeroDivError'], - ['Truffle::CExt.rb_const_get(Object, \'fatal\')', 'Fatal'], + ['Truffle::CExt.rb_const_get(Object, \'fatal\')', 'rb_eFatal'], + ['$0', 'rb_argv0'] ].map do |const| if const.is_a?(Array) value, name = const @@ -103,19 +104,20 @@ end end - expr = value.to_s - - if (value.is_a?(Class) && value <= Exception) or name == 'Fatal' - tag = 'rb_e' - elsif value.is_a?(Class) - tag = 'rb_c' - elsif value.is_a?(Module) - tag = 'rb_m' - else - raise value.inspect + unless value.is_a?(String) + if value.is_a?(Class) && value <= Exception + tag = 'rb_e' + elsif value.is_a?(Class) + tag = 'rb_c' + elsif value.is_a?(Module) + tag = 'rb_m' + else + raise value.inspect + end + name = "#{tag}#{name}" end - name = "#{tag}#{name}" + expr = value.to_s [name, expr] end @@ -143,15 +145,20 @@ end f.puts - f.puts "void rb_tr_init_global_constants(void) {" + f.puts "void rb_tr_init_global_constants(VALUE (*get_constant)(const char*)) {" constants.each do |name, expr| - f.puts " #{name} = RUBY_CEXT_INVOKE(\"#{name}\");" + f.puts " #{name} = get_constant(\"#{name}\");" end f.puts "}" end +File.write "src/main/c/cext-trampoline/cext_constants.c", + File.read("src/main/c/cext/cext_constants.c").sub('rb_tr_init_global_constants', 'rb_tr_trampoline_init_global_constants') + File.open("lib/truffle/truffle/cext_constants.rb", "w") do |f| f.puts < +#include +#include +#include +#include +#include + +#include + +// Functions +C + + functions.each do |declaration, return_type, function_name, argument_types| + f.puts "#undef #{function_name}" + f.puts "static #{declaration.sub(/\{$/, '').rstrip.sub(function_name, "(*impl_#{function_name})")};" + end + f.puts + + functions.each do |declaration, return_type, function_name, argument_types| + argument_names = argument_types.delete_prefix('(').delete_suffix(')') + argument_names = argument_names.scan(/(?:^|,)\s*(#{argument_regexp})\s*(?=,|$)/o) + argument_names = argument_names.map { |full_arg, name1, name2| + if full_arg == "void" + "" + elsif full_arg == "..." + raise "C does not allow to forward varargs:\n#{declaration}\nInstead copy the approach used for rb_yield_values() or rb_sprintf()\n\n" + else + name1 or name2 or raise "Could not find argument name for #{full_arg}" + end + } + argument_names = argument_names.join(', ') + argument_names = '' if argument_names == 'void' + + no_return = NO_RETURN_FUNCTIONS.include?(function_name) + + f.puts declaration + if return_type.strip == 'void' or no_return + f.puts " impl_#{function_name}(#{argument_names});" + else + f.puts " return impl_#{function_name}(#{argument_names});" + end + f.puts " UNREACHABLE;" if no_return + f.puts "}" + f.puts + end + + f.puts < Date: Mon, 16 Oct 2023 16:38:33 +0200 Subject: [PATCH 05/50] Implement setjmp/longjmp on exception in truffleruby --- .gitignore | 1 + .../include/truffleruby/truffleruby-pre.h | 2 + lib/truffle/truffle/cext.rb | 209 ++++++++++-------- lib/truffle/truffle/cext_ruby.rb | 68 +++--- mx.truffleruby/suite.py | 2 + src/main/c/Makefile | 2 +- src/main/c/cext-trampoline/Makefile | 2 +- src/main/c/cext/call.c | 36 +-- src/main/c/cext/exception.c | 2 +- .../java/org/truffleruby/cext/CExtNodes.java | 2 +- .../truffleruby/core/array/ArrayUtils.java | 5 + .../truffleruby/core/thread/ThreadNodes.java | 30 ++- .../language/loader/FeatureLoader.java | 6 +- tool/generate-cext-trampoline.rb | 104 ++++++++- 14 files changed, 292 insertions(+), 179 deletions(-) diff --git a/.gitignore b/.gitignore index 675178552e8b..1ee608e6e1f4 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ src/main/c/*/Makefile !src/main/c/spawn-helper/Makefile !src/main/c/truffleposix/Makefile src/main/c/cext-trampoline/trampoline.c +src/main/c/cext/wrappers.c src/main/c/etc/constdefs.h src/main/c/spawn-helper/spawn-helper diff --git a/lib/cext/include/truffleruby/truffleruby-pre.h b/lib/cext/include/truffleruby/truffleruby-pre.h index 851541f521a0..99fcaab1d228 100644 --- a/lib/cext/include/truffleruby/truffleruby-pre.h +++ b/lib/cext/include/truffleruby/truffleruby-pre.h @@ -76,6 +76,8 @@ void rb_tr_set_flags(VALUE object, unsigned long flags); #define RBASIC_SET_FLAGS(obj, flags_to_set) rb_tr_set_flags(obj, flags_to_set) void rb_exc_set_message(VALUE exc, VALUE message); + +// This is used by several C extensions. It is not a public API, but it is a public symbol exposed by CRuby. VALUE rb_ivar_lookup(VALUE object, const char *name, VALUE default_value); // Defines diff --git a/lib/truffle/truffle/cext.rb b/lib/truffle/truffle/cext.rb index f68306a3868a..70afb5d1a798 100644 --- a/lib/truffle/truffle/cext.rb +++ b/lib/truffle/truffle/cext.rb @@ -27,13 +27,45 @@ module Truffle::CExt SULONG = Truffle::Boot.get_option('cexts-sulong') NFI = !SULONG - if NFI - # rb_block_call_func_t - RB_BLOCK_CALL_FUNC_SIGNATURE = Primitive.interop_eval_nfi('(pointer,pointer,sint32,pointer,pointer):pointer') - POINTER_TO_VOID_SIGNATURE = Primitive.interop_eval_nfi('(pointer):void') - POINTER_TO_POINTER_SIGNATURE = Primitive.interop_eval_nfi('(pointer):pointer') - TWO_POINTERS_TO_POINTER_SIGNATURE = Primitive.interop_eval_nfi('(pointer,pointer):pointer') - THREE_POINTERS_TO_INT_SIGNATURE = Primitive.interop_eval_nfi('(pointer,pointer,pointer):sint32') + SET_LIBTRUFFLERUBY = -> libtruffleruby do + LIBTRUFFLERUBY = libtruffleruby + end + + SET_LIBTRAMPOLINE = -> libtrampoline do + LIBTRAMPOLINE = libtrampoline + end + + SETUP_WRAPPERS = -> lib do + # signature starts with an extra pointer which is for passing the native function to call + POINTER_TO_POINTER_WRAPPERS = [nil] + (1..16).map do |n| + sig = -"(pointer,#{(['pointer'] * n).join(',')}):pointer" + Primitive.interop_eval_nfi(sig).bind(lib[:"rb_tr_setjmp_wrapper_pointer#{n}_to_pointer"]) + end + + POINTER_TO_POINTER_WRAPPER = POINTER_TO_POINTER_WRAPPERS[1] + POINTER2_TO_POINTER_WRAPPER = POINTER_TO_POINTER_WRAPPERS[2] + POINTER3_TO_POINTER_WRAPPER = POINTER_TO_POINTER_WRAPPERS[3] + + POINTER_TO_VOID_WRAPPER = Primitive.interop_eval_nfi('(pointer,pointer):void').bind(lib[:rb_tr_setjmp_wrapper_pointer1_to_void]) + POINTER2_TO_VOID_WRAPPER = Primitive.interop_eval_nfi('(pointer,pointer,pointer):void').bind(lib[:rb_tr_setjmp_wrapper_pointer2_to_void]) + POINTER3_TO_VOID_WRAPPER = Primitive.interop_eval_nfi('(pointer,pointer,pointer,pointer):void').bind(lib[:rb_tr_setjmp_wrapper_pointer3_to_void]) + POINTER3_TO_INT_WRAPPER = Primitive.interop_eval_nfi('(pointer,pointer,pointer,pointer):sint32').bind(lib[:rb_tr_setjmp_wrapper_pointer3_to_int]) + POINTER_TO_SIZE_T_WRAPPER = Primitive.interop_eval_nfi('(pointer,pointer):uint64').bind(lib[:rb_tr_setjmp_wrapper_pointer1_to_size_t]) + INT_POINTER2_TO_POINTER_WRAPPER = Primitive.interop_eval_nfi('(pointer,sint32,pointer,pointer):pointer').bind(lib[:rb_tr_setjmp_wrapper_int_pointer2_to_pointer]) + POINTER2_INT_TO_POINTER_WRAPPER = Primitive.interop_eval_nfi('(pointer,pointer,pointer,sint32):pointer').bind(lib[:rb_tr_setjmp_wrapper_pointer2_int_to_pointer]) + POINTER2_INT_POINTER2_TO_POINTER_WRAPPER = Primitive.interop_eval_nfi('(pointer,pointer,pointer,sint32,pointer,pointer):pointer').bind(lib[:rb_tr_setjmp_wrapper_pointer2_int_pointer2_to_pointer]) + RB_BLOCK_CALL_FUNC_WRAPPER = POINTER2_INT_POINTER2_TO_POINTER_WRAPPER + + RB_DEFINE_METHOD_WRAPPERS = [ + # argc=0 still means passing (VALUE self) for rb_define_method + # argc >= 0 # (VALUE obj); (VALUE obj, VALUE arg1); (VALUE obj, VALUE arg1, VALUE arg2); ... + *POINTER_TO_POINTER_WRAPPERS[1..-1], + nil, + # argc == -2 # (VALUE obj, VALUE rubyArrayArgs) + POINTER_TO_POINTER_WRAPPERS[2], + # argc == -1 # (int argc, VALUE *argv, VALUE obj) + INT_POINTER2_TO_POINTER_WRAPPER + ] end extend self @@ -122,18 +154,18 @@ module Truffle::CExt RUBY_ECONV_PARTIAL_INPUT = Encoding::Converter::PARTIAL_INPUT RUBY_ECONV_AFTER_OUTPUT = Encoding::Converter::AFTER_OUTPUT - SET_LIBTRUFFLERUBY = -> libtruffleruby do - LIBTRUFFLERUBY = libtruffleruby - end - # Rails 6 overrides `Time.at` in a way that doesn't delegate keyword arguments to the original `Time.at` method. # The issue was fixed in Rails 6.1 but wasn't backported. # It causes ArgumentError exception when `rb_time_timespec_new` function is called in the `pg` gem. # See https://github.com/rails/rails/issues/47586. ORIGINAL_TIME_AT = Time.method(:at) - def self.register_libtruffleruby(libtruffleruby) + def self.init_libtruffleruby(libtruffleruby) SET_LIBTRUFFLERUBY.call(libtruffleruby) + + libtruffleruby.rb_tr_init(Truffle::CExt) + + SETUP_WRAPPERS.call(libtruffleruby) if SULONG end def self.init_libtrufflerubytrampoline(libtrampoline) @@ -152,7 +184,7 @@ def self.init_libtrufflerubytrampoline(libtrampoline) # init_globals.call(*globals_args) init_functions = libtrampoline[:rb_tr_trampoline_init_functions] - init_functions = Primitive.interop_eval_nfi('((string):pointer):void').bind(init_functions) + init_functions = Primitive.interop_eval_nfi('(env,(string):pointer):void').bind(init_functions) init_functions.call(-> name { LIBTRUFFLERUBY[name] }) init_constants = libtrampoline[:rb_tr_trampoline_init_global_constants] @@ -160,7 +192,11 @@ def self.init_libtrufflerubytrampoline(libtrampoline) # TODO: use a Hash instead to be faster? (or Array with fixed indices) init_constants.call(-> name { value = Truffle::CExt.send(name); keep_alive << value; Primitive.cext_wrap(value) }) - LIBTRUFFLERUBY[:set_rb_tr_rb_f_notimplement].call(libtrampoline[:rb_f_notimplement]) + LIBTRUFFLERUBY.set_rb_tr_rb_f_notimplement(libtrampoline[:rb_f_notimplement]) + + SET_LIBTRAMPOLINE.call(libtrampoline) + + SETUP_WRAPPERS.call(libtrampoline) @init_libtrufflerubytrampoline_keep_alive = keep_alive.freeze end @@ -538,8 +574,6 @@ def rb_f_global_variables end def rb_ivar_foreach(object, func, arg) - func = THREE_POINTERS_TO_INT_SIGNATURE.bind(func) if NFI - keys_and_vals = [] if Primitive.is_a?(object, Module) keys_and_vals << :__classpath__ @@ -556,8 +590,8 @@ def rb_ivar_foreach(object, func, arg) end keys_and_vals.each_slice(2) do |key, val| - st_result = Truffle::Interop.execute_without_conversion( - func, Primitive.cext_sym2id(key), Primitive.cext_wrap(val), arg) + st_result = Primitive.interop_execute(POINTER3_TO_INT_WRAPPER, + [func, Primitive.cext_sym2id(key), Primitive.cext_wrap(val), arg]) case st_result when ST_CONTINUE @@ -731,8 +765,8 @@ def rb_str_new_frozen(value) def rb_tracepoint_new(events, func, data) TracePoint.new(*events_to_events_array(events)) do |tp| Primitive.call_with_c_mutex_and_frame( - func, - [tp, data], + POINTER2_TO_VOID_WRAPPER, + [func, Primitive.cext_wrap(tp), data], Primitive.caller_special_variables_if_available, nil) end @@ -1041,9 +1075,9 @@ def rb_hash_set_ifnone(hash, value) ST_REPLACE = 4 def rb_hash_foreach(hash, func, farg) - func = THREE_POINTERS_TO_INT_SIGNATURE.bind(func) if NFI hash.each do |key, value| - st_result = Truffle::Interop.execute_without_conversion(func, Primitive.cext_wrap(key), Primitive.cext_wrap(value), farg) + st_result = Primitive.interop_execute(POINTER3_TO_INT_WRAPPER, + [func, Primitive.cext_wrap(key), Primitive.cext_wrap(value), farg]) case st_result when ST_CONTINUE @@ -1069,9 +1103,9 @@ def rb_path_to_class(path) end def rb_proc_new(function, value) - function = RB_BLOCK_CALL_FUNC_SIGNATURE.bind(function) if NFI Proc.new do |*args, &block| - Primitive.call_with_c_mutex_and_frame_and_unwrap(function, [ + Primitive.call_with_c_mutex_and_frame_and_unwrap(RB_BLOCK_CALL_FUNC_WRAPPER, [ + function, Primitive.cext_wrap(args.first), # yieldarg Primitive.cext_wrap(value), # procarg, args.size, # argc @@ -1105,7 +1139,6 @@ def rb_proc_call(prc, args) # throw it and allow normal error handling to continue. def rb_protect(function, arg, write_status, status) - function = POINTER_TO_POINTER_SIGNATURE.bind(function) if NFI # We wrap nil here to avoid wrapping any result returned, as the # function called will do that. In general we try not to touch the # values passed in or out of protected functions as C extensions @@ -1113,7 +1146,7 @@ def rb_protect(function, arg, write_status, status) res = Primitive.cext_wrap(nil) pos = 0 e = capture_exception do - res = Truffle::Interop.execute_without_conversion(function, arg) + res = Primitive.interop_execute(POINTER_TO_POINTER_WRAPPER, [function, arg]) end unless Primitive.nil?(e) @@ -1138,12 +1171,12 @@ def rb_jump_tag(pos) end def rb_yield(value) - Primitive.interop_execute(rb_block_proc, [value]) + rb_block_proc.call(value) end Truffle::Graal.always_split instance_method(:rb_yield) def rb_yield_splat(values) - Primitive.interop_execute(rb_block_proc, values) + rb_block_proc.call(*values) end Truffle::Graal.always_split instance_method(:rb_yield_splat) @@ -1333,11 +1366,10 @@ def rb_enumeratorize(obj, meth, args) def rb_enumeratorize_with_size(obj, meth, args, size_fn) return rb_enumeratorize(obj, meth, args) if Primitive.interop_null?(size_fn) - size_fn = Primitive.interop_eval_nfi('(pointer,pointer,pointer):pointer').bind(size_fn) if NFI enum = obj.to_enum(meth, *args) do Primitive.call_with_c_mutex_and_frame_and_unwrap( - size_fn, - [Primitive.cext_wrap(obj), Primitive.cext_wrap(args), Primitive.cext_wrap(enum)], + POINTER3_TO_POINTER_WRAPPER, + [size_fn, Primitive.cext_wrap(obj), Primitive.cext_wrap(args), Primitive.cext_wrap(enum)], Primitive.caller_special_variables_if_available, nil) end @@ -1353,11 +1385,10 @@ def rb_newobj_of(ruby_class) end def rb_define_alloc_func(ruby_class, function) - function = POINTER_TO_POINTER_SIGNATURE.bind(function) if NFI ruby_class.singleton_class.define_method(:__allocate__) do Primitive.call_with_c_mutex_and_frame_and_unwrap( - function, - [Primitive.cext_wrap(self)], + POINTER_TO_POINTER_WRAPPER, + [function, Primitive.cext_wrap(self)], Primitive.caller_special_variables_if_available, nil) end @@ -1488,9 +1519,8 @@ def rb_mutex_sleep(mutex, timeout) end def rb_mutex_synchronize(mutex, func, arg) - func = POINTER_TO_POINTER_SIGNATURE.bind(func) if NFI mutex.synchronize do - Primitive.cext_unwrap(Primitive.interop_execute(func, [arg])) + Primitive.cext_unwrap(Primitive.interop_execute(POINTER_TO_POINTER_WRAPPER, [func, arg])) end end Truffle::Graal.always_split instance_method(:rb_mutex_synchronize) @@ -1547,8 +1577,9 @@ def rb_nativethread_lock_destroy(lock) def rb_set_end_proc(func, data) at_exit do - func = POINTER_TO_VOID_SIGNATURE.bind(func) if NFI - Primitive.call_with_c_mutex_and_frame(func, [data], Primitive.caller_special_variables_if_available, nil) + Primitive.call_with_c_mutex_and_frame( + POINTER_TO_VOID_WRAPPER, [func, data], + Primitive.caller_special_variables_if_available, nil) end end @@ -1578,23 +1609,22 @@ def RTYPEDDATA(object) if Truffle::Interop.null?(marker_function) nil else - -> { Primitive.interop_execute(marker_function, [struct]) } + -> { Primitive.interop_execute(POINTER_TO_VOID_WRAPPER, [marker_function, struct]) } end end private def data_sizer(sizer_function, rtypeddata) raise unless sizer_function.respond_to?(:call) proc { - Primitive.call_with_c_mutex_and_frame(sizer_function, [rtypeddata], Primitive.caller_special_variables_if_available, nil) + Primitive.call_with_c_mutex_and_frame( + POINTER_TO_SIZE_T_WRAPPER, [sizer_function, rtypeddata], + Primitive.caller_special_variables_if_available, nil) } end def rb_data_object_wrap(ruby_class, data, mark, free) ruby_class = Object unless ruby_class object = ruby_class.__send__(:__layout_allocate__) - if NFI and !Truffle::Interop.null?(mark) - mark = POINTER_TO_VOID_SIGNATURE.bind(mark) - end rdata = LIBTRUFFLERUBY.rb_tr_rdata_create(mark, free, data) Primitive.object_hidden_var_set object, DATA_STRUCT, rdata @@ -1610,9 +1640,6 @@ def rb_data_object_wrap(ruby_class, data, mark, free) def rb_data_typed_object_wrap(ruby_class, data, data_type, mark, free, size) ruby_class = Object unless ruby_class object = ruby_class.__send__(:__layout_allocate__) - if NFI and !Truffle::Interop.null?(mark) - mark = POINTER_TO_VOID_SIGNATURE.bind(mark) - end rtypeddata = LIBTRUFFLERUBY.rb_tr_rtypeddata_create(data_type, data) Primitive.object_hidden_var_set object, DATA_STRUCT, rtypeddata @@ -1676,14 +1703,13 @@ def send_splatted(object, method, args) def rb_block_call(object, method, args, func, data) object.__send__(method, *args) do |*block_args| - func = RB_BLOCK_CALL_FUNC_SIGNATURE.bind(func) if NFI - - Primitive.cext_unwrap(Primitive.call_with_c_mutex(func, [ # Probably need to save the frame here for blocks. - Primitive.cext_wrap(block_args.first), - data, - block_args.size, # argc - Truffle::CExt.RARRAY_PTR(block_args), # argv - nil, # blockarg + Primitive.cext_unwrap(Primitive.call_with_c_mutex(RB_BLOCK_CALL_FUNC_WRAPPER, [ # Probably need to save the frame here for blocks. + func, + Primitive.cext_wrap(block_args.first), + data, + block_args.size, # argc + Truffle::CExt.RARRAY_PTR(block_args), # argv + nil, # blockarg ])) end end @@ -1693,38 +1719,32 @@ def rb_module_new end def rb_ensure(b_proc, data1, e_proc, data2) - b_proc = POINTER_TO_POINTER_SIGNATURE.bind(b_proc) if NFI begin - Primitive.interop_execute(b_proc, [data1]) + Primitive.interop_execute(POINTER_TO_POINTER_WRAPPER, [b_proc, data1]) ensure - e_proc = POINTER_TO_POINTER_SIGNATURE.bind(e_proc) if NFI - Primitive.interop_execute(e_proc, [data2]) + Primitive.interop_execute(POINTER_TO_POINTER_WRAPPER, [e_proc, data2]) end end Truffle::Graal.always_split instance_method(:rb_ensure) def rb_rescue(b_proc, data1, r_proc, data2) - b_proc = POINTER_TO_POINTER_SIGNATURE.bind(b_proc) if NFI begin - Primitive.interop_execute(b_proc, [data1]) + Primitive.interop_execute(POINTER_TO_POINTER_WRAPPER, [b_proc, data1]) rescue StandardError => e if Truffle::Interop.null?(r_proc) Primitive.cext_wrap(nil) else - r_proc = TWO_POINTERS_TO_POINTER_SIGNATURE.bind(r_proc) if NFI - Primitive.interop_execute(r_proc, [data2, Primitive.cext_wrap(e)]) + Primitive.interop_execute(POINTER2_TO_POINTER_WRAPPER, [r_proc, data2, Primitive.cext_wrap(e)]) end end end Truffle::Graal.always_split instance_method(:rb_rescue) def rb_rescue2(b_proc, data1, r_proc, data2, rescued) - b_proc = POINTER_TO_POINTER_SIGNATURE.bind(b_proc) if NFI begin - Primitive.interop_execute(b_proc, [data1]) + Primitive.interop_execute(POINTER_TO_POINTER_WRAPPER, [b_proc, data1]) rescue *rescued => e - r_proc = TWO_POINTERS_TO_POINTER_SIGNATURE.bind(r_proc) if NFI - Primitive.interop_execute(r_proc, [data2, Primitive.cext_wrap(e)]) + Primitive.interop_execute(POINTER2_TO_POINTER_WRAPPER, [r_proc, data2, Primitive.cext_wrap(e)]) end end Truffle::Graal.always_split instance_method(:rb_rescue2) @@ -1732,15 +1752,16 @@ def rb_rescue2(b_proc, data1, r_proc, data2, rescued) def rb_exec_recursive(func, obj, arg) result = nil - func = Primitive.interop_eval_nfi('(pointer,pointer,sint32):pointer').bind(func) if NFI recursive = Truffle::ThreadOperations.detect_recursion(obj) do result = Primitive.cext_unwrap(Primitive.interop_execute( - func, - [Primitive.cext_wrap(obj), Primitive.cext_wrap(arg), 0])) + POINTER2_INT_TO_POINTER_WRAPPER, + [func, Primitive.cext_wrap(obj), Primitive.cext_wrap(arg), 0])) end if recursive - Primitive.cext_unwrap(Primitive.interop_execute(func, [Primitive.cext_wrap(obj), Primitive.cext_wrap(arg), 1])) + Primitive.cext_unwrap(Primitive.interop_execute( + POINTER2_INT_TO_POINTER_WRAPPER, + [func, Primitive.cext_wrap(obj), Primitive.cext_wrap(arg), 1])) else result end @@ -1749,13 +1770,13 @@ def rb_exec_recursive(func, obj, arg) def rb_catch_obj(tag, func, data) catch tag do |caught| - func = RB_BLOCK_CALL_FUNC_SIGNATURE.bind(func) if NFI - Primitive.cext_unwrap(Primitive.call_with_c_mutex(func, [ - Primitive.cext_wrap(caught), - Primitive.cext_wrap(data), - 0, # argc - nil, # argv - nil, # blockarg + Primitive.cext_unwrap(Primitive.call_with_c_mutex(RB_BLOCK_CALL_FUNC_WRAPPER, [ + func, + Primitive.cext_wrap(caught), + Primitive.cext_wrap(data), + 0, # argc + nil, # argv + nil, # blockarg ])) end end @@ -1823,20 +1844,16 @@ def rb_time_interval_acceptable(time_val) end def rb_thread_create(fn, args) - fn = POINTER_TO_POINTER_SIGNATURE.bind(fn) if NFI Thread.new do - Primitive.call_with_c_mutex_and_frame(fn, [args], Primitive.caller_special_variables_if_available, nil) + Primitive.call_with_c_mutex_and_frame(POINTER_TO_POINTER_WRAPPER, [fn, args], Primitive.caller_special_variables_if_available, nil) end end def rb_thread_call_with_gvl(function, data) - function = POINTER_TO_POINTER_SIGNATURE.bind(function) if NFI - Primitive.call_with_c_mutex(function, [data]) + Primitive.call_with_c_mutex(POINTER_TO_POINTER_WRAPPER, [function, data]) end def rb_thread_call_without_gvl(function, data1, unblock, data2) - function = POINTER_TO_POINTER_SIGNATURE.bind(function) if NFI - unblock = POINTER_TO_VOID_SIGNATURE.bind(unblock) if NFI && !Primitive.nil?(unblock) Primitive.send_without_cext_lock( self, :rb_thread_call_without_gvl_inner, @@ -1845,13 +1862,16 @@ def rb_thread_call_without_gvl(function, data1, unblock, data2) end private def rb_thread_call_without_gvl_inner(function, data1, unblock, data2) - Primitive.call_with_unblocking_function(Thread.current, function, data1, unblock, data2) + Primitive.call_with_unblocking_function(Thread.current, + POINTER_TO_POINTER_WRAPPER, function, data1, + POINTER_TO_VOID_WRAPPER, unblock, data2) end def rb_iterate(iteration, iterated_object, callback, callback_arg) block = rb_block_proc wrapped_callback = proc do |block_arg| - Primitive.call_with_c_mutex_and_frame_and_unwrap(callback, [ + Primitive.call_with_c_mutex_and_frame_and_unwrap(RB_BLOCK_CALL_FUNC_WRAPPER, [ + callback, Primitive.cext_wrap(block_arg), Primitive.cext_wrap(callback_arg), 0, # argc @@ -1860,7 +1880,10 @@ def rb_iterate(iteration, iterated_object, callback, callback_arg) ], Primitive.cext_special_variables_from_stack, block) end Primitive.cext_unwrap( - Primitive.call_with_c_mutex_and_frame(iteration, [Primitive.cext_wrap(iterated_object)], Primitive.cext_special_variables_from_stack, wrapped_callback)) + Primitive.call_with_c_mutex_and_frame(POINTER_TO_POINTER_WRAPPER, [ + iteration, + Primitive.cext_wrap(iterated_object) + ], Primitive.cext_special_variables_from_stack, wrapped_callback)) end # From ruby.h @@ -1968,20 +1991,18 @@ def rb_define_hooked_variable(name, gvar, getter, setter) name = "$#{name}" unless name.start_with?('$') id = name.to_sym - getter = TWO_POINTERS_TO_POINTER_SIGNATURE.bind(getter) if NFI getter_proc = -> { Primitive.call_with_c_mutex_and_frame_and_unwrap( - getter, - [Primitive.cext_wrap(id), gvar], + POINTER2_TO_POINTER_WRAPPER, + [getter, Primitive.cext_wrap(id), gvar], Primitive.caller_special_variables_if_available, nil) } - setter = Primitive.interop_eval_nfi('(pointer,pointer,pointer):void').bind(setter) if NFI setter_proc = -> value { Primitive.call_with_c_mutex_and_frame( - setter, - [Primitive.cext_wrap(value), Primitive.cext_wrap(id), gvar], + POINTER3_TO_VOID_WRAPPER, + [setter, Primitive.cext_wrap(value), Primitive.cext_wrap(id), gvar], Primitive.caller_special_variables_if_available, nil) } @@ -2100,8 +2121,8 @@ def rb_fiber_current def rb_fiber_new(function, value) Fiber.new do |*args| - function = RB_BLOCK_CALL_FUNC_SIGNATURE.bind(function) if NFI - Primitive.call_with_c_mutex_and_frame_and_unwrap(function, [ + Primitive.call_with_c_mutex_and_frame_and_unwrap(RB_BLOCK_CALL_FUNC_WRAPPER, [ + function, Primitive.cext_wrap(args.first), # yieldarg nil, # procarg, 0, # argc diff --git a/lib/truffle/truffle/cext_ruby.rb b/lib/truffle/truffle/cext_ruby.rb index 72fd463bf280..f6a340ea1d3f 100644 --- a/lib/truffle/truffle/cext_ruby.rb +++ b/lib/truffle/truffle/cext_ruby.rb @@ -20,37 +20,49 @@ def rb_define_method(mod, name, function, argc) raise ArgumentError, "arity out of range: #{argc} for -2..15" end - if argc == -1 # (int argc, VALUE *argv, VALUE obj) - sig = '(sint32,pointer,pointer):uint64' - elsif argc == -2 # (VALUE obj, VALUE rubyArrayArgs) - sig = '(pointer,pointer):uint64' - elsif argc >= 0 # (VALUE obj); (VALUE obj, VALUE arg1); (VALUE obj, VALUE arg1, VALUE arg2); ... - sig = -"(#{Array.new(1 + argc, 'pointer').join(',')}):uint64" - end - - function = Primitive.interop_eval_nfi(sig).bind(function) if NFI - - method_body = Truffle::Graal.copy_captured_locals -> *args, &block do - if argc == -1 # (int argc, VALUE *argv, VALUE obj) - args = [args.size, Truffle::CExt.RARRAY_PTR(args), Primitive.cext_wrap(self)] - elsif argc == -2 # (VALUE obj, VALUE rubyArrayArgs) - args = [Primitive.cext_wrap(self), Primitive.cext_wrap(args)] - elsif argc >= 0 # (VALUE obj); (VALUE obj, VALUE arg1); (VALUE obj, VALUE arg1, VALUE arg2); ... - if args.size != argc - raise ArgumentError, "wrong number of arguments (given #{args.size}, expected #{argc})" + if NFI + wrapper = RB_DEFINE_METHOD_WRAPPERS[argc] + method_body = Truffle::Graal.copy_captured_locals -> *args, &block do + if argc == -1 # (int argc, VALUE *argv, VALUE obj) + args = [function, args.size, Truffle::CExt.RARRAY_PTR(args), Primitive.cext_wrap(self)] + elsif argc == -2 # (VALUE obj, VALUE rubyArrayArgs) + args = [function, Primitive.cext_wrap(self), Primitive.cext_wrap(args)] + elsif argc >= 0 # (VALUE obj); (VALUE obj, VALUE arg1); (VALUE obj, VALUE arg1, VALUE arg2); ... + if args.size != argc + raise ArgumentError, "wrong number of arguments (given #{args.size}, expected #{argc})" + end + args = [function, Primitive.cext_wrap(self), *args.map! { |arg| Primitive.cext_wrap(arg) }] end - args = [Primitive.cext_wrap(self), *args.map! { |arg| Primitive.cext_wrap(arg) }] - end - exc = $! - Primitive.thread_set_exception(nil) - # Using raw execute instead of #call here to avoid argument conversion + exc = $! + Primitive.thread_set_exception(nil) + # We must set block argument if given here so that the + # `rb_block_*` functions will be able to find it by walking the stack. + res = Primitive.call_with_c_mutex_and_frame_and_unwrap(wrapper, args, Primitive.caller_special_variables_if_available, block) + Primitive.thread_set_exception(exc) + res + end + else + method_body = Truffle::Graal.copy_captured_locals -> *args, &block do + if argc == -1 # (int argc, VALUE *argv, VALUE obj) + args = [args.size, Truffle::CExt.RARRAY_PTR(args), Primitive.cext_wrap(self)] + elsif argc == -2 # (VALUE obj, VALUE rubyArrayArgs) + args = [Primitive.cext_wrap(self), Primitive.cext_wrap(args)] + elsif argc >= 0 # (VALUE obj); (VALUE obj, VALUE arg1); (VALUE obj, VALUE arg1, VALUE arg2); ... + if args.size != argc + raise ArgumentError, "wrong number of arguments (given #{args.size}, expected #{argc})" + end + args = [Primitive.cext_wrap(self), *args.map! { |arg| Primitive.cext_wrap(arg) }] + end - # We must set block argument if given here so that the - # `rb_block_*` functions will be able to find it by walking the stack. - res = Primitive.call_with_c_mutex_and_frame_and_unwrap(function, args, Primitive.caller_special_variables_if_available, block) - Primitive.thread_set_exception(exc) - res + exc = $! + Primitive.thread_set_exception(nil) + # We must set block argument if given here so that the + # `rb_block_*` functions will be able to find it by walking the stack. + res = Primitive.call_with_c_mutex_and_frame_and_unwrap(function, args, Primitive.caller_special_variables_if_available, block) + Primitive.thread_set_exception(exc) + res + end end # Even if the argc is -2, the arity number diff --git a/mx.truffleruby/suite.py b/mx.truffleruby/suite.py index ebbbd915385a..99aa8b64125a 100644 --- a/mx.truffleruby/suite.py +++ b/mx.truffleruby/suite.py @@ -381,11 +381,13 @@ "buildDependencies": [ "sulong:SULONG_BOOTSTRAP_TOOLCHAIN", # graalvm-native-clang "sulong:SULONG_HOME", # polyglot.h + "truffle:TRUFFLE_NFI_NATIVE", # trufflenfi.h "TRUFFLERUBY-BOOTSTRAP-LAUNCHER", ], "buildEnv": { "TRUFFLERUBY_BOOTSTRAP_LAUNCHER": "/miniruby", "GRAALVM_TOOLCHAIN_CC": "", + "TRUFFLE_NFI_NATIVE_INCLUDE": "/include", }, "output": ".", "results": [ diff --git a/src/main/c/Makefile b/src/main/c/Makefile index 6d4291d65774..58f342735066 100644 --- a/src/main/c/Makefile +++ b/src/main/c/Makefile @@ -55,7 +55,7 @@ clean_truffleposix: $(Q) rm -f $(TRUFFLE_POSIX) truffleposix/*.o clean_trampoline: - $(Q) rm -f $(TRAMPOLINE) cext-trampoline/trampoline.c cext-trampoline/*.o + $(Q) rm -f $(TRAMPOLINE) cext-trampoline/trampoline.c cext/wrappers.c cext-trampoline/*.o clean_cexts: $(Q) rm -f cext/Makefile cext/*.o $(LIBTRUFFLERUBY) diff --git a/src/main/c/cext-trampoline/Makefile b/src/main/c/cext-trampoline/Makefile index 24dbd25f5527..596f32969a7d 100644 --- a/src/main/c/cext-trampoline/Makefile +++ b/src/main/c/cext-trampoline/Makefile @@ -23,4 +23,4 @@ libtrufflerubytrampoline.$(SOEXT): $(OBJECT_FILES) Makefile $(Q) $(CC) -shared $(LDFLAGS) -o $@ $(OBJECT_FILES) %.o: %.c Makefile - $(Q) $(CC) -o $@ -c $(CFLAGS) $(LDFLAGS) -I$(RUBY_HDR_DIR) -I$(ROOT)/lib/cext/include/stubs $< + $(Q) $(CC) -o $@ -c $(CFLAGS) $(LDFLAGS) -I$(RUBY_HDR_DIR) -I$(ROOT)/lib/cext/include/stubs -I$(TRUFFLE_NFI_NATIVE_INCLUDE) $< diff --git a/src/main/c/cext/call.c b/src/main/c/cext/call.c index fe2394ffaf7a..01c9dae594be 100644 --- a/src/main/c/cext/call.c +++ b/src/main/c/cext/call.c @@ -16,7 +16,7 @@ void rb_error_arity(int argc, int min, int max) { rb_exc_raise(rb_exc_new3(rb_eArgError, rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_arity_error_string", argc, min, max)))); } -VALUE rb_iterate(VALUE (*function)(), VALUE arg1, VALUE (*block)(), VALUE arg2) { +VALUE rb_iterate(VALUE (*function)(VALUE), VALUE arg1, rb_block_call_func_t block, VALUE arg2) { return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_iterate", function, rb_tr_unwrap(arg1), block, rb_tr_unwrap(arg2))); } @@ -144,42 +144,12 @@ void *rb_thread_call_with_gvl(gvl_call *function, void *data1) { return polyglot_invoke(RUBY_CEXT, "rb_thread_call_with_gvl", function, data1); } -struct gvl_call_data { - gvl_call *function; - void* data; -}; - -struct unblock_function_data { - rb_unblock_function_t* function; - void* data; -}; - -static void* call_gvl_call_function(void *data) { - struct gvl_call_data* s = (struct gvl_call_data*) data; - return s->function(s->data); -} - -static void call_unblock_function(void *data) { - struct unblock_function_data* s = (struct unblock_function_data*) data; - s->function(s->data); -} - void* rb_thread_call_without_gvl(gvl_call *function, void *data1, rb_unblock_function_t *unblock_function, void *data2) { - // wrap functions to handle native functions - // TODO is it needed if we .bind() in Ruby code? - struct gvl_call_data call_struct = { function, data1 }; - struct unblock_function_data unblock_struct = { unblock_function, data2 }; - - rb_unblock_function_t *wrapped_unblock_function; if (unblock_function == RUBY_UBF_IO) { - wrapped_unblock_function = (rb_unblock_function_t*) rb_tr_unwrap(Qnil); - } else { - wrapped_unblock_function = call_unblock_function; + unblock_function = (rb_unblock_function_t*) rb_tr_unwrap(Qnil); } - return polyglot_invoke(RUBY_CEXT, "rb_thread_call_without_gvl", - call_gvl_call_function, &call_struct, - wrapped_unblock_function, &unblock_struct); + return polyglot_invoke(RUBY_CEXT, "rb_thread_call_without_gvl", function, data1, unblock_function, data2); } void* rb_thread_call_without_gvl2(gvl_call *function, void *data1, rb_unblock_function_t *unblock_function, void *data2) { diff --git a/src/main/c/cext/exception.c b/src/main/c/cext/exception.c index 27f4cc7e7b55..61e8165a69bd 100644 --- a/src/main/c/cext/exception.c +++ b/src/main/c/cext/exception.c @@ -26,7 +26,7 @@ VALUE rb_exc_new_str(VALUE exception_class, VALUE message) { } void rb_exc_raise(VALUE exception) { - RUBY_CEXT_INVOKE_NO_WRAP("rb_exc_raise", exception); + RUBY_CEXT_INVOKE_NO_WRAP("rb_exc_raise", exception); // TODO UNREACHABLE; } diff --git a/src/main/java/org/truffleruby/cext/CExtNodes.java b/src/main/java/org/truffleruby/cext/CExtNodes.java index 5f96cd38c5e5..b0a214dd82df 100644 --- a/src/main/java/org/truffleruby/cext/CExtNodes.java +++ b/src/main/java/org/truffleruby/cext/CExtNodes.java @@ -292,7 +292,7 @@ Object callWithCExtLock(Object receiver, RubyArray argsArray, @Cached ArrayToObjectArrayNode arrayToObjectArrayNode, @Cached TranslateInteropExceptionNode translateInteropExceptionNode, @Cached InlinedConditionProfile ownedProfile) { - final Object[] args = arrayToObjectArrayNode.executeToObjectArray(argsArray); + Object[] args = arrayToObjectArrayNode.executeToObjectArray(argsArray); if (getContext().getOptions().CEXT_LOCK) { final ReentrantLock lock = getContext().getCExtensionsLock(); diff --git a/src/main/java/org/truffleruby/core/array/ArrayUtils.java b/src/main/java/org/truffleruby/core/array/ArrayUtils.java index 37999e6ea6aa..b4f893a37bca 100644 --- a/src/main/java/org/truffleruby/core/array/ArrayUtils.java +++ b/src/main/java/org/truffleruby/core/array/ArrayUtils.java @@ -224,6 +224,11 @@ public static Object[] copy(Object[] array) { return copy; } + public static Object[] shift(Object[] array) { + assert array.length > 0; + return extractRange(array, 1, array.length); + } + public static Object[] unshift(Object[] array, Object element) { final Object[] newArray = new Object[1 + array.length]; newArray[0] = element; diff --git a/src/main/java/org/truffleruby/core/thread/ThreadNodes.java b/src/main/java/org/truffleruby/core/thread/ThreadNodes.java index 02e1454910b0..f9d970a3611e 100644 --- a/src/main/java/org/truffleruby/core/thread/ThreadNodes.java +++ b/src/main/java/org/truffleruby/core/thread/ThreadNodes.java @@ -657,8 +657,15 @@ public abstract static class CallWithUnblockingFunctionNode extends PrimitiveArr @SuppressWarnings("truffle-neverdefault") // GR-43642 @Specialization(limit = "getCacheLimit()") - static Object call(RubyThread thread, Object function, Object arg, Object unblocker, Object unblockerArg, - @CachedLibrary("function") InteropLibrary receivers, + static Object call( + RubyThread thread, + Object wrapper, + Object function, + Object arg, + Object unblockWrapper, + Object unblocker, + Object unblockerArg, + @CachedLibrary("wrapper") InteropLibrary receivers, @Cached TranslateInteropExceptionNode translateInteropExceptionNode, @Bind("this") Node node, @Cached("new(node, receivers, translateInteropExceptionNode)") BlockingCallInterruptible blockingCallInterruptible) { @@ -667,33 +674,36 @@ static Object call(RubyThread thread, Object function, Object arg, Object unbloc if (unblocker == nil) { interrupter = threadManager.getNativeCallInterrupter(); } else { - interrupter = makeInterrupter(getContext(node), unblocker, unblockerArg); + interrupter = makeInterrupter(getContext(node), unblockWrapper, unblocker, unblockerArg); } - final Object[] args = { arg }; + final Object[] args = { function, arg }; return ThreadManager.executeBlockingCall( thread, interrupter, - function, + wrapper, args, blockingCallInterruptible, node); } @TruffleBoundary - private static Interrupter makeInterrupter(RubyContext context, Object function, Object argument) { - return new CExtInterrupter(context, function, argument); + private static Interrupter makeInterrupter(RubyContext context, Object wrapper, Object function, + Object argument) { + return new CExtInterrupter(context, wrapper, function, argument); } private static final class CExtInterrupter implements Interrupter { private final RubyContext context; + private final Object wrapper; private final Object function; private final Object argument; - public CExtInterrupter(RubyContext context, Object function, Object argument) { - assert InteropLibrary.getUncached().isExecutable(function); + public CExtInterrupter(RubyContext context, Object wrapper, Object function, Object argument) { + assert InteropLibrary.getUncached().isExecutable(wrapper); this.context = context; + this.wrapper = wrapper; this.function = function; this.argument = argument; } @@ -720,7 +730,7 @@ public void interrupt(Thread thread) { } try { - InteropLibrary.getUncached().execute(function, argument); + InteropLibrary.getUncached().execute(wrapper, function, argument); } catch (InteropException e) { throw CompilerDirectives.shouldNotReachHere(e); } finally { diff --git a/src/main/java/org/truffleruby/language/loader/FeatureLoader.java b/src/main/java/org/truffleruby/language/loader/FeatureLoader.java index 1dd6f3b19782..45ac00611928 100644 --- a/src/main/java/org/truffleruby/language/loader/FeatureLoader.java +++ b/src/main/java/org/truffleruby/language/loader/FeatureLoader.java @@ -461,15 +461,11 @@ public void ensureCExtImplementationLoaded(String feature, RequireNode requireNo final String rubyLibPath = language.getRubyHome() + "/lib/cext/libtruffleruby" + Platform.LIB_SUFFIX; final Object library = loadCExtLibRuby(rubyLibPath, feature, requireNode); - var initFunction = findFunctionInLibrary(library, "rb_tr_init", rubyLibPath); - final InteropLibrary interop = InteropLibrary.getUncached(); language.getCurrentFiber().extensionCallStack.push(false, nil, nil); try { // Truffle::CExt.register_libtruffleruby(libtruffleruby) - interop.invokeMember(truffleCExt, "register_libtruffleruby", library); - // rb_tr_init(Truffle::CExt) - interop.execute(initFunction, truffleCExt); + interop.invokeMember(truffleCExt, "init_libtruffleruby", library); if (!context.getOptions().CEXTS_SULONG) { // Truffle::CExt.init_libtrufflerubytrampoline(libtrampoline) diff --git a/tool/generate-cext-trampoline.rb b/tool/generate-cext-trampoline.rb index 0f2c1bc47f01..49d492b72b49 100755 --- a/tool/generate-cext-trampoline.rb +++ b/tool/generate-cext-trampoline.rb @@ -41,7 +41,7 @@ functions = [] Dir["src/main/c/cext/*.c"].sort.each do |file| - next if %w[cext_constants.c ruby.c st.c].include?(File.basename(file)) + next if %w[cext_constants.c wrappers.c ruby.c st.c].include?(File.basename(file)) contents = File.read(file) found_functions = contents.scan(function_regexp) @@ -91,9 +91,90 @@ #include -// Functions +#include + +#include + +static TruffleContext* nfiContext = NULL; + +static __thread jmp_buf *rb_tr_jmp_buf = NULL; + +static inline bool rb_tr_exception_from_java(void) { + TruffleEnv* env = (*nfiContext)->getTruffleEnv(nfiContext); + return (*env)->exceptionCheck(env); +} + +RBIMPL_ATTR_NORETURN() +static inline void rb_tr_exception_from_java_jump(void) { + if (LIKELY(rb_tr_jmp_buf != NULL)) { + // fprintf(stderr, "There was an exception, longjmp()'ing"); + RUBY_LONGJMP(*rb_tr_jmp_buf, 1); + } else { + fprintf(stderr, "ERROR: There was an exception in Java but rb_tr_jmp_buf is NULL."); + abort(); + } +} + +C + + File.open("src/main/c/cext/wrappers.c", "w") do |sulong| + sulong.puts < + +C + + signatures = [ + ['rb_tr_setjmp_wrapper_pointer1_to_void', '(VALUE arg):void'], + ['rb_tr_setjmp_wrapper_pointer2_to_void', '(VALUE tracepoint, void *data):void'], + ['rb_tr_setjmp_wrapper_pointer3_to_void', '(VALUE val, ID id, VALUE *data):void'], + ['rb_tr_setjmp_wrapper_pointer3_to_int', '(VALUE key, VALUE val, VALUE arg):int'], + ['rb_tr_setjmp_wrapper_pointer1_to_size_t', '(const void *arg):size_t'], + ['rb_tr_setjmp_wrapper_int_pointer2_to_pointer', '(int argc, VALUE *argv, VALUE obj):VALUE'], + ['rb_tr_setjmp_wrapper_pointer2_int_to_pointer', '(VALUE g, VALUE h, int r):VALUE'], + ['rb_tr_setjmp_wrapper_pointer2_int_pointer2_to_pointer', '(VALUE yielded_arg, VALUE callback_arg, int argc, const VALUE *argv, VALUE blockarg):VALUE'], + ] + (1..16).each do |arity| + signatures << [ + "rb_tr_setjmp_wrapper_pointer#{arity}_to_pointer", + "(#{(1..arity).map { |i| "VALUE arg#{i}" }.join(', ')}):VALUE" + ] + end + + signatures.each do |function_name, signature| + argument_types, return_type = signature.split(':') + argument_types = argument_types.delete_prefix('(').delete_suffix(')') + void = return_type == 'void' + f.puts <getTruffleContext(env); + C functions.each do |declaration, return_type, function_name, argument_types| f.puts " impl_#{function_name} = get_libtruffleruby_function(\"#{function_name}\");" From aa06b18d3ba2907665baaaf73a12da3f3a57c37a Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 20 Oct 2023 13:52:32 +0200 Subject: [PATCH 06/50] Fix the C ext backtraces test --- test/truffle/cexts/backtraces/bin/backtraces | 6 +++--- test/truffle/cexts/backtraces/expected.txt | 19 ++++++++----------- .../backtraces/ext/backtraces/backtraces.c | 4 ---- tool/jt.rb | 5 ++++- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/test/truffle/cexts/backtraces/bin/backtraces b/test/truffle/cexts/backtraces/bin/backtraces index 847c52192d38..222835c0aa96 100644 --- a/test/truffle/cexts/backtraces/bin/backtraces +++ b/test/truffle/cexts/backtraces/bin/backtraces @@ -1,6 +1,6 @@ #!/usr/bin/env ruby require 'backtraces' - +abort "This test's expected output assumes not using --cexts-sulong" if Truffle::Boot.get_option('cexts-sulong') TEST_DIR = File.expand_path("../..", __FILE__) SRC_DIR = File.expand_path("../../../../../..", __FILE__) @@ -11,7 +11,7 @@ def puts_backtrace(backtrace) end puts "Test error in Ruby => C ext support => Ruby" -RB_FUNCALLV = Truffle::Interop.import('rb_funcallv') +RB_FUNCALLV = Truffle::CExt::LIBTRUFFLERUBY[:rb_funcallv] def callback raise 'Ruby callback error' @@ -35,7 +35,7 @@ end puts puts "Test with an internal error from rb_mutex_lock()" LOCK = Mutex.new -RB_MUTEX_LOCK = Truffle::Interop.import('rb_mutex_lock') +RB_MUTEX_LOCK = Truffle::CExt::LIBTRUFFLERUBY[:rb_mutex_lock] LOCK.lock diff --git a/test/truffle/cexts/backtraces/expected.txt b/test/truffle/cexts/backtraces/expected.txt index 0efe7fe2453f..3fb87f3957db 100644 --- a/test/truffle/cexts/backtraces/expected.txt +++ b/test/truffle/cexts/backtraces/expected.txt @@ -19,22 +19,20 @@ Test with an internal error from rb_mutex_lock() Test error in Ruby => C ext => Ruby => C ext Proc => Ruby /bin/backtraces:57:in `block in bar': error (RuntimeError) - from /lib/truffle/truffle/cext.rb:n:in `rb_funcall' - from /ext/backtraces/backtraces.c:5:in `baz' + from /lib/truffle/truffle/cext.rb:n:in `rb_funcallv' + from /src/main/c/cext/call.c:n:in `rb_funcallv' from /lib/truffle/truffle/cext.rb:n:in `block in rb_proc_new' from /bin/backtraces:57:in `bar' - from /lib/truffle/truffle/cext.rb:n:in `rb_funcall' - from /ext/backtraces/backtraces.c:9:in `foo' + from /lib/truffle/truffle/cext.rb:n:in `rb_funcallv' + from /src/main/c/cext/call.c:n:in `rb_funcallv' from /lib/truffle/truffle/cext_ruby.rb:n:in `foo' from /bin/backtraces:62:in `
' Test error in the callback to the Sulong-instrinsified qsort() [1, 2, 3, 4] /bin/backtraces:74:in `block in
': error from Ruby called from instrinsified qsort() (RuntimeError) - from /lib/truffle/truffle/cext.rb:n:in `rb_funcall' - from /ext/backtraces/backtraces.c:17:in `compare_function' - from com.oracle.truffle.llvm.libraries.bitcode/src/qsort.c:n:in `qsort' - from /ext/backtraces/backtraces.c:25:in `call_qsort' + from /lib/truffle/truffle/cext.rb:n:in `rb_funcallv' + from /src/main/c/cext/call.c:n:in `rb_funcallv' from /lib/truffle/truffle/cext_ruby.rb:n:in `qsort' from /bin/backtraces:73:in `
' @@ -42,8 +40,7 @@ Test error in callback from a native function 42 /bin/backtraces:86:in `error_helper': error from Ruby called from Sulong called from native callback (RuntimeError) from /bin/backtraces:91:in `block in
' - from /lib/truffle/truffle/cext.rb:n:in `rb_funcall' - from /ext/backtraces/backtraces.c:38:in `sulong_callback' - from /ext/backtraces/backtraces.c:44:in `native_callback' + from /lib/truffle/truffle/cext.rb:n:in `rb_funcallv' + from /src/main/c/cext/call.c:n:in `rb_funcallv' from /lib/truffle/truffle/cext_ruby.rb:n:in `native_callback' from /bin/backtraces:90:in `
' diff --git a/test/truffle/cexts/backtraces/ext/backtraces/backtraces.c b/test/truffle/cexts/backtraces/ext/backtraces/backtraces.c index db4aef0c5a79..deac0d4aee7b 100644 --- a/test/truffle/cexts/backtraces/ext/backtraces/backtraces.c +++ b/test/truffle/cexts/backtraces/ext/backtraces/backtraces.c @@ -49,8 +49,4 @@ void Init_backtraces() { rb_define_module_function(module, "foo", &foo, 1); rb_define_module_function(module, "qsort", call_qsort, 1); rb_define_module_function(module, "native_callback", native_callback, 1); - - // used in the main script of the test - polyglot_export("rb_funcallv", &rb_funcallv); - polyglot_export("rb_mutex_lock", &rb_mutex_lock); } diff --git a/tool/jt.rb b/tool/jt.rb index 2982d1b4181c..f77aac7af957 100755 --- a/tool/jt.rb +++ b/tool/jt.rb @@ -1442,7 +1442,10 @@ def retag(*args) output_file = 'cext-output.txt' dir = "test/truffle/cexts/#{test_name}" cextc(dir) - run_ruby "-I#{dir}/lib", "#{dir}/bin/#{test_name}", out: output_file + script = "#{dir}/bin/#{test_name}" + # bin/backtraces relies on being run with an absolute path for __FILE__ + script = "#{TRUFFLERUBY_DIR}/#{script}" if test_name == 'backtraces' + run_ruby "-I#{dir}/lib", script, out: output_file begin actual = File.read(output_file) expected_file = "#{dir}/expected.txt" From 1ad36ca26f933e63870cf2b530d06439da0facfe Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 20 Oct 2023 14:05:21 +0200 Subject: [PATCH 07/50] Fix ruby_native_thread_p() on native --- spec/tags/optional/capi/thread_tags.txt | 1 - tool/generate-cext-trampoline.rb | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) delete mode 100644 spec/tags/optional/capi/thread_tags.txt diff --git a/spec/tags/optional/capi/thread_tags.txt b/spec/tags/optional/capi/thread_tags.txt deleted file mode 100644 index f4fce423ec49..000000000000 --- a/spec/tags/optional/capi/thread_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails(TODO, does not work yet when run natively):C-API Thread function ruby_native_thread_p returns zero for a non ruby thread diff --git a/tool/generate-cext-trampoline.rb b/tool/generate-cext-trampoline.rb index 49d492b72b49..93520a6aef96 100755 --- a/tool/generate-cext-trampoline.rb +++ b/tool/generate-cext-trampoline.rb @@ -210,6 +210,12 @@ f.puts " }" f.puts " UNREACHABLE;" if no_return else + if function_name == 'ruby_native_thread_p' + # We need to check if the current thread is attached first before calling to Sulong/Ruby + f.puts " if ((*nfiContext)->getTruffleEnv(nfiContext) == NULL) {" + f.puts " return 0;" + f.puts " }" + end f.puts " #{return_type} _result = impl_#{function_name}(#{argument_names});" f.puts " if (UNLIKELY(rb_tr_exception_from_java())) {" f.puts " rb_tr_exception_from_java_jump();" From a9ac3bfa422e8346c3759ae94eb54aad1bfdec9d Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 20 Oct 2023 14:24:56 +0200 Subject: [PATCH 08/50] Fixes to pass `jt test cexts werror` --- lib/cext/include/ruby/internal/error.h | 12 ------------ lib/cext/include/ruby/internal/intern/gc.h | 1 + 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/cext/include/ruby/internal/error.h b/lib/cext/include/ruby/internal/error.h index f5a2f77a2ea3..34b426cb01a6 100644 --- a/lib/cext/include/ruby/internal/error.h +++ b/lib/cext/include/ruby/internal/error.h @@ -557,13 +557,7 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 3, 4) * @param[in] line The number corresponding to Ruby level `__LINE__`. * @param[in] fmt Format specifier string compatible with rb_sprintf(). */ -#ifdef TRUFFLERUBY -static inline void rb_compile_warning(const char *file, int line, const char *fmt, ...) { - rb_tr_not_implemented("rb_compile_warning"); -} -#else void rb_compile_warning(const char *file, int line, const char *fmt, ...); -#endif RBIMPL_ATTR_NONNULL((1)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 2) @@ -621,13 +615,7 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 3, 4) * @param[in] line The number corresponding to Ruby level `__LINE__`. * @param[in] fmt Format specifier string compatible with rb_sprintf(). */ -#ifdef TRUFFLERUBY -static inline void rb_compile_warn(const char *file, int line, const char *fmt, ...) { - rb_tr_not_implemented("rb_compile_warn"); -} -#else void rb_compile_warn(const char *file, int line, const char *fmt, ...); -#endif RBIMPL_ATTR_NONNULL((2, 4)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 4, 5) diff --git a/lib/cext/include/ruby/internal/intern/gc.h b/lib/cext/include/ruby/internal/intern/gc.h index d44aafbc3e77..a309fe12fcf7 100644 --- a/lib/cext/include/ruby/internal/intern/gc.h +++ b/lib/cext/include/ruby/internal/intern/gc.h @@ -388,6 +388,7 @@ VALUE rb_gc_latest_gc_info(VALUE key_or_buf); #ifdef TRUFFLERUBY static inline void rb_gc_adjust_memory_usage(ssize_t diff) { // No-op for now + (void) diff; // To silence -Wunused-parameter } #else void rb_gc_adjust_memory_usage(ssize_t diff); From 0d966be9966da3c7bdb50bb1204a2c22caa948f3 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 20 Oct 2023 14:41:42 +0200 Subject: [PATCH 09/50] Fixes for C++ extensions --- lib/cext/include/ruby/internal/core/rdata.h | 4 +++- .../include/ruby/internal/core/rtypeddata.h | 5 ++++- .../include/ruby/internal/encoding/sprintf.h | 2 +- .../include/truffleruby/truffleruby-pre.h | 22 +++++++++---------- lib/mri/mkmf.rb | 5 ++++- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/lib/cext/include/ruby/internal/core/rdata.h b/lib/cext/include/ruby/internal/core/rdata.h index 2e08842b8ed3..f8b746f7c379 100644 --- a/lib/cext/include/ruby/internal/core/rdata.h +++ b/lib/cext/include/ruby/internal/core/rdata.h @@ -61,7 +61,6 @@ * @return The passed object casted to ::RData. */ #ifdef TRUFFLERUBY -struct RData* rb_tr_rdata(VALUE object); #define RDATA(obj) rb_tr_rdata(obj) #else #define RDATA(obj) RBIMPL_CAST((struct RData *)(obj)) @@ -158,6 +157,9 @@ struct RData { }; RBIMPL_SYMBOL_EXPORT_BEGIN() +#ifdef TRUFFLERUBY +struct RData* rb_tr_rdata(VALUE object); +#endif /** * This is the primitive way to wrap an existing C struct into ::RData. diff --git a/lib/cext/include/ruby/internal/core/rtypeddata.h b/lib/cext/include/ruby/internal/core/rtypeddata.h index fb3c1c888b8c..33e43e11e2c9 100644 --- a/lib/cext/include/ruby/internal/core/rtypeddata.h +++ b/lib/cext/include/ruby/internal/core/rtypeddata.h @@ -92,7 +92,6 @@ * @return The passed object casted to ::RTypedData. */ #ifdef TRUFFLERUBY -struct RTypedData* rb_tr_rtypeddata(VALUE object); #define RTYPEDDATA(obj) rb_tr_rtypeddata(obj) #else #define RTYPEDDATA(obj) RBIMPL_CAST((struct RTypedData *)(obj)) @@ -370,6 +369,10 @@ struct RTypedData { }; RBIMPL_SYMBOL_EXPORT_BEGIN() +#ifdef TRUFFLERUBY +struct RTypedData* rb_tr_rtypeddata(VALUE object); +#endif + RBIMPL_ATTR_NONNULL((3)) /** * This is the primitive way to wrap an existing C struct into ::RTypedData. diff --git a/lib/cext/include/ruby/internal/encoding/sprintf.h b/lib/cext/include/ruby/internal/encoding/sprintf.h index 51f6c4ed0c2d..daa3243be320 100644 --- a/lib/cext/include/ruby/internal/encoding/sprintf.h +++ b/lib/cext/include/ruby/internal/encoding/sprintf.h @@ -29,6 +29,7 @@ #include "ruby/internal/encoding/encoding.h" #include "ruby/internal/value.h" +RBIMPL_SYMBOL_EXPORT_BEGIN() #ifdef TRUFFLERUBY // We need to declare rb_enc_vsprintf() before rb_enc_sprintf() RBIMPL_ATTR_NONNULL((2)) @@ -36,7 +37,6 @@ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 0) VALUE rb_enc_vsprintf(rb_encoding *enc, const char *fmt, va_list ap); #endif -RBIMPL_SYMBOL_EXPORT_BEGIN() RBIMPL_ATTR_NONNULL((2)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3) /** diff --git a/lib/cext/include/truffleruby/truffleruby-pre.h b/lib/cext/include/truffleruby/truffleruby-pre.h index 99fcaab1d228..70178ec4d8ed 100644 --- a/lib/cext/include/truffleruby/truffleruby-pre.h +++ b/lib/cext/include/truffleruby/truffleruby-pre.h @@ -14,12 +14,6 @@ #ifndef TRUFFLERUBY_PRE_H #define TRUFFLERUBY_PRE_H -#if defined(__cplusplus) -extern "C" { -#endif - -RUBY_SYMBOL_EXPORT_BEGIN - // Configuration // We disable USE_FLONUM, as we do not use pointer tagging for Float. @@ -39,6 +33,17 @@ RUBY_SYMBOL_EXPORT_BEGIN // Skip DTrace-generated code #define DTRACE_PROBES_DISABLED 1 +// Declare VALUE for below + +#include "ruby/defines.h" +#include "ruby/internal/value.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +RUBY_SYMBOL_EXPORT_BEGIN + // Support #include @@ -52,11 +57,6 @@ const char* rb_tr_abi_version(void) { return TRUFFLERUBY_ABI_VERSION; } -// Declare VALUE for below - -#include "ruby/defines.h" -#include "ruby/internal/value.h" - // Helpers #ifndef offsetof diff --git a/lib/mri/mkmf.rb b/lib/mri/mkmf.rb index 5cac93e57987..258416a28818 100644 --- a/lib/mri/mkmf.rb +++ b/lib/mri/mkmf.rb @@ -2346,11 +2346,14 @@ def depend_rules(depend) # +VPATH+ and added to the list of +INCFLAGS+. # def create_makefile(target, srcprefix = nil) - if defined?(::TruffleRuby) and $LIBRUBYARG == nil + if defined?(::TruffleRuby) and ($LIBRUBYARG == nil or !Truffle::Boot.get_option('building-core-cexts')) # $LIBRUBYARG was explicitly unset, the built library is not a C extension but used with FFI (e.g., sassc does). # Since $LIBRUBYARG is unset we won't link to libgraalvm-llvm.so, which is expected. # In the case the library uses C++ code, libc++.so/libc++abi.so will be linked and needs to be found by NFI. # The toolchain does not pass -rpath automatically for libc++.so/libc++abi.so, so we do it. + # + # We also add this rpath for C++ extensions (which have $LIBRUBYARG != nil) + # so that libc++.so/libc++abi.so can be found. libcxx_dir = ::Truffle::Boot.toolchain_paths(:LD_LIBRARY_PATH) raise 'libcxx_dir should not be empty' if libcxx_dir.empty? $DLDFLAGS << " -rpath #{libcxx_dir}" From 8404d4fdfc5be0ecac158dc64f83a3b9c9a45f19 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 20 Oct 2023 15:37:14 +0200 Subject: [PATCH 10/50] Fix rb_type() for values >= 1**62 and < 1**63 and corresponding negative values * So rb_type(value) == T_FIXNUM is equivalent to RB_FIXNUM_P(). * Fix related rb_big* functions so they handle such values (which are not RubyBignum objects). --- lib/truffle/truffle/cext.rb | 17 ++++++++++++++++- src/main/c/cext/integer.c | 4 ++-- src/main/c/cext/numeric.c | 8 ++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/lib/truffle/truffle/cext.rb b/lib/truffle/truffle/cext.rb index 70afb5d1a798..38a937708d5b 100644 --- a/lib/truffle/truffle/cext.rb +++ b/lib/truffle/truffle/cext.rb @@ -102,6 +102,9 @@ module Truffle::CExt T_MASK = 0x1f + RUBY_FIXNUM_MIN = -(1 << 62) + RUBY_FIXNUM_MAX = (1 << 62) - 1 + # This list of types is derived from MRI's error.c builtin_types array. BUILTIN_TYPES = [ '', @@ -276,7 +279,11 @@ def rb_tr_cached_type(value, type) T_OBJECT end elsif type == T_FIXNUM - Truffle::Type.fits_into_long?(value) ? T_FIXNUM : T_BIGNUM + if RUBY_FIXNUM_MIN <= value && value <= RUBY_FIXNUM_MAX + T_FIXNUM + else + T_BIGNUM + end else type end @@ -427,6 +434,14 @@ def rb_num2long(val) Primitive.rb_num2long(val) end + def rb_big_sign(val) + val >= 0 + end + + def rb_big_cmp(x, y) + x <=> y + end + def rb_big2dbl(val) Truffle::Type.rb_big2dbl(val) end diff --git a/src/main/c/cext/integer.c b/src/main/c/cext/integer.c index dbaf5e34724c..91bb3ed6cea8 100644 --- a/src/main/c/cext/integer.c +++ b/src/main/c/cext/integer.c @@ -136,7 +136,7 @@ size_t rb_absint_size(VALUE value, int *nlz_bits_ret) { } int rb_big_sign(VALUE x) { - return RTEST(RUBY_INVOKE(x, ">=", INT2FIX(0))) ? 1 : 0; + return RTEST(RUBY_CEXT_INVOKE("rb_big_sign", x)) ? 1 : 0; } int rb_cmpint(VALUE val, VALUE a, VALUE b) { @@ -144,7 +144,7 @@ int rb_cmpint(VALUE val, VALUE a, VALUE b) { } VALUE rb_big_cmp(VALUE x, VALUE y) { - return RUBY_INVOKE(x, "<=>", y); + return RUBY_CEXT_INVOKE("rb_big_cmp", x, y); } void rb_big_pack(VALUE val, unsigned long *buf, long num_longs) { diff --git a/src/main/c/cext/numeric.c b/src/main/c/cext/numeric.c index cb02ae431a38..aead1875d9bb 100644 --- a/src/main/c/cext/numeric.c +++ b/src/main/c/cext/numeric.c @@ -230,10 +230,6 @@ long rb_big2long(VALUE x) { return polyglot_as_i64(RUBY_CEXT_INVOKE_NO_WRAP("rb_num2long", x)); } -VALUE rb_big2str(VALUE x, int base) { - return rb_tr_wrap(polyglot_invoke(rb_tr_unwrap(x), "to_s", base)); -} - unsigned long rb_big2ulong(VALUE x) { return polyglot_as_i64(RUBY_CEXT_INVOKE_NO_WRAP("rb_num2ulong", x)); } @@ -262,6 +258,10 @@ VALUE rb_fix2str(VALUE x, int base) { return RUBY_CEXT_INVOKE("rb_fix2str", x, INT2FIX(base)); } +VALUE rb_big2str(VALUE x, int base) { + return rb_fix2str(x, base); +} + VALUE rb_to_int(VALUE object) { return RUBY_CEXT_INVOKE("rb_to_int", object); } From 78a6fc81d54d5d20048625006986e3b0fa4380a5 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 21 Oct 2023 13:45:22 +0200 Subject: [PATCH 11/50] RB_INT2FIX() is constexpr now that we use the original definition --- lib/cext/include/ruby/internal/arithmetic/long.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/cext/include/ruby/internal/arithmetic/long.h b/lib/cext/include/ruby/internal/arithmetic/long.h index 7dab59114acc..b4e3191a1057 100644 --- a/lib/cext/include/ruby/internal/arithmetic/long.h +++ b/lib/cext/include/ruby/internal/arithmetic/long.h @@ -106,10 +106,8 @@ int rb_long2int(long value); #endif RBIMPL_SYMBOL_EXPORT_END() -#ifndef TRUFFLERUBY RBIMPL_ATTR_CONST_UNLESS_DEBUG() RBIMPL_ATTR_CONSTEXPR_UNLESS_DEBUG(CXX14) -#endif RBIMPL_ATTR_ARTIFICIAL() /** * Converts a C's `long` into an instance of ::rb_cInteger. From b74b4f7d82cd8a3b1fe7220afeea85e487ef5ca6 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 21 Oct 2023 13:55:43 +0200 Subject: [PATCH 12/50] Workaround grpc_c.so needing symbols from libz and libssl but not depending on them natively --- test/truffle/cexts/grpc/grpc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/truffle/cexts/grpc/grpc.sh b/test/truffle/cexts/grpc/grpc.sh index a0ec61d4ca9c..ac060d9bcef2 100755 --- a/test/truffle/cexts/grpc/grpc.sh +++ b/test/truffle/cexts/grpc/grpc.sh @@ -19,7 +19,7 @@ cd "$truffle/cexts/grpc" || exit 1 bundle config --local cache_path "$gem_test_pack/gem-cache" bundle install --local --no-cache -output=$(bundle exec ruby -e 'require "grpc"; p GRPC') +output=$(bundle exec ruby -rzlib -ropenssl -e 'require "grpc"; p GRPC') if [ "$output" = 'GRPC' ]; then echo Success From f8bc852b88e58a0a4d9bc944695efaa9f0d6acc2 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 24 Oct 2023 17:45:56 +0200 Subject: [PATCH 13/50] CI: Print RbConfig::MAKEFILE_CONFIG on CRuby on all platforms --- ci.jsonnet | 1 + 1 file changed, 1 insertion(+) diff --git a/ci.jsonnet b/ci.jsonnet index c5e3575e2758..f7ececf2f338 100644 --- a/ci.jsonnet +++ b/ci.jsonnet @@ -381,6 +381,7 @@ local part_definitions = { generate_native_config: { setup+: [ + ["ruby", "-e", "pp RbConfig::MAKEFILE_CONFIG"], # For convenience ["mx", "sforceimports"], # clone the graal repo ["mx", "-p", "../graal/sulong", "build"], ["set-export", "TOOLCHAIN_PATH", ["mx", "-p", "../graal/sulong", "lli", "--print-toolchain-path"]], From e73d648867798a47d583d72bccc372b37f301616 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 20 Oct 2023 16:45:43 +0200 Subject: [PATCH 14/50] Use the system toolchain to compile C extensions --- lib/mri/mkmf.rb | 18 ++++--- lib/truffle/rbconfig.rb | 47 +++++++++++++++---- mx.truffleruby/mx_truffleruby.py | 4 ++ src/main/c/Makefile | 4 +- src/main/c/cext-trampoline/st.c | 2 + src/main/c/cext/st.c | 2 + .../backtrace/BacktraceFormatter.java | 5 +- 7 files changed, 62 insertions(+), 20 deletions(-) diff --git a/lib/mri/mkmf.rb b/lib/mri/mkmf.rb index 258416a28818..02c06bff1562 100644 --- a/lib/mri/mkmf.rb +++ b/lib/mri/mkmf.rb @@ -48,7 +48,7 @@ end end -if defined?(::TruffleRuby) and Truffle::Boot.get_option('cexts-prepend-toolchain-to-path') +if defined?(::TruffleRuby) and Truffle::Boot.get_option('cexts-prepend-toolchain-to-path') and Truffle::Boot.get_option('cexts-sulong') ENV['PATH'] = "#{RbConfig::CONFIG['toolchain_path']}:#{ENV['PATH']}" end @@ -2346,7 +2346,7 @@ def depend_rules(depend) # +VPATH+ and added to the list of +INCFLAGS+. # def create_makefile(target, srcprefix = nil) - if defined?(::TruffleRuby) and ($LIBRUBYARG == nil or !Truffle::Boot.get_option('building-core-cexts')) + if defined?(::TruffleRuby) and Truffle::Boot.get_option('cexts-sulong') and ($LIBRUBYARG == nil or !Truffle::Boot.get_option('building-core-cexts')) # $LIBRUBYARG was explicitly unset, the built library is not a C extension but used with FFI (e.g., sassc does). # Since $LIBRUBYARG is unset we won't link to libgraalvm-llvm.so, which is expected. # In the case the library uses C++ code, libc++.so/libc++abi.so will be linked and needs to be found by NFI. @@ -2894,10 +2894,16 @@ def MAIN_DOES_NOTHING(*refs) # We need to link to libtruffleruby for MakeMakefile#try_link to succeed. # The created executable will link against both libgraalvm-llvm.so and libtruffleruby and # might be executed for #try_constant and #try_run so we also need -rpath for both. - libtruffleruby_dir = File.dirname(RbConfig::CONFIG['libtruffleruby']) - TRY_LINK << " -L#{libtruffleruby_dir} -rpath #{libtruffleruby_dir} -ltruffleruby" - libgraalvm_llvm_dir = ::Truffle::Boot.toolchain_paths(:LD_LIBRARY_PATH) - TRY_LINK << " -rpath #{libgraalvm_llvm_dir}" + # TODO: could likely always use the second branch here + if Truffle::Boot.get_option('cexts-sulong') + libtruffleruby_dir = File.dirname(RbConfig::CONFIG['libtruffleruby']) + TRY_LINK << " -L#{libtruffleruby_dir} -rpath #{libtruffleruby_dir} -ltruffleruby" + libgraalvm_llvm_dir = ::Truffle::Boot.toolchain_paths(:LD_LIBRARY_PATH) + TRY_LINK << " -rpath #{libgraalvm_llvm_dir}" + else + libtrufflerubytrampoline_dir = File.dirname(RbConfig::CONFIG['libtrufflerubytrampoline']) + TRY_LINK << " -L#{libtrufflerubytrampoline_dir} -Wl,-rpath,#{libtrufflerubytrampoline_dir} -ltrufflerubytrampoline" + end end ## diff --git a/lib/truffle/rbconfig.rb b/lib/truffle/rbconfig.rb index a47cec63a15e..1b08e5f518eb 100644 --- a/lib/truffle/rbconfig.rb +++ b/lib/truffle/rbconfig.rb @@ -41,6 +41,8 @@ module RbConfig raise 'The TruffleRuby home needs to be set to require RbConfig' unless ruby_home TOPDIR = ruby_home + sulong = Truffle::Boot.get_option('cexts-sulong') + host_os = Truffle::System.host_os host_cpu = Truffle::System.host_cpu host_vendor = 'unknown' @@ -61,26 +63,48 @@ module RbConfig prefix = ruby_home rubyhdrdir = "#{prefix}/lib/cext/include" - ar = Truffle::Boot.toolchain_executable(:AR) - cc = Truffle::Boot.toolchain_executable(:CC) - cxx = Truffle::Boot.toolchain_executable(:CXX) - ranlib = Truffle::Boot.toolchain_executable(:RANLIB) - strip = Truffle::Boot.toolchain_executable(:STRIP) + if sulong + ar = Truffle::Boot.toolchain_executable(:AR) + cc = Truffle::Boot.toolchain_executable(:CC) + cxx = Truffle::Boot.toolchain_executable(:CXX) + ranlib = Truffle::Boot.toolchain_executable(:RANLIB) + strip = Truffle::Boot.toolchain_executable(:STRIP) - strip = "#{strip} --keep-section=.llvmbc" unless Truffle::Platform.darwin? + strip = "#{strip} --keep-section=.llvmbc" unless Truffle::Platform.darwin? + gcc, clang = false, true + else + if Truffle::Platform.linux? + ar = 'gcc-ar' + cc = 'gcc' # -std=gnu99 ? + cxx = 'g++' + ranlib = 'gcc-ranlib' + strip = 'strip -S -x' + gcc, clang = true, false + elsif Truffle::Platform.darwin? + ar = 'ar' + cc = 'clang -fdeclspec' + cxx = 'clang++ -fdeclspec' + ranlib = 'ranlib' + strip = 'strip -A -n' + gcc, clang = false, true + else + raise 'Unknown platform' + end + end # Determine the various flags for native compilation - optflags = '' - debugflags = '' + optflags = sulong ? '' : '-O3 -fno-fast-math' + debugflags = sulong ? '' : '-ggdb3' warnflags = [ '-Werror=implicit-function-declaration', # https://bugs.ruby-lang.org/issues/18615 '-Wno-int-conversion', # MRI has VALUE defined as long while we have it as void* '-Wno-int-to-pointer-cast', # Same as above '-Wno-incompatible-pointer-types', # Fix byebug 8.2.1 compile (st_data_t error) - '-Wno-format-invalid-specifier', # Our PRIsVALUE generates this because compilers ignore printf extensions '-Wno-format-extra-args', # Our PRIsVALUE generates this because compilers ignore printf extensions - '-ferror-limit=500' ] + warnflags << '-Wno-format-invalid-specifier' if clang # Our PRIsVALUE generates this because compilers ignore printf extensions + # TODO fix it, happens in openssl + warnflags << '-Wno-discarded-qualifiers' if gcc defs = '' cppflags = '' @@ -107,6 +131,7 @@ module RbConfig # Set extra flags needed for --building-core-cexts if Truffle::Boot.get_option 'building-core-cexts' libtruffleruby = "#{ruby_home}/src/main/c/cext/libtruffleruby.#{soext}" + libtrufflerubytrampoline = "#{ruby_home}/src/main/c/cext-trampoline/libtrufflerubytrampoline.#{soext}" relative_debug_paths = " -fdebug-prefix-map=#{ruby_home}=." cppflags << relative_debug_paths @@ -115,6 +140,7 @@ module RbConfig warnflags << '-Werror' # Make sure there are no warnings in core C extensions else libtruffleruby = "#{cext_dir}/libtruffleruby.#{soext}" + libtrufflerubytrampoline = "#{cext_dir}/libtrufflerubytrampoline.#{soext}" end # We do not link to libtruffleruby here to workaround GR-29448 @@ -166,6 +192,7 @@ module RbConfig 'LIBRUBY_SO' => "cext/libtruffleruby.#{soext}", 'LIBS' => libs, 'libtruffleruby' => libtruffleruby, + 'libtrufflerubytrampoline' => libtrufflerubytrampoline, 'MAKEDIRS' => 'mkdir -p', 'MKDIR_P' => 'mkdir -p', 'NULLCMD' => ':', diff --git a/mx.truffleruby/mx_truffleruby.py b/mx.truffleruby/mx_truffleruby.py index a1e635fc6759..76aa328ad65b 100644 --- a/mx.truffleruby/mx_truffleruby.py +++ b/mx.truffleruby/mx_truffleruby.py @@ -120,6 +120,10 @@ def contents(self, result): '--disable-gems', '--disable-rubyopt', ] + trufflerubyopt = os.environ.get("TRUFFLERUBYOPT") + if trufflerubyopt and '--cexts-sulong' in trufflerubyopt: + ruby_options.append('--cexts-sulong') + command = [jdk.java] + jvm_args + ['-m', 'org.graalvm.ruby.launcher/' + main_class] + ruby_options + ['"$@"'] return "#!/usr/bin/env bash\n" + "exec " + " ".join(command) + "\n" diff --git a/src/main/c/Makefile b/src/main/c/Makefile index 58f342735066..30f15d0737c2 100644 --- a/src/main/c/Makefile +++ b/src/main/c/Makefile @@ -88,9 +88,9 @@ cext-trampoline/trampoline.c: $(CEXT_C_FILES) $(BASIC_DEPS) $(ROOT)/tool/generat $(TRAMPOLINE): cext-trampoline/Makefile cext-trampoline/trampoline.c cext-trampoline/*.c Makefile $(Q) cd cext-trampoline && $(MAKE) -# libtruffleruby +# libtruffleruby, must be compiled with the GraalVM LLVM Toolchain as it needs to run on Sulong cext/Makefile: cext/extconf.rb $(BASIC_EXTCONF_DEPS) $(TRAMPOLINE) - $(Q) cd cext && $(RUBY) extconf.rb || $(IF_EXTCONF_FAIL) + $(Q) cd cext && $(RUBY) --experimental-options --cexts-sulong extconf.rb || $(IF_EXTCONF_FAIL) $(LIBTRUFFLERUBY): cext/Makefile cext/*.c cext/*.h $(Q) cd cext && $(MAKE) diff --git a/src/main/c/cext-trampoline/st.c b/src/main/c/cext-trampoline/st.c index 3d71c7b946c0..8d28d643c111 100644 --- a/src/main/c/cext-trampoline/st.c +++ b/src/main/c/cext-trampoline/st.c @@ -2044,6 +2044,7 @@ st_numhash(st_data_t n) return (st_index_t)((n>>s1|(n<>s2)); } +#ifndef TRUFFLERUBY /* Expand TAB to be suitable for holding SIZ entries in total. Pre-existing entries remain not deleted inside of TAB, but its bins are cleared to expect future reconstruction. See rehash below. */ @@ -2251,3 +2252,4 @@ rb_st_nth_key(st_table *tab, st_index_t index) } #endif +#endif /* TRUFFLERUBY */ diff --git a/src/main/c/cext/st.c b/src/main/c/cext/st.c index 3d71c7b946c0..8d28d643c111 100644 --- a/src/main/c/cext/st.c +++ b/src/main/c/cext/st.c @@ -2044,6 +2044,7 @@ st_numhash(st_data_t n) return (st_index_t)((n>>s1|(n<>s2)); } +#ifndef TRUFFLERUBY /* Expand TAB to be suitable for holding SIZ entries in total. Pre-existing entries remain not deleted inside of TAB, but its bins are cleared to expect future reconstruction. See rehash below. */ @@ -2251,3 +2252,4 @@ rb_st_nth_key(st_table *tab, st_index_t index) } #endif +#endif /* TRUFFLERUBY */ diff --git a/src/main/java/org/truffleruby/language/backtrace/BacktraceFormatter.java b/src/main/java/org/truffleruby/language/backtrace/BacktraceFormatter.java index 8f1cd8d40c1d..7d4828887a5f 100644 --- a/src/main/java/org/truffleruby/language/backtrace/BacktraceFormatter.java +++ b/src/main/java/org/truffleruby/language/backtrace/BacktraceFormatter.java @@ -26,10 +26,10 @@ import org.truffleruby.core.exception.RubyException; import org.truffleruby.core.string.StringOperations; import org.truffleruby.core.string.StringUtils; -import org.truffleruby.language.RubyGuards; import org.truffleruby.language.RubyRootNode; import org.truffleruby.language.control.RaiseException; import org.truffleruby.language.dispatch.DispatchNode; +import org.truffleruby.language.library.RubyStringLibrary; import org.truffleruby.language.methods.TranslateExceptionNode; import org.truffleruby.parser.RubySource; @@ -137,7 +137,8 @@ public void printRubyExceptionOnEnvStderr(String info, AbstractTruffleException "get_formatted_backtrace", exceptionObject); final String formatted = fullMessage != null - ? RubyGuards.getJavaString(fullMessage) + // Use toJavaStringUncached() instead of RubyGuards.getJavaString() here so it still shows something if BINARY encoding and there are non-ASCII bytes + ? RubyStringLibrary.getUncached().getTString(fullMessage).toJavaStringUncached() : ""; if (formatted.endsWith("\n")) { printer.print(formatted); From f483e671b1da1811eb404208bbfaa946142f1511 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 26 Oct 2023 11:26:13 +0200 Subject: [PATCH 15/50] Adapt MRI C ext tests to load fine * They would fail to e.g. `undefined symbol: rb_yield_block` --- test/mri/tests/cext-c/iter/yield.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/mri/tests/cext-c/iter/yield.c b/test/mri/tests/cext-c/iter/yield.c index 0f6f3e87eb77..010e361e468d 100644 --- a/test/mri/tests/cext-c/iter/yield.c +++ b/test/mri/tests/cext-c/iter/yield.c @@ -12,5 +12,7 @@ Init_yield(VALUE klass) { VALUE yield = rb_define_module_under(klass, "Yield"); +#ifndef TRUFFLERUBY // rb_yield_block undefined rb_define_method(yield, "yield_block", yield_block, -1); +#endif } From 16020b631db5605489aa86be43dbcc4d44f3b88f Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 26 Oct 2023 12:16:52 +0200 Subject: [PATCH 16/50] We need to use a wrapper to call the Init_* function too * In case that raises an error. * Also this calls Init_* with the cext lock, it was missing before. --- lib/truffle/truffle/cext.rb | 12 ++++++ .../language/loader/RequireNode.java | 39 +++---------------- tool/generate-cext-trampoline.rb | 7 +++- 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/lib/truffle/truffle/cext.rb b/lib/truffle/truffle/cext.rb index 38a937708d5b..ba4941164844 100644 --- a/lib/truffle/truffle/cext.rb +++ b/lib/truffle/truffle/cext.rb @@ -46,6 +46,7 @@ module Truffle::CExt POINTER2_TO_POINTER_WRAPPER = POINTER_TO_POINTER_WRAPPERS[2] POINTER3_TO_POINTER_WRAPPER = POINTER_TO_POINTER_WRAPPERS[3] + VOID_TO_VOID_WRAPPER = Primitive.interop_eval_nfi('(pointer):void').bind(lib[:rb_tr_setjmp_wrapper_void_to_void]) POINTER_TO_VOID_WRAPPER = Primitive.interop_eval_nfi('(pointer,pointer):void').bind(lib[:rb_tr_setjmp_wrapper_pointer1_to_void]) POINTER2_TO_VOID_WRAPPER = Primitive.interop_eval_nfi('(pointer,pointer,pointer):void').bind(lib[:rb_tr_setjmp_wrapper_pointer2_to_void]) POINTER3_TO_VOID_WRAPPER = Primitive.interop_eval_nfi('(pointer,pointer,pointer,pointer):void').bind(lib[:rb_tr_setjmp_wrapper_pointer3_to_void]) @@ -204,6 +205,17 @@ def self.init_libtrufflerubytrampoline(libtrampoline) @init_libtrufflerubytrampoline_keep_alive = keep_alive.freeze end + def init_extension(library, library_path) + name = File.basename(library_path, '.*') + function_name = "Init_#{name}" + init_function = library[function_name] + begin + Primitive.call_with_c_mutex_and_frame(VOID_TO_VOID_WRAPPER, [init_function], nil, nil) + ensure + resolve_registered_addresses + end + end + def supported? Interop.mime_type_supported?('application/x-sulong-library') end diff --git a/src/main/java/org/truffleruby/language/loader/RequireNode.java b/src/main/java/org/truffleruby/language/loader/RequireNode.java index 5caead54b8e1..191e7fce5c39 100644 --- a/src/main/java/org/truffleruby/language/loader/RequireNode.java +++ b/src/main/java/org/truffleruby/language/loader/RequireNode.java @@ -21,14 +21,12 @@ import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.source.Source; import org.graalvm.collections.Pair; +import org.truffleruby.RubyContext; import org.truffleruby.RubyLanguage; import org.truffleruby.cext.ValueWrapperManager; -import org.truffleruby.core.array.ArrayUtils; import org.truffleruby.core.cast.BooleanCastNode; import org.truffleruby.core.string.StringUtils; import org.truffleruby.core.string.TStringWithEncoding; -import org.truffleruby.interop.InteropNodes; -import org.truffleruby.interop.TranslateInteropExceptionNodeGen; import org.truffleruby.language.RubyBaseNode; import org.truffleruby.language.RubyConstant; import org.truffleruby.language.RubyGuards; @@ -40,13 +38,11 @@ import org.truffleruby.language.methods.DeclarationContext; import org.truffleruby.language.methods.TranslateExceptionNode; import org.truffleruby.parser.ParserContext; -import org.truffleruby.platform.TruffleNFIPlatform; import org.truffleruby.shared.Metrics; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.nodes.IndirectCallNode; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.SourceSection; @@ -276,41 +272,18 @@ private void requireCExtension(String feature, String expandedPath, Node current throw e; } - final String initFunctionName = "Init_" + getBaseName(expandedPath); - var initFunction = featureLoader.findFunctionInLibrary(library, initFunctionName, expandedPath); - initFunction = TruffleNFIPlatform.bind(getContext(), initFunction, "():void"); - - final InteropLibrary interop = InteropLibrary.getUncached(); - if (!interop.isExecutable(initFunction)) { - throw new RaiseException( - getContext(), - coreExceptions().loadError(initFunctionName + "() is not executable", expandedPath, currentNode)); - } - requireMetric("before-execute-" + feature); - ValueWrapperManager.allocateNewBlock(getContext(), getLanguage()); var currentFiber = getLanguage().getCurrentFiber(); + ValueWrapperManager.allocateNewBlock(getContext(), getLanguage()); var prevGlobals = currentFiber.cGlobalVariablesDuringInitFunction; currentFiber.cGlobalVariablesDuringInitFunction = createEmptyArray(); - currentFiber.extensionCallStack.push(false, nil, nil); try { - InteropNodes - .execute( - null, - initFunction, - ArrayUtils.EMPTY_ARRAY, - interop, - TranslateInteropExceptionNodeGen.getUncached()); + RubyContext.send(currentNode, coreLibrary().truffleCExtModule, "init_extension", library, expandedPath); } finally { - try { - DispatchNode.getUncached().call(coreLibrary().truffleCExtModule, "resolve_registered_addresses"); - } finally { - currentFiber.extensionCallStack.pop(); - currentFiber.cGlobalVariablesDuringInitFunction = prevGlobals; - ValueWrapperManager.allocateNewBlock(getContext(), getLanguage()); - requireMetric("after-execute-" + feature); - } + currentFiber.cGlobalVariablesDuringInitFunction = prevGlobals; + ValueWrapperManager.allocateNewBlock(getContext(), getLanguage()); + requireMetric("after-execute-" + feature); } } diff --git a/tool/generate-cext-trampoline.rb b/tool/generate-cext-trampoline.rb index 93520a6aef96..0a26adc0b43b 100755 --- a/tool/generate-cext-trampoline.rb +++ b/tool/generate-cext-trampoline.rb @@ -124,6 +124,7 @@ C signatures = [ + ['rb_tr_setjmp_wrapper_void_to_void', '(void):void'], ['rb_tr_setjmp_wrapper_pointer1_to_void', '(VALUE arg):void'], ['rb_tr_setjmp_wrapper_pointer2_to_void', '(VALUE tracepoint, void *data):void'], ['rb_tr_setjmp_wrapper_pointer3_to_void', '(VALUE val, ID id, VALUE *data):void'], @@ -143,9 +144,11 @@ signatures.each do |function_name, signature| argument_types, return_type = signature.split(':') argument_types = argument_types.delete_prefix('(').delete_suffix(')') + original_argument_types = argument_types + argument_types = '' if argument_types == 'void' void = return_type == 'void' f.puts < Date: Sat, 21 Oct 2023 13:41:04 +0200 Subject: [PATCH 17/50] Use the original definition of RB_SPECIAL_CONST_P() * Otherwise RBIMPL_ATTR_CONST() RBIMPL_ATTR_CONSTEXPR(CXX11) cause errors. * rb_tr_special_const_p() is slower when running extensions natively. --- lib/cext/include/ruby/internal/special_consts.h | 10 ---------- lib/truffle/truffle/cext.rb | 4 ---- spec/tags/optional/capi/object_tags.txt | 1 + src/main/c/cext/object.c | 10 ---------- test/mri/tests/cext-ruby/st/test_numhash.rb | 7 ++++--- 5 files changed, 5 insertions(+), 27 deletions(-) create mode 100644 spec/tags/optional/capi/object_tags.txt diff --git a/lib/cext/include/ruby/internal/special_consts.h b/lib/cext/include/ruby/internal/special_consts.h index 4223ead1e5d5..1a45eabae20b 100644 --- a/lib/cext/include/ruby/internal/special_consts.h +++ b/lib/cext/include/ruby/internal/special_consts.h @@ -31,12 +31,6 @@ #include "ruby/internal/stdbool.h" #include "ruby/internal/value.h" -#ifdef TRUFFLERUBY -RBIMPL_SYMBOL_EXPORT_BEGIN() -bool rb_tr_special_const_p(VALUE object); -RBIMPL_SYMBOL_EXPORT_END() -#endif - /** * @private * @warning Do not touch this macro. @@ -338,11 +332,7 @@ RBIMPL_ATTR_ARTIFICIAL() static inline bool RB_SPECIAL_CONST_P(VALUE obj) { -#ifdef TRUFFLERUBY - return rb_tr_special_const_p(obj); -#else return RB_IMMEDIATE_P(obj) || obj == RUBY_Qfalse; -#endif } RBIMPL_ATTR_CONST() diff --git a/lib/truffle/truffle/cext.rb b/lib/truffle/truffle/cext.rb index ba4941164844..a1f21f4ffa46 100644 --- a/lib/truffle/truffle/cext.rb +++ b/lib/truffle/truffle/cext.rb @@ -1281,10 +1281,6 @@ def rb_ivar_set(object, name, value) Primitive.object_ivar_set object, name, value end - def rb_special_const_p(object) - Truffle::Type.is_special_const?(object) - end - def rb_id2str(sym) sym.name end diff --git a/spec/tags/optional/capi/object_tags.txt b/spec/tags/optional/capi/object_tags.txt new file mode 100644 index 000000000000..5a3dc5b61faf --- /dev/null +++ b/spec/tags/optional/capi/object_tags.txt @@ -0,0 +1 @@ +fails:CApiObject rb_special_const_p returns true if passed a Symbol diff --git a/src/main/c/cext/object.c b/src/main/c/cext/object.c index 777644af9025..16bf2f7526cb 100644 --- a/src/main/c/cext/object.c +++ b/src/main/c/cext/object.c @@ -38,16 +38,6 @@ bool RB_TYPE_P(VALUE value, enum ruby_value_type type) { return polyglot_as_boolean(polyglot_invoke(RUBY_CEXT, "RB_TYPE_P", rb_tr_unwrap(value), type)); } -bool rb_tr_special_const_p(VALUE object) { - // Ripper calls this from add_mark_object - // Cannot unwrap a natively-allocated NODE* - if (rb_tr_is_native_object(object)) { - return false; - } - - return polyglot_as_boolean(RUBY_CEXT_INVOKE_NO_WRAP("rb_special_const_p", object)); -} - void rb_check_type(VALUE value, int type) { polyglot_invoke(RUBY_CEXT, "rb_check_type", rb_tr_unwrap(value), type); } diff --git a/test/mri/tests/cext-ruby/st/test_numhash.rb b/test/mri/tests/cext-ruby/st/test_numhash.rb index d63a4438f6d3..ad809e5fb471 100644 --- a/test/mri/tests/cext-ruby/st/test_numhash.rb +++ b/test/mri/tests/cext-ruby/st/test_numhash.rb @@ -20,9 +20,10 @@ def test_check end def test_update - assert_equal(true, @tbl.update(0) {@tbl[5] = :x}) - assert_equal(:x, @tbl[0]) - assert_equal(:x, @tbl[5]) + value = 42 + assert_equal(true, @tbl.update(0) {@tbl[5] = value}) + assert_equal(value, @tbl[0]) + assert_equal(value, @tbl[5]) end def test_size_after_delete_safe From d7c441e70839a5e12d67085a1e2e778e8bac76d4 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 26 Oct 2023 13:28:33 +0200 Subject: [PATCH 18/50] Revert "Use the original definition of RB_SPECIAL_CONST_P()" This reverts commit d871e9f915e5b9a714990c5ddcac12a89cff402e. --- lib/cext/include/ruby/internal/special_consts.h | 10 ++++++++++ lib/truffle/truffle/cext.rb | 4 ++++ spec/tags/optional/capi/object_tags.txt | 1 - src/main/c/cext/object.c | 10 ++++++++++ test/mri/tests/cext-ruby/st/test_numhash.rb | 7 +++---- 5 files changed, 27 insertions(+), 5 deletions(-) delete mode 100644 spec/tags/optional/capi/object_tags.txt diff --git a/lib/cext/include/ruby/internal/special_consts.h b/lib/cext/include/ruby/internal/special_consts.h index 1a45eabae20b..4223ead1e5d5 100644 --- a/lib/cext/include/ruby/internal/special_consts.h +++ b/lib/cext/include/ruby/internal/special_consts.h @@ -31,6 +31,12 @@ #include "ruby/internal/stdbool.h" #include "ruby/internal/value.h" +#ifdef TRUFFLERUBY +RBIMPL_SYMBOL_EXPORT_BEGIN() +bool rb_tr_special_const_p(VALUE object); +RBIMPL_SYMBOL_EXPORT_END() +#endif + /** * @private * @warning Do not touch this macro. @@ -332,7 +338,11 @@ RBIMPL_ATTR_ARTIFICIAL() static inline bool RB_SPECIAL_CONST_P(VALUE obj) { +#ifdef TRUFFLERUBY + return rb_tr_special_const_p(obj); +#else return RB_IMMEDIATE_P(obj) || obj == RUBY_Qfalse; +#endif } RBIMPL_ATTR_CONST() diff --git a/lib/truffle/truffle/cext.rb b/lib/truffle/truffle/cext.rb index a1f21f4ffa46..ba4941164844 100644 --- a/lib/truffle/truffle/cext.rb +++ b/lib/truffle/truffle/cext.rb @@ -1281,6 +1281,10 @@ def rb_ivar_set(object, name, value) Primitive.object_ivar_set object, name, value end + def rb_special_const_p(object) + Truffle::Type.is_special_const?(object) + end + def rb_id2str(sym) sym.name end diff --git a/spec/tags/optional/capi/object_tags.txt b/spec/tags/optional/capi/object_tags.txt deleted file mode 100644 index 5a3dc5b61faf..000000000000 --- a/spec/tags/optional/capi/object_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:CApiObject rb_special_const_p returns true if passed a Symbol diff --git a/src/main/c/cext/object.c b/src/main/c/cext/object.c index 16bf2f7526cb..777644af9025 100644 --- a/src/main/c/cext/object.c +++ b/src/main/c/cext/object.c @@ -38,6 +38,16 @@ bool RB_TYPE_P(VALUE value, enum ruby_value_type type) { return polyglot_as_boolean(polyglot_invoke(RUBY_CEXT, "RB_TYPE_P", rb_tr_unwrap(value), type)); } +bool rb_tr_special_const_p(VALUE object) { + // Ripper calls this from add_mark_object + // Cannot unwrap a natively-allocated NODE* + if (rb_tr_is_native_object(object)) { + return false; + } + + return polyglot_as_boolean(RUBY_CEXT_INVOKE_NO_WRAP("rb_special_const_p", object)); +} + void rb_check_type(VALUE value, int type) { polyglot_invoke(RUBY_CEXT, "rb_check_type", rb_tr_unwrap(value), type); } diff --git a/test/mri/tests/cext-ruby/st/test_numhash.rb b/test/mri/tests/cext-ruby/st/test_numhash.rb index ad809e5fb471..d63a4438f6d3 100644 --- a/test/mri/tests/cext-ruby/st/test_numhash.rb +++ b/test/mri/tests/cext-ruby/st/test_numhash.rb @@ -20,10 +20,9 @@ def test_check end def test_update - value = 42 - assert_equal(true, @tbl.update(0) {@tbl[5] = value}) - assert_equal(value, @tbl[0]) - assert_equal(value, @tbl[5]) + assert_equal(true, @tbl.update(0) {@tbl[5] = :x}) + assert_equal(:x, @tbl[0]) + assert_equal(:x, @tbl[5]) end def test_size_after_delete_safe From 6c5fe8b8f31b418674b17e42b6cd35558de4e87d Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 26 Oct 2023 13:33:21 +0200 Subject: [PATCH 19/50] Add a fast-path for RB_SPECIAL_CONST_P() --- lib/cext/include/ruby/internal/special_consts.h | 4 ++-- src/main/c/cext/object.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/cext/include/ruby/internal/special_consts.h b/lib/cext/include/ruby/internal/special_consts.h index 4223ead1e5d5..3dc9f5849b22 100644 --- a/lib/cext/include/ruby/internal/special_consts.h +++ b/lib/cext/include/ruby/internal/special_consts.h @@ -33,7 +33,7 @@ #ifdef TRUFFLERUBY RBIMPL_SYMBOL_EXPORT_BEGIN() -bool rb_tr_special_const_p(VALUE object); +bool rb_tr_special_const_symbol_p(VALUE object); RBIMPL_SYMBOL_EXPORT_END() #endif @@ -339,7 +339,7 @@ static inline bool RB_SPECIAL_CONST_P(VALUE obj) { #ifdef TRUFFLERUBY - return rb_tr_special_const_p(obj); + return RB_IMMEDIATE_P(obj) || obj == RUBY_Qfalse || rb_tr_special_const_symbol_p(obj); #else return RB_IMMEDIATE_P(obj) || obj == RUBY_Qfalse; #endif diff --git a/src/main/c/cext/object.c b/src/main/c/cext/object.c index 777644af9025..51b792244616 100644 --- a/src/main/c/cext/object.c +++ b/src/main/c/cext/object.c @@ -38,14 +38,14 @@ bool RB_TYPE_P(VALUE value, enum ruby_value_type type) { return polyglot_as_boolean(polyglot_invoke(RUBY_CEXT, "RB_TYPE_P", rb_tr_unwrap(value), type)); } -bool rb_tr_special_const_p(VALUE object) { +bool rb_tr_special_const_symbol_p(VALUE object) { // Ripper calls this from add_mark_object // Cannot unwrap a natively-allocated NODE* if (rb_tr_is_native_object(object)) { return false; } - return polyglot_as_boolean(RUBY_CEXT_INVOKE_NO_WRAP("rb_special_const_p", object)); + return rb_tr_symbol_p(object); } void rb_check_type(VALUE value, int type) { From 08fcf3b3508bae5db98a6545526140ab5a8b0825 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 26 Oct 2023 13:47:04 +0200 Subject: [PATCH 20/50] Don't include warnflags in cxxflags * Most of these warnflags only work for C and not C++. * On CRuby cxxflags actually seems empty. --- lib/truffle/rbconfig.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/truffle/rbconfig.rb b/lib/truffle/rbconfig.rb index 1b08e5f518eb..3230f0a349f3 100644 --- a/lib/truffle/rbconfig.rb +++ b/lib/truffle/rbconfig.rb @@ -300,8 +300,8 @@ module RbConfig mkconfig['CFLAGS'] = '$(cflags)' cxxflags = \ - expanded['cxxflags'] = "#{optflags} #{debugflags} #{warnflags}" - mkconfig['cxxflags'] = '$(optflags) $(debugflags) $(warnflags)' + expanded['cxxflags'] = "#{optflags} #{debugflags}" + mkconfig['cxxflags'] = '$(optflags) $(debugflags)' expanded['CXXFLAGS'] = cxxflags mkconfig['CXXFLAGS'] = '$(cxxflags)' cppflags_hardcoded = Truffle::Platform.darwin? ? ' -D_DARWIN_C_SOURCE' : '' From 3ff2e8e86dd8c7715c6d0aceef21910eb4006f50 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 26 Oct 2023 13:50:54 +0200 Subject: [PATCH 21/50] Fixes for C++ --- lib/cext/include/ruby/internal/value_type.h | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/lib/cext/include/ruby/internal/value_type.h b/lib/cext/include/ruby/internal/value_type.h index 2f3ac0666884..053cf23319a2 100644 --- a/lib/cext/include/ruby/internal/value_type.h +++ b/lib/cext/include/ruby/internal/value_type.h @@ -169,6 +169,9 @@ void rb_check_type(VALUE obj, int t); #ifdef TRUFFLERUBY enum ruby_value_type rb_type(VALUE obj); bool RB_TYPE_P(VALUE value, enum ruby_value_type type); +bool rb_tr_integer_type_p(VALUE obj); +bool rb_tr_float_type_p(VALUE obj); +bool rb_tr_symbol_p(VALUE obj); #endif RBIMPL_SYMBOL_EXPORT_END() @@ -199,10 +202,6 @@ RB_BUILTIN_TYPE(VALUE obj) #endif } -#ifdef TRUFFLERUBY -bool rb_tr_integer_type_p(VALUE obj); -#endif - RBIMPL_ATTR_PURE_UNLESS_DEBUG() /** * Queries if the object is an instance of ::rb_cInteger. @@ -268,10 +267,6 @@ rb_type(VALUE obj) } #endif -#ifdef TRUFFLERUBY -bool rb_tr_float_type_p(VALUE obj); -#endif - RBIMPL_ATTR_PURE_UNLESS_DEBUG() RBIMPL_ATTR_ARTIFICIAL() /** @@ -320,10 +315,6 @@ RB_DYNAMIC_SYM_P(VALUE obj) } } -#ifdef TRUFFLERUBY -bool rb_tr_symbol_p(VALUE obj); -#endif - RBIMPL_ATTR_PURE_UNLESS_DEBUG() RBIMPL_ATTR_ARTIFICIAL() /** From 09e99f43a94632463901e0f8854878e19d5ca0e2 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 26 Oct 2023 13:56:55 +0200 Subject: [PATCH 22/50] Run SPEC_CAPI_CXX=true C API specs in CI --- ci.jsonnet | 1 + 1 file changed, 1 insertion(+) diff --git a/ci.jsonnet b/ci.jsonnet index f7ececf2f338..7f3a2e8c1522 100644 --- a/ci.jsonnet +++ b/ci.jsonnet @@ -308,6 +308,7 @@ local part_definitions = { run+: jt(["test", "specs", ":all"]) + jt(["test", "specs", ":tracepoint"]) + jt(["test", "specs", ":next"]) + + [["env", "SPEC_CAPI_CXX=true"] + jt(["test", "specs", "--timeout", "180", ":capi"])[0]] + jt(["test", "basictest"]), }, From 7fb763e3a640b6b809ca39726ad41bb5118eeca6 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 26 Oct 2023 14:08:18 +0200 Subject: [PATCH 23/50] Compile libtruffleposix and spawn-helper using the system compiler --- src/main/c/spawn-helper/Makefile | 2 +- src/main/c/truffleposix/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/c/spawn-helper/Makefile b/src/main/c/spawn-helper/Makefile index 07b3f383419c..211268479a72 100644 --- a/src/main/c/spawn-helper/Makefile +++ b/src/main/c/spawn-helper/Makefile @@ -1,6 +1,6 @@ Q$(MX_VERBOSE) = @ -CC := $(GRAALVM_TOOLCHAIN_CC) +CC := cc CFLAGS := -Wall -Werror -std=c99 LDFLAGS := -m64 diff --git a/src/main/c/truffleposix/Makefile b/src/main/c/truffleposix/Makefile index b1bd61c779e2..b15efca6d927 100644 --- a/src/main/c/truffleposix/Makefile +++ b/src/main/c/truffleposix/Makefile @@ -7,7 +7,7 @@ else SOEXT := so endif -CC := $(GRAALVM_TOOLCHAIN_CC) +CC := cc CFLAGS := -Wall -Werror -fPIC -std=c99 -g LDFLAGS := -m64 From 566bade287c6ff98a43b3e4ee907cb2d8e8885a2 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 26 Oct 2023 14:09:30 +0200 Subject: [PATCH 24/50] CRuby headers need -fdeclspec on system clang on macOS Mojave --- src/main/c/cext-trampoline/Makefile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/c/cext-trampoline/Makefile b/src/main/c/cext-trampoline/Makefile index 596f32969a7d..0c8c592c52f3 100644 --- a/src/main/c/cext-trampoline/Makefile +++ b/src/main/c/cext-trampoline/Makefile @@ -10,7 +10,14 @@ endif # cc should be enough to compile this library but might produce different warnings based on the system compiler. # If that is the case and fails the build, it might be better to use instead: # CC := $(GRAALVM_TOOLCHAIN_CC) -CC := cc + +# Same values as in RbConfig +ifeq ($(OS),Darwin) +CC := clang -fdeclspec +else +CC := gcc +endif + CFLAGS := -Wall -Werror -fPIC -std=c99 -g LDFLAGS := -m64 From b5f2e3ad78d965829b7c53339c7ea4a43d6c1cd6 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 26 Oct 2023 15:42:28 +0200 Subject: [PATCH 25/50] strlcpy() should be defined directly in libtrufflerubytrampoline --- src/main/c/cext-trampoline/Makefile | 2 +- src/main/c/cext-trampoline/strlcpy.c | 51 ++++++++++++++++++++++++++++ tool/generate-cext-trampoline.rb | 2 +- tool/import-mri-files.sh | 1 + 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 src/main/c/cext-trampoline/strlcpy.c diff --git a/src/main/c/cext-trampoline/Makefile b/src/main/c/cext-trampoline/Makefile index 0c8c592c52f3..d642803cbbf4 100644 --- a/src/main/c/cext-trampoline/Makefile +++ b/src/main/c/cext-trampoline/Makefile @@ -24,7 +24,7 @@ LDFLAGS := -m64 ROOT := $(realpath ../../../..) RUBY_HDR_DIR := $(ROOT)/lib/cext/include -OBJECT_FILES := trampoline.o st.o cext_constants.o +OBJECT_FILES := trampoline.o st.o strlcpy.o cext_constants.o libtrufflerubytrampoline.$(SOEXT): $(OBJECT_FILES) Makefile $(Q) $(CC) -shared $(LDFLAGS) -o $@ $(OBJECT_FILES) diff --git a/src/main/c/cext-trampoline/strlcpy.c b/src/main/c/cext-trampoline/strlcpy.c new file mode 100644 index 000000000000..4d39e3b328b9 --- /dev/null +++ b/src/main/c/cext-trampoline/strlcpy.c @@ -0,0 +1,51 @@ +/* $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $ */ + +/* + * Copyright (c) 1998, 2015 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ruby/missing.h" +#include +#include + +#ifndef HAVE_STRLCPY +/* + * Copy string src to buffer dst of size dsize. At most dsize-1 + * chars will be copied. Always NUL terminates (unless dsize == 0). + * Returns strlen(src); if retval >= dsize, truncation occurred. + */ +size_t strlcpy(char *dst, const char *src, size_t dsize) { + const char *osrc = src; + size_t nleft = dsize; + + /* Copy as many bytes as will fit. */ + if (nleft != 0) { + while (--nleft != 0) { + if ((*dst++ = *src++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src. */ + if (nleft == 0) { + if (dsize != 0) + *dst = '\0'; /* NUL-terminate dst */ + while (*src++) + ; + } + + return(src - osrc - 1); /* count does not include NUL */ +} +#endif diff --git a/tool/generate-cext-trampoline.rb b/tool/generate-cext-trampoline.rb index 0a26adc0b43b..81ff68a52709 100755 --- a/tool/generate-cext-trampoline.rb +++ b/tool/generate-cext-trampoline.rb @@ -41,7 +41,7 @@ functions = [] Dir["src/main/c/cext/*.c"].sort.each do |file| - next if %w[cext_constants.c wrappers.c ruby.c st.c].include?(File.basename(file)) + next if %w[cext_constants.c wrappers.c ruby.c st.c strlcpy.c].include?(File.basename(file)) contents = File.read(file) found_functions = contents.scan(function_regexp) diff --git a/tool/import-mri-files.sh b/tool/import-mri-files.sh index 8d4aa6370208..a201d8cd893c 100755 --- a/tool/import-mri-files.sh +++ b/tool/import-mri-files.sh @@ -40,6 +40,7 @@ find lib/mri -name '.document' -delete cp ../ruby/st.c src/main/c/cext cp ../ruby/st.c src/main/c/cext-trampoline cp ../ruby/missing/strlcpy.c src/main/c/cext +cp ../ruby/missing/strlcpy.c src/main/c/cext-trampoline # Copy Ruby files in ext/, sorted alphabetically cp -R ../ruby/ext/bigdecimal/lib/bigdecimal lib/mri From 51bbcd2a37a6505e64b4e9608176137974cde0cf Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 26 Oct 2023 15:50:51 +0200 Subject: [PATCH 26/50] Remove -m64 in LDFLAGS * It is not accepted by gcc. --- src/main/c/cext-trampoline/Makefile | 2 +- src/main/c/spawn-helper/Makefile | 2 +- src/main/c/truffleposix/Makefile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/c/cext-trampoline/Makefile b/src/main/c/cext-trampoline/Makefile index d642803cbbf4..636495966781 100644 --- a/src/main/c/cext-trampoline/Makefile +++ b/src/main/c/cext-trampoline/Makefile @@ -19,7 +19,7 @@ CC := gcc endif CFLAGS := -Wall -Werror -fPIC -std=c99 -g -LDFLAGS := -m64 +LDFLAGS := ROOT := $(realpath ../../../..) RUBY_HDR_DIR := $(ROOT)/lib/cext/include diff --git a/src/main/c/spawn-helper/Makefile b/src/main/c/spawn-helper/Makefile index 211268479a72..fa6214153d86 100644 --- a/src/main/c/spawn-helper/Makefile +++ b/src/main/c/spawn-helper/Makefile @@ -2,7 +2,7 @@ Q$(MX_VERBOSE) = @ CC := cc CFLAGS := -Wall -Werror -std=c99 -LDFLAGS := -m64 +LDFLAGS := spawn-helper: spawn-helper.c $(Q) $(CC) $(LDFLAGS) -o $@ $< diff --git a/src/main/c/truffleposix/Makefile b/src/main/c/truffleposix/Makefile index b15efca6d927..19db470d38f1 100644 --- a/src/main/c/truffleposix/Makefile +++ b/src/main/c/truffleposix/Makefile @@ -9,7 +9,7 @@ endif CC := cc CFLAGS := -Wall -Werror -fPIC -std=c99 -g -LDFLAGS := -m64 +LDFLAGS := ifeq ($(OS),Linux) LDFLAGS += -lrt From 1a96f98954e576133e2ec358b449d30b56668836 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 27 Oct 2023 14:06:10 +0200 Subject: [PATCH 27/50] Fix for --no-home-provided --- src/main/ruby/truffleruby/core/truffle/gem_util.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/ruby/truffleruby/core/truffle/gem_util.rb b/src/main/ruby/truffleruby/core/truffle/gem_util.rb index fd4e4b17bab8..10b4d054844a 100644 --- a/src/main/ruby/truffleruby/core/truffle/gem_util.rb +++ b/src/main/ruby/truffleruby/core/truffle/gem_util.rb @@ -81,7 +81,7 @@ module Truffle::GemUtil MARKER_NAME = 'truffleruby_gem_dir_marker.txt' - ABI_VERSION = -Truffle::Boot.read_abi_version + ABI_VERSION = -Truffle::Boot.read_abi_version if Truffle::Boot.ruby_home def self.upgraded_default_gem?(feature) if i = feature.index('/') From bab06656a70b6863073ef835fda2e75bdd8379e1 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 27 Oct 2023 14:11:11 +0200 Subject: [PATCH 28/50] Remove nativeconversion test, it only makes sense with Sulong --- spec/nativeconversion.mspec | 63 ------------------- .../truffle/capi/ext/handle_conversion_spec.c | 54 ---------------- spec/truffle/capi/handle_conversion_spec.rb | 34 ---------- test/truffle/integration/native_conversion.sh | 6 -- 4 files changed, 157 deletions(-) delete mode 100644 spec/nativeconversion.mspec delete mode 100644 spec/truffle/capi/ext/handle_conversion_spec.c delete mode 100644 spec/truffle/capi/handle_conversion_spec.rb delete mode 100755 test/truffle/integration/native_conversion.sh diff --git a/spec/nativeconversion.mspec b/spec/nativeconversion.mspec deleted file mode 100644 index 816542542160..000000000000 --- a/spec/nativeconversion.mspec +++ /dev/null @@ -1,63 +0,0 @@ -class NativeHandleChecker - EXPECTED_FAILURES = { - "C language construct switch (VALUE) works for the default case" => 1, - "C-API Debug function rb_debug_inspector_open creates a debug context and calls the given callback" => 2, - "C-API Debug function rb_debug_inspector_frame_self_get returns self" => 2, - "C-API Debug function rb_debug_inspector_frame_class_get returns the frame class" => 2, - "C-API Debug function rb_debug_inspector_frame_binding_get returns the current binding" => 2, - "C-API Debug function rb_debug_inspector_frame_binding_get matches the locations in rb_debug_inspector_backtrace_locations" => 2, - "C-API Debug function rb_debug_inspector_frame_iseq_get returns an InstructionSequence" => 2, - "C-API Debug function rb_debug_inspector_backtrace_locations returns an array of Thread::Backtrace::Location" => 2, - "C-API IO function rb_io_printf calls #to_str to convert the format object to a String" => 2, - "C-API IO function rb_io_printf calls #to_s to convert the object to a String" => 2, - "C-API IO function rb_io_printf writes the Strings to the IO" => 4, - "C-API IO function rb_io_print calls #to_s to convert the object to a String" => 1, - "C-API IO function rb_io_print writes the Strings to the IO with no separator" => 3, - "C-API IO function rb_io_puts calls #to_s to convert the object to a String" => 1, - "C-API IO function rb_io_puts writes the Strings to the IO separated by newlines" => 3, - "C-API Kernel function rb_rescue executes the passed 'rescue function' if a StandardError exception is raised" => 3, - "C-API Kernel function rb_rescue passes the user supplied argument to the 'rescue function' if a StandardError exception is raised" => 4, - "C-API Kernel function rb_rescue passes the raised exception to the 'rescue function' if a StandardError exception is raised" => 2, - "C-API Kernel function rb_rescue raises an exception if any exception is raised inside the 'rescue function'" => 1, - "C-API Kernel function rb_rescue makes $! available only during the 'rescue function' execution" => 1, - "CApiObject rb_obj_call_init sends #initialize" => 2, - "C-API String function rb_sprintf can format a string VALUE as a pointer and gives the same output as sprintf in C" => 1, - "C-API String function rb_string_value_cstr returns a non-null pointer for a simple string" => 1, - "C-API String function rb_string_value_cstr returns a non-null pointer for a UTF-16 string" => 1, - "C-API String function rb_string_value_cstr raises an error if a string contains a null" => 1, - "C-API String function rb_string_value_cstr raises an error if a UTF-16 string contains a null" => 1, - "C-API Util function rb_get_kwargs extracts required arguments in the order requested" => 2, - "C-API Util function rb_get_kwargs extracts required and optional arguments in the order requested" => 1, - "C-API Util function rb_get_kwargs raises an error if a required argument is not in the hash" => 1, - "CApiGCSpecs rb_gc_register_address can be called outside Init_" => 1, - "Native handle conversion converts all elements to native handles when memcpying an RARRAY_PTR" => 1000, - } - - def register - MSpec.register :before, self - MSpec.register :passed, self - end - - def unregister - MSpec.unregister :before, self - MSpec.unregister :passed, self - end - - def before(state) - @start_count = Truffle::Debug.cexts_to_native_count - end - - def passed(state, example) - expected = EXPECTED_FAILURES[state.description] || 0 - actual = Truffle::Debug.cexts_to_native_count - @start_count - unless actual == expected - raise SpecExpectationNotMetError, "Expected #{expected} conversions to native but was #{actual}" - end - end -end - -begin - checker = NativeHandleChecker.new - checker.register - load File.expand_path('../truffleruby.mspec', __FILE__) -end diff --git a/spec/truffle/capi/ext/handle_conversion_spec.c b/spec/truffle/capi/ext/handle_conversion_spec.c deleted file mode 100644 index 25783b37c758..000000000000 --- a/spec/truffle/capi/ext/handle_conversion_spec.c +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. This - * code is released under a tri EPL/GPL/LGPL license. You can use it, - * redistribute it and/or modify it under the terms of the: - * - * Eclipse Public License version 2.0, or - * GNU General Public License version 2, or - * GNU Lesser General Public License version 2.1. - */ -#include "ruby.h" -#include "rubyspec.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -static VALUE value_comparison_with_nil(VALUE self, VALUE val) { - return val == Qnil ? Qtrue : Qfalse; -} - -static VALUE value_array_ptr_access(VALUE self, VALUE ary) { - VALUE *ptr = RARRAY_PTR(ary); - return ptr[0]; -} - -static VALUE value_array_ptr_memcpy(VALUE self, VALUE ary) { - VALUE *ptr = RARRAY_PTR(ary); - VALUE *cpy = malloc(RARRAY_LEN(ary) * sizeof(VALUE)); - VALUE res; - memcpy(cpy, ptr, RARRAY_LEN(ary) * sizeof(VALUE)); - res = cpy[1]; - free(cpy); - return res; -} - -static VALUE our_static_value; - -static VALUE value_store_in_static(VALUE self, VALUE val) { - our_static_value = val; - return val; -} - -void Init_handle_conversion_spec(void) { - VALUE cls = rb_define_class("CAPIHandleConversionTest", rb_cObject); - rb_define_method(cls, "value_comparison_with_nil", value_comparison_with_nil, 1); - rb_define_method(cls, "value_array_ptr_access", value_array_ptr_access, 1); - rb_define_method(cls, "value_array_ptr_memcpy", value_array_ptr_memcpy, 1); - rb_define_method(cls, "value_store_in_static", value_store_in_static, 1); -} - -#ifdef __cplusplus -} -#endif diff --git a/spec/truffle/capi/handle_conversion_spec.rb b/spec/truffle/capi/handle_conversion_spec.rb deleted file mode 100644 index 368471be10c0..000000000000 --- a/spec/truffle/capi/handle_conversion_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) 2017, 2023 Oracle and/or its affiliates. All rights reserved. This -# code is released under a tri EPL/GPL/LGPL license. You can use it, -# redistribute it and/or modify it under the terms of the: -# -# Eclipse Public License version 2.0, or -# GNU General Public License version 2, or -# GNU Lesser General Public License version 2.1. - -require_relative '../../ruby/optional/capi/spec_helper' - -load_extension('handle_conversion') - -guard -> { Truffle::Boot.get_option('cexts-to-native-count') == true } do - describe "Native handle conversion" do - it "converts no handles when comparing a VALUE with a constant" do - CAPIHandleConversionTest.new.value_comparison_with_nil(Object.new).should == false - end - - it "converts no handles when accessing array elements via an RARRAY_PTR" do - ary = Array.new(1000) { Object.new } - CAPIHandleConversionTest.new.value_array_ptr_access(ary).should == ary[0] - end - - it "converts all elements to native handles when memcpying an RARRAY_PTR" do - ary = Array.new(1000) { Object.new } - CAPIHandleConversionTest.new.value_array_ptr_memcpy(ary).should == ary[1] - end - - it "converts no handles when storing a VALUE in a static variable" do - obj = Object.new - CAPIHandleConversionTest.new.value_store_in_static(obj).should == obj - end - end -end diff --git a/test/truffle/integration/native_conversion.sh b/test/truffle/integration/native_conversion.sh deleted file mode 100755 index 0bc628a079fa..000000000000 --- a/test/truffle/integration/native_conversion.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -source test/truffle/common.sh.inc - -export TRUFFLERUBY_MSPEC_CONFIG=spec/nativeconversion.mspec -jt test :capi :truffle_capi -- --cexts-to-native-count From dc6470009cac10a70d8160db2d0ce2a259b03ea1 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 27 Oct 2023 14:17:33 +0200 Subject: [PATCH 29/50] Use --disable-gems to speedup CRuby startup in tool/lint.sh --- tool/lint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/lint.sh b/tool/lint.sh index dd08d7210bd5..cf1b5c22b102 100755 --- a/tool/lint.sh +++ b/tool/lint.sh @@ -8,7 +8,7 @@ do continue fi - bad=$(ruby -e 'puts STDIN.read.scan /^.+\)\s*\n\s*\{/' < "$f" || exit 0) + bad=$(ruby --disable-gems -e 'puts STDIN.read.scan /^.+\)\s*\n\s*\{/' < "$f" || exit 0) if [ -n "$bad" ]; then echo "Error in $f" echo "The function definition opening brace should be on the same line: ...args) {" From 7139c2657c053ddf87323e05b23efb512c40140e Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 27 Oct 2023 14:33:56 +0200 Subject: [PATCH 30/50] Replace tool/lint.sh by a Ruby script * It's much faster, 0.055s vs 0.677s and even 1.468s before --disable-gems --- tool/c-linter.rb | 27 ++++++++++++++++++++++++++ tool/jt.rb | 2 +- tool/lint.sh | 50 ------------------------------------------------ 3 files changed, 28 insertions(+), 51 deletions(-) create mode 100755 tool/c-linter.rb delete mode 100755 tool/lint.sh diff --git a/tool/c-linter.rb b/tool/c-linter.rb new file mode 100755 index 000000000000..156ff4930a0c --- /dev/null +++ b/tool/c-linter.rb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby + +files = Dir['src/main/c/cext/*.c'] + Dir['spec/ruby/optional/capi/ext/*.c'] +files -= %w[src/main/c/cext/st.c] + +files.each do |file| + contents = File.read(file) + if contents =~ /^.+\)\s*\n\s*\{/ + raise "#{file}: The function definition opening brace should be on the same line: ...args) {\n#{$&}" + end + + if contents.include? '){' + raise "#{file}: There should be a space between ) and {\n){" + end + + if contents.include? '() {' + raise "#{file}: The function declaration should use function(void) {\n() {" + end + + if contents =~ /\bif\(/ + raise "#{file}: There should be a space between if and (\n#{$&}" + end + + if contents.include? "\t" + raise "#{file}: There should be no tabs" + end +end diff --git a/tool/jt.rb b/tool/jt.rb index f77aac7af957..4795c0775dd9 100755 --- a/tool/jt.rb +++ b/tool/jt.rb @@ -3100,7 +3100,7 @@ def lint(*args) # Lint rubocop if changed['.rb'] - sh 'tool/lint.sh' if changed['.c'] + sh ruby_running_jt_env, 'tool/c-linter.rb' if changed['.c'] checkstyle(changed['.java']) if changed['.java'] command_format(changed['.java']) if changed['.java'] shellcheck if changed['.sh'] or changed['.inc'] diff --git a/tool/lint.sh b/tool/lint.sh deleted file mode 100755 index cf1b5c22b102..000000000000 --- a/tool/lint.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash - -set -e - -for f in src/main/c/cext/*.c spec/ruby/optional/capi/ext/*.c -do - if [[ "$f" == "src/main/c/cext/st.c" ]]; then - continue - fi - - bad=$(ruby --disable-gems -e 'puts STDIN.read.scan /^.+\)\s*\n\s*\{/' < "$f" || exit 0) - if [ -n "$bad" ]; then - echo "Error in $f" - echo "The function definition opening brace should be on the same line: ...args) {" - echo "$bad" - exit 1 - fi - - bad=$(grep -E '\)\{' "$f" || exit 0) - if [ -n "$bad" ]; then - echo "Error in $f" - echo "There should be a space between ) and {" - echo "$bad" - exit 1 - fi - - bad=$(grep -E '\(\) \{' "$f" || exit 0) - if [ -n "$bad" ]; then - echo "Error in $f" - echo "The function declaration should use function(void) {" - echo "$bad" - exit 1 - fi - - bad=$(grep -E '\bif\(' "$f" || exit 0) - if [ -n "$bad" ]; then - echo "Error in $f" - echo "There should be a space between if and (" - echo "$bad" - exit 1 - fi - - bad=$(grep -E "$(printf '\t')" "$f" || exit 0) - if [ -n "$bad" ]; then - echo "Error in $f" - echo "There should be no tabs in $f" - echo "$bad" - exit 1 - fi -done \ No newline at end of file From 8456370ea0d6617e234c8625ec9b3049de5ce810 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 27 Oct 2023 14:43:38 +0200 Subject: [PATCH 31/50] Move some cexts debugging options as internal since they are only meant for debugging --- .../java/org/truffleruby/options/Options.java | 20 ++++---- src/options.yml | 5 +- .../shared/options/OptionsCatalog.java | 48 +++++++++---------- 3 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/truffleruby/options/Options.java b/src/main/java/org/truffleruby/options/Options.java index b93031d9a937..1437359f990f 100644 --- a/src/main/java/org/truffleruby/options/Options.java +++ b/src/main/java/org/truffleruby/options/Options.java @@ -107,14 +107,10 @@ public final class Options { public final boolean BACKTRACE_ON_NEW_FIBER; /** --cexts=true */ public final boolean CEXTS; - /** --cexts-sulong=false */ - public final boolean CEXTS_SULONG; /** --cexts-lock=true */ public final boolean CEXT_LOCK; /** --cexts-prepend-toolchain-to-path=true */ public final boolean CEXTS_PREPEND_TOOLCHAIN_TO_PATH; - /** --keep-handles-alive=false */ - public final boolean CEXTS_KEEP_HANDLES_ALIVE; /** --options-log=false */ public final boolean OPTIONS_LOG; /** --log-load=false */ @@ -173,6 +169,10 @@ public final class Options { public final boolean CEXTS_TO_NATIVE_COUNT; /** --backtraces-to-native=false */ public final boolean BACKTRACE_ON_TO_NATIVE; + /** --keep-handles-alive=false */ + public final boolean CEXTS_KEEP_HANDLES_ALIVE; + /** --cexts-sulong=false */ + public final boolean CEXTS_SULONG; /** --basic-ops-log-rewrite=false */ public final boolean BASICOPS_LOG_REWRITE; /** --array-small=3 */ @@ -254,10 +254,8 @@ public Options(Env env, OptionValues options, LanguageOptions languageOptions) { BACKTRACE_ON_NEW_THREAD = options.get(OptionsCatalog.BACKTRACE_ON_NEW_THREAD_KEY); BACKTRACE_ON_NEW_FIBER = options.get(OptionsCatalog.BACKTRACE_ON_NEW_FIBER_KEY); CEXTS = options.get(OptionsCatalog.CEXTS_KEY); - CEXTS_SULONG = options.get(OptionsCatalog.CEXTS_SULONG_KEY); CEXT_LOCK = options.get(OptionsCatalog.CEXT_LOCK_KEY); CEXTS_PREPEND_TOOLCHAIN_TO_PATH = options.get(OptionsCatalog.CEXTS_PREPEND_TOOLCHAIN_TO_PATH_KEY); - CEXTS_KEEP_HANDLES_ALIVE = options.get(OptionsCatalog.CEXTS_KEEP_HANDLES_ALIVE_KEY); OPTIONS_LOG = options.get(OptionsCatalog.OPTIONS_LOG_KEY); LOG_LOAD = options.get(OptionsCatalog.LOG_LOAD_KEY); LOG_AUTOLOAD = options.get(OptionsCatalog.LOG_AUTOLOAD_KEY); @@ -287,6 +285,8 @@ public Options(Env env, OptionValues options, LanguageOptions languageOptions) { CEXTS_TO_NATIVE_STATS = options.get(OptionsCatalog.CEXTS_TO_NATIVE_STATS_KEY); CEXTS_TO_NATIVE_COUNT = options.hasBeenSet(OptionsCatalog.CEXTS_TO_NATIVE_COUNT_KEY) ? options.get(OptionsCatalog.CEXTS_TO_NATIVE_COUNT_KEY) : CEXTS_TO_NATIVE_STATS; BACKTRACE_ON_TO_NATIVE = options.get(OptionsCatalog.BACKTRACE_ON_TO_NATIVE_KEY); + CEXTS_KEEP_HANDLES_ALIVE = options.get(OptionsCatalog.CEXTS_KEEP_HANDLES_ALIVE_KEY); + CEXTS_SULONG = options.get(OptionsCatalog.CEXTS_SULONG_KEY); BASICOPS_LOG_REWRITE = options.get(OptionsCatalog.BASICOPS_LOG_REWRITE_KEY); ARRAY_SMALL = options.get(OptionsCatalog.ARRAY_SMALL_KEY); CEXTS_MARKING_CACHE = options.get(OptionsCatalog.CEXTS_MARKING_CACHE_KEY); @@ -392,14 +392,10 @@ public Object fromDescriptor(OptionDescriptor descriptor) { return BACKTRACE_ON_NEW_FIBER; case "ruby.cexts": return CEXTS; - case "ruby.cexts-sulong": - return CEXTS_SULONG; case "ruby.cexts-lock": return CEXT_LOCK; case "ruby.cexts-prepend-toolchain-to-path": return CEXTS_PREPEND_TOOLCHAIN_TO_PATH; - case "ruby.keep-handles-alive": - return CEXTS_KEEP_HANDLES_ALIVE; case "ruby.options-log": return OPTIONS_LOG; case "ruby.log-load": @@ -458,6 +454,10 @@ public Object fromDescriptor(OptionDescriptor descriptor) { return CEXTS_TO_NATIVE_COUNT; case "ruby.backtraces-to-native": return BACKTRACE_ON_TO_NATIVE; + case "ruby.keep-handles-alive": + return CEXTS_KEEP_HANDLES_ALIVE; + case "ruby.cexts-sulong": + return CEXTS_SULONG; case "ruby.basic-ops-log-rewrite": return BASICOPS_LOG_REWRITE; case "ruby.array-small": diff --git a/src/options.yml b/src/options.yml index 6548b3674275..2d813bc4e25b 100644 --- a/src/options.yml +++ b/src/options.yml @@ -127,10 +127,8 @@ EXPERT: # C extension options CEXTS: [cexts, boolean, true, Enable use of C extensions] - CEXTS_SULONG: [cexts-sulong, boolean, false, Run C extensions on Sulong instead of natively] CEXT_LOCK: [cexts-lock, boolean, true, Use a Global Lock when running C extensions] CEXTS_PREPEND_TOOLCHAIN_TO_PATH: [cexts-prepend-toolchain-to-path, boolean, true, Prepend the GraalVM LLVM Toolchain to PATH when installing gems] - CEXTS_KEEP_HANDLES_ALIVE: [keep-handles-alive, boolean, false, Keep handles for value wrappers alive forever] # Debugging the values of options OPTIONS_LOG: [options-log, boolean, false, Log the final value of all options] @@ -177,6 +175,9 @@ INTERNAL: # Options for debugging the TruffleRuby implementation CEXTS_TO_NATIVE_STATS: [cexts-to-native-stats, boolean, false, Track the number of conversions of VALUEs to native and print the stats at application exit] CEXTS_TO_NATIVE_COUNT: [cexts-to-native-count, boolean, CEXTS_TO_NATIVE_STATS, Track the number of conversions of VALUEs to native] BACKTRACE_ON_TO_NATIVE: [backtraces-to-native, boolean, false, Show a backtrace when a ValueWrapper handle is created for a Ruby object] + CEXTS_KEEP_HANDLES_ALIVE: [keep-handles-alive, boolean, false, Keep handles for value wrappers alive forever] + # jt clean cexts && TRUFFLERUBYOPT="--experimental-options --cexts-sulong" jt build && TRUFFLERUBYOPT="--experimental-options --cexts-sulong" jt test :capi + CEXTS_SULONG: [cexts-sulong, boolean, false, "Run C extensions on Sulong instead of natively, requires rebuilding TruffleRuby from source with this option"] # Options to debug the implementation LAZY_BUILTINS: [lazy-builtins, boolean, LAZY_CALLTARGETS, Load builtin classes (core methods & primitives) lazily on first use] diff --git a/src/shared/java/org/truffleruby/shared/options/OptionsCatalog.java b/src/shared/java/org/truffleruby/shared/options/OptionsCatalog.java index 8cc1c98f522c..5a3053bcb5ee 100644 --- a/src/shared/java/org/truffleruby/shared/options/OptionsCatalog.java +++ b/src/shared/java/org/truffleruby/shared/options/OptionsCatalog.java @@ -71,10 +71,8 @@ public final class OptionsCatalog { public static final OptionKey BACKTRACE_ON_NEW_THREAD_KEY = new OptionKey<>(false); public static final OptionKey BACKTRACE_ON_NEW_FIBER_KEY = new OptionKey<>(false); public static final OptionKey CEXTS_KEY = new OptionKey<>(true); - public static final OptionKey CEXTS_SULONG_KEY = new OptionKey<>(false); public static final OptionKey CEXT_LOCK_KEY = new OptionKey<>(true); public static final OptionKey CEXTS_PREPEND_TOOLCHAIN_TO_PATH_KEY = new OptionKey<>(true); - public static final OptionKey CEXTS_KEEP_HANDLES_ALIVE_KEY = new OptionKey<>(false); public static final OptionKey OPTIONS_LOG_KEY = new OptionKey<>(false); public static final OptionKey LOG_LOAD_KEY = new OptionKey<>(false); public static final OptionKey LOG_AUTOLOAD_KEY = new OptionKey<>(false); @@ -106,6 +104,8 @@ public final class OptionsCatalog { public static final OptionKey CEXTS_TO_NATIVE_STATS_KEY = new OptionKey<>(false); public static final OptionKey CEXTS_TO_NATIVE_COUNT_KEY = new OptionKey<>(CEXTS_TO_NATIVE_STATS_KEY.getDefaultValue()); public static final OptionKey BACKTRACE_ON_TO_NATIVE_KEY = new OptionKey<>(false); + public static final OptionKey CEXTS_KEEP_HANDLES_ALIVE_KEY = new OptionKey<>(false); + public static final OptionKey CEXTS_SULONG_KEY = new OptionKey<>(false); public static final OptionKey LAZY_BUILTINS_KEY = new OptionKey<>(LAZY_CALLTARGETS_KEY.getDefaultValue()); public static final OptionKey LAZY_TRANSLATION_CORE_KEY = new OptionKey<>(LAZY_CALLTARGETS_KEY.getDefaultValue()); public static final OptionKey CHAOS_DATA_KEY = new OptionKey<>(false); @@ -574,14 +574,6 @@ public final class OptionsCatalog { .usageSyntax("") .build(); - public static final OptionDescriptor CEXTS_SULONG = OptionDescriptor - .newBuilder(CEXTS_SULONG_KEY, "ruby.cexts-sulong") - .help("Run C extensions on Sulong instead of natively") - .category(OptionCategory.EXPERT) - .stability(OptionStability.EXPERIMENTAL) - .usageSyntax("") - .build(); - public static final OptionDescriptor CEXT_LOCK = OptionDescriptor .newBuilder(CEXT_LOCK_KEY, "ruby.cexts-lock") .help("Use a Global Lock when running C extensions") @@ -598,14 +590,6 @@ public final class OptionsCatalog { .usageSyntax("") .build(); - public static final OptionDescriptor CEXTS_KEEP_HANDLES_ALIVE = OptionDescriptor - .newBuilder(CEXTS_KEEP_HANDLES_ALIVE_KEY, "ruby.keep-handles-alive") - .help("Keep handles for value wrappers alive forever") - .category(OptionCategory.EXPERT) - .stability(OptionStability.EXPERIMENTAL) - .usageSyntax("") - .build(); - public static final OptionDescriptor OPTIONS_LOG = OptionDescriptor .newBuilder(OPTIONS_LOG_KEY, "ruby.options-log") .help("Log the final value of all options") @@ -854,6 +838,22 @@ public final class OptionsCatalog { .usageSyntax("") .build(); + public static final OptionDescriptor CEXTS_KEEP_HANDLES_ALIVE = OptionDescriptor + .newBuilder(CEXTS_KEEP_HANDLES_ALIVE_KEY, "ruby.keep-handles-alive") + .help("Keep handles for value wrappers alive forever") + .category(OptionCategory.INTERNAL) + .stability(OptionStability.EXPERIMENTAL) + .usageSyntax("") + .build(); + + public static final OptionDescriptor CEXTS_SULONG = OptionDescriptor + .newBuilder(CEXTS_SULONG_KEY, "ruby.cexts-sulong") + .help("Run C extensions on Sulong instead of natively, requires rebuilding TruffleRuby from source with this option") + .category(OptionCategory.INTERNAL) + .stability(OptionStability.EXPERIMENTAL) + .usageSyntax("") + .build(); + public static final OptionDescriptor LAZY_BUILTINS = OptionDescriptor .newBuilder(LAZY_BUILTINS_KEY, "ruby.lazy-builtins") .help("Load builtin classes (core methods & primitives) lazily on first use") @@ -1430,14 +1430,10 @@ public static OptionDescriptor fromName(String name) { return BACKTRACE_ON_NEW_FIBER; case "ruby.cexts": return CEXTS; - case "ruby.cexts-sulong": - return CEXTS_SULONG; case "ruby.cexts-lock": return CEXT_LOCK; case "ruby.cexts-prepend-toolchain-to-path": return CEXTS_PREPEND_TOOLCHAIN_TO_PATH; - case "ruby.keep-handles-alive": - return CEXTS_KEEP_HANDLES_ALIVE; case "ruby.options-log": return OPTIONS_LOG; case "ruby.log-load": @@ -1500,6 +1496,10 @@ public static OptionDescriptor fromName(String name) { return CEXTS_TO_NATIVE_COUNT; case "ruby.backtraces-to-native": return BACKTRACE_ON_TO_NATIVE; + case "ruby.keep-handles-alive": + return CEXTS_KEEP_HANDLES_ALIVE; + case "ruby.cexts-sulong": + return CEXTS_SULONG; case "ruby.lazy-builtins": return LAZY_BUILTINS; case "ruby.lazy-translation-core": @@ -1676,10 +1676,8 @@ public static OptionDescriptor[] allDescriptors() { BACKTRACE_ON_NEW_THREAD, BACKTRACE_ON_NEW_FIBER, CEXTS, - CEXTS_SULONG, CEXT_LOCK, CEXTS_PREPEND_TOOLCHAIN_TO_PATH, - CEXTS_KEEP_HANDLES_ALIVE, OPTIONS_LOG, LOG_LOAD, LOG_AUTOLOAD, @@ -1711,6 +1709,8 @@ public static OptionDescriptor[] allDescriptors() { CEXTS_TO_NATIVE_STATS, CEXTS_TO_NATIVE_COUNT, BACKTRACE_ON_TO_NATIVE, + CEXTS_KEEP_HANDLES_ALIVE, + CEXTS_SULONG, LAZY_BUILTINS, LAZY_TRANSLATION_CORE, CHAOS_DATA, From c21c3fa7e1b9a1513816082f35f7a3ecb59e88da Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 30 Oct 2023 15:54:38 +0100 Subject: [PATCH 32/50] Move -fdeclspec to cflags/cxxflags * Otherwise `system RbConfig::CONFIG['CC'], *args` fails. --- lib/truffle/rbconfig.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/truffle/rbconfig.rb b/lib/truffle/rbconfig.rb index 3230f0a349f3..d0f36dd3b84c 100644 --- a/lib/truffle/rbconfig.rb +++ b/lib/truffle/rbconfig.rb @@ -62,6 +62,7 @@ module RbConfig prefix = ruby_home rubyhdrdir = "#{prefix}/lib/cext/include" + cflags_pre = '' if sulong ar = Truffle::Boot.toolchain_executable(:AR) @@ -82,11 +83,12 @@ module RbConfig gcc, clang = true, false elsif Truffle::Platform.darwin? ar = 'ar' - cc = 'clang -fdeclspec' - cxx = 'clang++ -fdeclspec' + cc = 'clang' + cxx = 'clang++' ranlib = 'ranlib' strip = 'strip -A -n' gcc, clang = false, true + cflags_pre = '-fdeclspec ' else raise 'Unknown platform' end @@ -294,14 +296,14 @@ module RbConfig mkconfig['LDSHAREDXX'] = "$(CXX) #{ldshared_flags}" cflags = \ - expanded['cflags'] = "#{optflags} #{debugflags} #{warnflags}" - mkconfig['cflags'] = '$(optflags) $(debugflags) $(warnflags)' + expanded['cflags'] = "#{cflags_pre}#{optflags} #{debugflags} #{warnflags}" + mkconfig['cflags'] = "#{cflags_pre}$(optflags) $(debugflags) $(warnflags)" expanded['CFLAGS'] = cflags mkconfig['CFLAGS'] = '$(cflags)' cxxflags = \ - expanded['cxxflags'] = "#{optflags} #{debugflags}" - mkconfig['cxxflags'] = '$(optflags) $(debugflags)' + expanded['cxxflags'] = "#{cflags_pre}#{optflags} #{debugflags}" + mkconfig['cxxflags'] = "#{cflags_pre}$(optflags) $(debugflags)" expanded['CXXFLAGS'] = cxxflags mkconfig['CXXFLAGS'] = '$(cxxflags)' cppflags_hardcoded = Truffle::Platform.darwin? ? ' -D_DARWIN_C_SOURCE' : '' From dea5c24354a8a26afa54e02a2da649cb10849daf Mon Sep 17 00:00:00 2001 From: Andrew Konchin Date: Tue, 31 Oct 2023 17:43:59 +0200 Subject: [PATCH 33/50] Fix `jt test cexts stripped` --- .../cexts/stripped/ext/stripped/extconf.rb | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/test/truffle/cexts/stripped/ext/stripped/extconf.rb b/test/truffle/cexts/stripped/ext/stripped/extconf.rb index db6652c660de..72475355d0b0 100644 --- a/test/truffle/cexts/stripped/ext/stripped/extconf.rb +++ b/test/truffle/cexts/stripped/ext/stripped/extconf.rb @@ -1,11 +1,27 @@ require 'mkmf' + +# This test tests stripping like grpc does it: +# https://github.com/grpc/grpc/blob/54f65e0dbd2151a3ba2ad364327c0c31b200a5ae/src/ruby/ext/grpc/extconf.rb#L125-L126 +strip = RbConfig::CONFIG['STRIP'] +if RUBY_PLATFORM.include? 'darwin' + # This is necessary on macOS, otherwise it fails like: + # .../strip: error: symbols referenced by indirect symbol table entries that can't be stripped in: .../stripped.bundle + # _rb_define_module + # _rb_define_singleton_method + # _rb_str_new_static + # dyld_stub_binder + # + # on both CRuby and TruffleRuby. + strip += ' -x' +end + create_makefile('stripped') contents = File.read('Makefile') contents = "hijack: all strip-ext\n\n" + contents + < Date: Tue, 31 Oct 2023 16:48:05 +0100 Subject: [PATCH 34/50] rb_tr_get_sprintf_args() should be static --- src/main/c/cext/printf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/c/cext/printf.c b/src/main/c/cext/printf.c index d3cec00b84d7..a285116eff29 100644 --- a/src/main/c/cext/printf.c +++ b/src/main/c/cext/printf.c @@ -36,7 +36,7 @@ char* rb_value_to_str(const VALUE *arg, int showsign) { return cstr; } -VALUE rb_tr_get_sprintf_args(va_list args, VALUE types); +static VALUE rb_tr_get_sprintf_args(va_list args, VALUE types); VALUE rb_tr_vsprintf_new_cstr(char *cstr) { if (cstr == NULL) { @@ -106,7 +106,7 @@ enum printf_arg_types { TYPE_SLONGLONG, }; -VALUE rb_tr_get_sprintf_args(va_list args, VALUE types) { +static VALUE rb_tr_get_sprintf_args(va_list args, VALUE types) { VALUE ary = rb_ary_new(); long len = RARRAY_LEN(types); From 5e5e5c58f13c24edce05b4c91f5bf6a4eed963a0 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 31 Oct 2023 17:00:38 +0100 Subject: [PATCH 35/50] undefined symbol results in SIGABRT on macOS --- spec/truffle/capi/unimplemented_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/truffle/capi/unimplemented_spec.rb b/spec/truffle/capi/unimplemented_spec.rb index 84aa96927dca..a261d8573884 100644 --- a/spec/truffle/capi/unimplemented_spec.rb +++ b/spec/truffle/capi/unimplemented_spec.rb @@ -12,7 +12,8 @@ describe "Unimplemented functions in the C-API" do it "abort the process and show an error including the function name" do - out = ruby_exe('require ARGV[0]; CApiRbTrErrorSpecs.new.not_implemented_function("foo")', args: "#{extension_path} 2>&1", exit_status: 127) - out.should.include?('undefined symbol: rb_str_shared_replace') + expected_status = platform_is(:darwin) ? :SIGABRT : 127 + out = ruby_exe('require ARGV[0]; CApiRbTrErrorSpecs.new.not_implemented_function("foo")', args: "#{extension_path} 2>&1", exit_status: expected_status) + out.should =~ /undefined symbol: rb_str_shared_replace|Symbol not found: _rb_str_shared_replace/ end end From ac02f14e12456bf1ca521c28449d94d28364e407 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 1 Nov 2023 12:30:47 +0100 Subject: [PATCH 36/50] Use RBIMPL_WARNING_{PUSH,IGNORED,POP} to ignore warnings in C API specs * We could define ruby/spec-specific macros for this but it's easier to just reuse what's in ruby headers. --- spec/ruby/optional/capi/ext/array_spec.c | 19 +++---------------- spec/ruby/optional/capi/ext/object_spec.c | 22 ++++------------------ spec/ruby/optional/capi/ext/string_spec.c | 22 +++++----------------- spec/ruby/optional/capi/ext/thread_spec.c | 4 +--- 4 files changed, 13 insertions(+), 54 deletions(-) diff --git a/spec/ruby/optional/capi/ext/array_spec.c b/spec/ruby/optional/capi/ext/array_spec.c index 59d3f09986c2..8d5005c89114 100644 --- a/spec/ruby/optional/capi/ext/array_spec.c +++ b/spec/ruby/optional/capi/ext/array_spec.c @@ -197,15 +197,8 @@ static VALUE copy_ary(RB_BLOCK_CALL_FUNC_ARGLIST(el, new_ary)) { } // Suppress deprecations warnings for rb_iterate(), we want to test it while it exists -#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#elif defined(__clang__) && defined(__has_warning) -# if __has_warning("-Wdeprecated-declarations") -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdeprecated-declarations" -# endif -#endif +RBIMPL_WARNING_PUSH() +RBIMPL_WARNING_IGNORED(-Wdeprecated-declarations) static VALUE array_spec_rb_iterate(VALUE self, VALUE ary) { VALUE new_ary = rb_ary_new(); @@ -262,13 +255,7 @@ static VALUE array_spec_rb_block_call_then_yield(VALUE self, VALUE obj) { return Qnil; } -#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic pop -#elif defined(__clang__) && defined(__has_warning) -# if __has_warning("-Wdeprecated-declarations") -# pragma clang diagnostic pop -# endif -#endif +RBIMPL_WARNING_POP() static VALUE array_spec_rb_mem_clear(VALUE self, VALUE obj) { VALUE ary[1]; diff --git a/spec/ruby/optional/capi/ext/object_spec.c b/spec/ruby/optional/capi/ext/object_spec.c index fbcf6d99626b..4c19ec12c719 100644 --- a/spec/ruby/optional/capi/ext/object_spec.c +++ b/spec/ruby/optional/capi/ext/object_spec.c @@ -154,28 +154,14 @@ static VALUE object_specs_rb_obj_method(VALUE self, VALUE obj, VALUE method) { return rb_obj_method(obj, method); } -#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#elif defined(__clang__) && defined(__has_warning) -# if __has_warning("-Wdeprecated-declarations") -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdeprecated-declarations" -# endif -#endif - #ifndef RUBY_VERSION_IS_3_2 +// Suppress deprecations warnings for rb_obj_taint(), we want to test it while it exists +RBIMPL_WARNING_PUSH() +RBIMPL_WARNING_IGNORED(-Wdeprecated-declarations) static VALUE object_spec_rb_obj_taint(VALUE self, VALUE obj) { return rb_obj_taint(obj); } -#endif - -#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic pop -#elif defined(__clang__) && defined(__has_warning) -# if __has_warning("-Wdeprecated-declarations") -# pragma clang diagnostic pop -# endif +RBIMPL_WARNING_POP() #endif static VALUE so_require(VALUE self) { diff --git a/spec/ruby/optional/capi/ext/string_spec.c b/spec/ruby/optional/capi/ext/string_spec.c index 24a9b4e4ca0b..702620b9dacb 100644 --- a/spec/ruby/optional/capi/ext/string_spec.c +++ b/spec/ruby/optional/capi/ext/string_spec.c @@ -254,17 +254,11 @@ VALUE string_spec_rb_str_new5(VALUE self, VALUE str, VALUE ptr, VALUE len) { return rb_str_new5(str, RSTRING_PTR(ptr), FIX2INT(len)); } -#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#elif defined(__clang__) && defined(__has_warning) -# if __has_warning("-Wdeprecated-declarations") -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdeprecated-declarations" -# endif -#endif - #ifndef RUBY_VERSION_IS_3_2 +// Suppress deprecations warnings for rb_tainted_str_new(), we want to test it while it exists +RBIMPL_WARNING_PUSH() +RBIMPL_WARNING_IGNORED(-Wdeprecated-declarations) + VALUE string_spec_rb_tainted_str_new(VALUE self, VALUE str, VALUE len) { return rb_tainted_str_new(RSTRING_PTR(str), FIX2INT(len)); } @@ -272,14 +266,8 @@ VALUE string_spec_rb_tainted_str_new(VALUE self, VALUE str, VALUE len) { VALUE string_spec_rb_tainted_str_new2(VALUE self, VALUE str) { return rb_tainted_str_new2(RSTRING_PTR(str)); } -#endif -#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic pop -#elif defined(__clang__) && defined(__has_warning) -# if __has_warning("-Wdeprecated-declarations") -# pragma clang diagnostic pop -# endif +RBIMPL_WARNING_POP() #endif VALUE string_spec_rb_str_plus(VALUE self, VALUE str1, VALUE str2) { diff --git a/spec/ruby/optional/capi/ext/thread_spec.c b/spec/ruby/optional/capi/ext/thread_spec.c index 6307e5cc99e1..14bd20795402 100644 --- a/spec/ruby/optional/capi/ext/thread_spec.c +++ b/spec/ruby/optional/capi/ext/thread_spec.c @@ -26,9 +26,7 @@ static VALUE thread_spec_rb_thread_alone(VALUE self) { return rb_thread_alone() ? Qtrue : Qfalse; } -#if defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif +RBIMPL_WARNING_IGNORED(-Wdeprecated-declarations) /* This is unblocked by unblock_func(). */ static void* blocking_gvl_func(void* data) { From 43e057154f515d1a3a09411beda51a36e71e4420 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 1 Nov 2023 12:40:31 +0100 Subject: [PATCH 37/50] Set install_name for libtrufflerubytrampoline * So macOS actually looks in rpath instead of ignoring it... --- src/main/c/cext-trampoline/Makefile | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/c/cext-trampoline/Makefile b/src/main/c/cext-trampoline/Makefile index 636495966781..6b0db8845d0e 100644 --- a/src/main/c/cext-trampoline/Makefile +++ b/src/main/c/cext-trampoline/Makefile @@ -11,23 +11,32 @@ endif # If that is the case and fails the build, it might be better to use instead: # CC := $(GRAALVM_TOOLCHAIN_CC) -# Same values as in RbConfig +# Similar values as in RbConfig ifeq ($(OS),Darwin) -CC := clang -fdeclspec +CC := clang +CFLAGS := -fdeclspec -Wall -Werror -fPIC -std=c99 -g else CC := gcc +CFLAGS := -Wall -Werror -fPIC -std=c99 -g endif -CFLAGS := -Wall -Werror -fPIC -std=c99 -g LDFLAGS := +ifeq ($(OS),Darwin) +# Set the install_name of libtrufflerubytrampoline on macOS, so mkmf executables linking to it +# will know they need to look at the rpath to find it. +LIBS := -Wl,-install_name,@rpath/libtrufflerubytrampoline.$(SOEXT) +else +LIBS := +endif + ROOT := $(realpath ../../../..) RUBY_HDR_DIR := $(ROOT)/lib/cext/include OBJECT_FILES := trampoline.o st.o strlcpy.o cext_constants.o libtrufflerubytrampoline.$(SOEXT): $(OBJECT_FILES) Makefile - $(Q) $(CC) -shared $(LDFLAGS) -o $@ $(OBJECT_FILES) + $(Q) $(CC) -shared $(LDFLAGS) -o $@ $(OBJECT_FILES) $(LIBS) %.o: %.c Makefile $(Q) $(CC) -o $@ -c $(CFLAGS) $(LDFLAGS) -I$(RUBY_HDR_DIR) -I$(ROOT)/lib/cext/include/stubs -I$(TRUFFLE_NFI_NATIVE_INCLUDE) $< From 3689787a5dc5b995991cabeac3652dfa721d6378 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 1 Nov 2023 12:47:53 +0100 Subject: [PATCH 38/50] Workaround to get grpc to build in macOS --- lib/truffle/rbconfig.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/truffle/rbconfig.rb b/lib/truffle/rbconfig.rb index d0f36dd3b84c..47f4e9be779c 100644 --- a/lib/truffle/rbconfig.rb +++ b/lib/truffle/rbconfig.rb @@ -86,7 +86,10 @@ module RbConfig cc = 'clang' cxx = 'clang++' ranlib = 'ranlib' - strip = 'strip -A -n' + # We add -x here, `strip -A -n` always fails, like `error: symbols referenced by indirect symbol table entries that can't be stripped` even on CRuby. + # This is notably necessary for grpc where the current logic does not append -x for TruffleRuby: + # https://github.com/grpc/grpc/blob/54f65e0dbd2151a3ba2ad364327c0c31b200a5ae/src/ruby/ext/grpc/extconf.rb#L125-L126 + strip = 'strip -A -n -x' gcc, clang = false, true cflags_pre = '-fdeclspec ' else From 5a31a59cc04c4830873806f61b98c01db7f490a6 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 1 Nov 2023 12:50:11 +0100 Subject: [PATCH 39/50] Avoid -Wgnu-zero-variadic-macro-arguments warning * .../languages/ruby/lib/cext/include/truffleruby/truffleruby.h:48:59: warning: token pasting of ',' and __VA_ARGS__ is a GNU extension [-Wgnu-zero-variadic-macro-arguments] rb_exc_raise(rb_exc_new_str(EXCEPTION, rb_sprintf(FORMAT, ##__VA_ARGS__))) ^ --- lib/cext/include/truffleruby/truffleruby.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cext/include/truffleruby/truffleruby.h b/lib/cext/include/truffleruby/truffleruby.h index bb89e9cf7fd8..962f9dcc5d6d 100644 --- a/lib/cext/include/truffleruby/truffleruby.h +++ b/lib/cext/include/truffleruby/truffleruby.h @@ -44,8 +44,8 @@ typedef void* (gvl_call)(void *); // Exceptions -#define rb_raise(EXCEPTION, FORMAT, ...) \ - rb_exc_raise(rb_exc_new_str(EXCEPTION, rb_sprintf(FORMAT, ##__VA_ARGS__))) +#define rb_raise(EXCEPTION, ...) \ + rb_exc_raise(rb_exc_new_str(EXCEPTION, rb_sprintf(__VA_ARGS__))) // Macros for rb_funcall(). As written they currently only work on Sulong. // From ac0ca1a2694c9c1a48f2e60f439599300e1de5a9 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 1 Nov 2023 18:16:10 +0100 Subject: [PATCH 40/50] Remove dead code --- .../core/objectspace/ObjectSpaceNodes.java | 6 ------ .../org/truffleruby/language/loader/RequireNode.java | 11 ----------- 2 files changed, 17 deletions(-) diff --git a/src/main/java/org/truffleruby/core/objectspace/ObjectSpaceNodes.java b/src/main/java/org/truffleruby/core/objectspace/ObjectSpaceNodes.java index d51d7b981bbb..58297d7ad5d9 100644 --- a/src/main/java/org/truffleruby/core/objectspace/ObjectSpaceNodes.java +++ b/src/main/java/org/truffleruby/core/objectspace/ObjectSpaceNodes.java @@ -272,12 +272,6 @@ Object defineFinalizer(RubyDynamicObject object, Object finalizerCFunction, Obje return nil; } - - - @TruffleBoundary - private void startSharing() { - getContext().getSharedObjects().startSharing(getLanguage(), "creating finalizer"); - } } @CoreMethod(names = "undefine_finalizer", isModuleFunction = true, required = 1) diff --git a/src/main/java/org/truffleruby/language/loader/RequireNode.java b/src/main/java/org/truffleruby/language/loader/RequireNode.java index 191e7fce5c39..56a7fa48f36d 100644 --- a/src/main/java/org/truffleruby/language/loader/RequireNode.java +++ b/src/main/java/org/truffleruby/language/loader/RequireNode.java @@ -346,17 +346,6 @@ private Throwable searchForException(String exceptionClass, Throwable exception) return null; } - @TruffleBoundary - private String getBaseName(String path) { - final String name = new File(path).getName(); - final int firstDot = name.indexOf('.'); - if (firstDot == -1) { - return name; - } else { - return name.substring(0, firstDot); - } - } - public boolean isFeatureLoaded(Object feature) { final Object included = isInLoadedFeatures .call(coreLibrary().truffleFeatureLoaderModule, "feature_provided?", feature, true); From c932d5b0beb6d9c96cbc1ed6bb154daaa3495249 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 8 Nov 2023 15:07:44 +0100 Subject: [PATCH 41/50] Avoid struct-by-value argument for rb_thread_wait_for() * struct by value arguments are not well supported from NFI to Sulong, sometimes it works by chance due to how it is compiled to bitcode. * This is the only struct-by-value argument in the C API subset we implement. --- lib/cext/include/ruby/internal/intern/thread.h | 7 +++++++ src/main/c/cext/thread.c | 4 ++-- tool/generate-cext-trampoline.rb | 10 +++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/cext/include/ruby/internal/intern/thread.h b/lib/cext/include/ruby/internal/intern/thread.h index 716375acd768..be511a50d754 100644 --- a/lib/cext/include/ruby/internal/intern/thread.h +++ b/lib/cext/include/ruby/internal/intern/thread.h @@ -196,7 +196,14 @@ VALUE rb_thread_create(VALUE (*f)(void *g), void *g); * @param[in] time Duration. * @exception rb_eInterrupt Interrupted. */ +#ifdef TRUFFLERUBY +void rb_tr_thread_wait_for(struct timeval* time); +static inline void rb_thread_wait_for(struct timeval time) { + rb_tr_thread_wait_for(&time); +} +#else void rb_thread_wait_for(struct timeval time); +#endif /** * Obtains the "current" thread. diff --git a/src/main/c/cext/thread.c b/src/main/c/cext/thread.c index 3e1b8c5b79c0..4471ccae427b 100644 --- a/src/main/c/cext/thread.c +++ b/src/main/c/cext/thread.c @@ -29,8 +29,8 @@ VALUE rb_thread_local_aset(VALUE thread, ID id, VALUE val) { return RUBY_INVOKE(thread, "[]=", ID2SYM(id), val); } -void rb_thread_wait_for(struct timeval time) { - double seconds = (double)time.tv_sec + (double)time.tv_usec/1000000; +void rb_tr_thread_wait_for(struct timeval* time) { + double seconds = (double)time->tv_sec + (double)time->tv_usec/1000000; polyglot_invoke(rb_tr_unwrap(rb_mKernel), "sleep", seconds); } diff --git a/tool/generate-cext-trampoline.rb b/tool/generate-cext-trampoline.rb index 81ff68a52709..b3bc442407b4 100755 --- a/tool/generate-cext-trampoline.rb +++ b/tool/generate-cext-trampoline.rb @@ -55,13 +55,17 @@ functions.concat found_functions end +def struct_by_value?(type) + type =~ /\bstruct\b/ and !type.include?('*') +end + functions.each do |declaration, return_type, function_name, argument_types| raise declaration if /\bstatic\b/ =~ declaration raise declaration if function_name.start_with?('rb_tr_init') and function_name != 'rb_tr_init_exception' if declaration.include? "\n" abort "This declaration includes newlines but should not:\n#{declaration}\n\n" end - if return_type =~ /\bstruct\s/ and !return_type.include?('*') + if struct_by_value? return_type abort "Returning a struct by value from Sulong to NFI is not supported for:\n#{declaration}" end end @@ -188,6 +192,10 @@ argument_names = argument_types.delete_prefix('(').delete_suffix(')') argument_names = argument_names.scan(/(?:^|,)\s*(#{argument_regexp})\s*(?=,|$)/o) argument_names = argument_names.map { |full_arg, name1, name2| + if struct_by_value? full_arg + abort "struct by value argument not well supported from NFI to Sulong: #{full_arg}\n#{declaration}" + end + if full_arg == "void" "" elsif full_arg == "..." From 6656678fe019fa7cee78f6fc8375d2c595a6c599 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 8 Nov 2023 18:54:43 +0100 Subject: [PATCH 42/50] Always define HAVE_VA_ARGS_MACRO on TruffleRuby --- lib/cext/include/ruby/internal/config.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/cext/include/ruby/internal/config.h b/lib/cext/include/ruby/internal/config.h index 3d7ccbb4d9d1..8ef6021ca08a 100644 --- a/lib/cext/include/ruby/internal/config.h +++ b/lib/cext/include/ruby/internal/config.h @@ -56,6 +56,11 @@ # define HAVE_VA_ARGS_MACRO # else # /* NG, not known. */ +// Always define HAVE_VA_ARGS_MACRO on TruffleRuby, as it leads to cleaner macros and more optimizations: +// clang++ on macOS Ventura has __cplusplus defined as 199711 but it supports newer standards like c++11 +#ifdef TRUFFLERUBY +#define HAVE_VA_ARGS_MACRO +#endif # endif #endif From 3f6571484f595b6f6287405765e787c182d568ce Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 9 Nov 2023 16:44:42 +0100 Subject: [PATCH 43/50] Detect if CI in tool/generate-config-header.sh --- tool/generate-config-header.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tool/generate-config-header.sh b/tool/generate-config-header.sh index 9016a50011fb..3c620855180f 100755 --- a/tool/generate-config-header.sh +++ b/tool/generate-config-header.sh @@ -5,10 +5,11 @@ set -x VERSION=$(cat .ruby-version) -url="$1" -if [ -z "$url" ]; then +if [ -n "$TRUFFLERUBY_CI" ]; then # The source archive, a copy from https://www.ruby-lang.org/en/downloads/ url=$(mx urlrewrite "https://lafo.ssw.uni-linz.ac.at/pub/graal-external-deps/ruby-$VERSION.tar.gz") +else + url="https://cache.ruby-lang.org/pub/ruby/${VERSION%.*}/ruby-$VERSION.tar.gz" fi os=$(uname -s) From da2df4e025cc8ddc70ac2044fc298419687c01b3 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 9 Nov 2023 16:49:53 +0100 Subject: [PATCH 44/50] Also check the native config after generating in generate-native-config CI jobs --- ci.jsonnet | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ci.jsonnet b/ci.jsonnet index 7f3a2e8c1522..1e0b7f4f4685 100644 --- a/ci.jsonnet +++ b/ci.jsonnet @@ -703,11 +703,12 @@ local composition_environment = utils.add_inclusion_tracking(part_definitions, " manual_builds: { local shared = $.use.common + $.cap.manual + { timelimit: "15:00" }, + local native_config = $.run.generate_native_config + $.run.check_native_config, - "ruby-generate-native-config-linux-amd64": $.platform.linux + $.jdk.stable + shared + $.run.generate_native_config, - "ruby-generate-native-config-linux-aarch64": $.platform.linux_aarch64 + $.jdk.stable + shared + $.run.generate_native_config, - "ruby-generate-native-config-darwin-amd64": $.platform.darwin_amd64 + $.jdk.stable + shared + $.run.generate_native_config, - "ruby-generate-native-config-darwin-aarch64": $.platform.darwin_aarch64 + $.jdk.stable + shared + $.run.generate_native_config, + "ruby-generate-native-config-linux-amd64": $.platform.linux + $.jdk.stable + shared + native_config, + "ruby-generate-native-config-linux-aarch64": $.platform.linux_aarch64 + $.jdk.stable + shared + native_config, + "ruby-generate-native-config-darwin-amd64": $.platform.darwin_amd64 + $.jdk.stable + shared + native_config, + "ruby-generate-native-config-darwin-aarch64": $.platform.darwin_aarch64 + $.jdk.stable + shared + native_config, }, builds: From d787504fdf4e3ef74512aea2ed97d39de35c1f6b Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 9 Nov 2023 16:35:54 +0100 Subject: [PATCH 45/50] Generate the native configs using the system compiler --- ci.jsonnet | 9 +-------- lib/cext/include/truffleruby/config_linux_aarch64.h | 11 ++++++----- lib/cext/include/truffleruby/config_linux_amd64.h | 9 ++++++--- tool/generate-config-header.sh | 8 -------- tool/generate-native-config.rb | 2 +- 5 files changed, 14 insertions(+), 25 deletions(-) diff --git a/ci.jsonnet b/ci.jsonnet index 1e0b7f4f4685..fa95361e2e9c 100644 --- a/ci.jsonnet +++ b/ci.jsonnet @@ -383,18 +383,11 @@ local part_definitions = { generate_native_config: { setup+: [ ["ruby", "-e", "pp RbConfig::MAKEFILE_CONFIG"], # For convenience - ["mx", "sforceimports"], # clone the graal repo - ["mx", "-p", "../graal/sulong", "build"], - ["set-export", "TOOLCHAIN_PATH", ["mx", "-p", "../graal/sulong", "lli", "--print-toolchain-path"]], ], run+: [ - ["env", - "LD_LIBRARY_PATH=$BUILD_DIR/graal/sulong/mxbuild/" + self.os + "-" + self.arch + "/SULONG_HOME/native/lib:$LD_LIBRARY_PATH", # for finding libc++ - "PATH=$TOOLCHAIN_PATH:$PATH", - "ruby", "tool/generate-native-config.rb"], + ["ruby", "tool/generate-native-config.rb"], ["cat", "src/main/java/org/truffleruby/platform/" + self.platform_name + "NativeConfiguration.java"], - # Uses the system compiler as using the toolchain for this does not work on macOS ["tool/generate-config-header.sh"], ["cat", "lib/cext/include/truffleruby/config_" + self.os + "_" + self.arch + ".h"], ], diff --git a/lib/cext/include/truffleruby/config_linux_aarch64.h b/lib/cext/include/truffleruby/config_linux_aarch64.h index 671bd43b1174..4155af2cf5ae 100644 --- a/lib/cext/include/truffleruby/config_linux_aarch64.h +++ b/lib/cext/include/truffleruby/config_linux_aarch64.h @@ -51,6 +51,7 @@ #define HAVE_LOCALE_H 1 #define HAVE_MALLOC_H 1 #define HAVE_PWD_H 1 +#define HAVE_SANITIZER_ASAN_INTERFACE_H 1 #define HAVE_STDALIGN_H 1 #define HAVE_STDIO_H 1 #define HAVE_SYS_EVENTFD_H 1 @@ -155,7 +156,6 @@ #define DEPRECATED_BY(n,x) __attribute__ ((__deprecated__("by "#n))) x #define NOINLINE(x) __attribute__ ((__noinline__)) x #define ALWAYS_INLINE(x) __attribute__ ((__always_inline__)) x -#define NO_SANITIZE(san, x) __attribute__ ((__no_sanitize__(san))) x #define NO_SANITIZE_ADDRESS(x) __attribute__ ((__no_sanitize_address__)) x #define NO_ADDRESS_SAFETY_ANALYSIS(x) __attribute__ ((__no_address_safety_analysis__)) x #define WARN_UNUSED_RESULT(x) __attribute__ ((__warn_unused_result__)) x @@ -166,7 +166,8 @@ #define HAVE_FUNC_WEAK 1 #define RUBY_CXX_DEPRECATED(msg) __attribute__((__deprecated__(msg))) #define HAVE_NULLPTR 1 -#define FUNC_CDECL(x) __attribute__ ((__cdecl__)) x +#define FUNC_UNOPTIMIZED(x) __attribute__ ((__optimize__("O0"))) x +#define FUNC_MINIMIZED(x) __attribute__ ((__optimize__("-Os","-fomit-frame-pointer"))) x #define HAVE_ATTRIBUTE_FUNCTION_ALIAS 1 #define RUBY_ALIAS_FUNCTION_TYPE(type, prot, name, args) type prot __attribute__((alias(#name))); #define RUBY_ALIAS_FUNCTION_VOID(prot, name, args) RUBY_ALIAS_FUNCTION_TYPE(void, prot, name, args) @@ -406,6 +407,7 @@ #define HAVE_BUILTIN___BUILTIN_ADD_OVERFLOW 1 #define HAVE_BUILTIN___BUILTIN_SUB_OVERFLOW 1 #define HAVE_BUILTIN___BUILTIN_MUL_OVERFLOW 1 +#define HAVE_BUILTIN___BUILTIN_MUL_OVERFLOW_P 1 #define HAVE_BUILTIN___BUILTIN_CONSTANT_P 1 #define HAVE_BUILTIN___BUILTIN_CHOOSE_EXPR 1 #define HAVE_BUILTIN___BUILTIN_CHOOSE_EXPR_CONSTANT_P 1 @@ -454,9 +456,8 @@ #define DLEXT_MAXLEN 3 #define DLEXT ".so" #define HAVE__SETJMP 1 -#define RUBY_SETJMP(env) _setjmp((env)) -#define RUBY_LONGJMP(env,val) _longjmp((env),val) -#define RUBY_JMP_BUF jmp_buf +#define RUBY_SETJMP(env) __builtin_setjmp((env)) +#define RUBY_LONGJMP(env,val) __builtin_longjmp((env),val) #define USE_MJIT 1 #define USE_YJIT 0 #define RUBY_PLATFORM "aarch64-linux" diff --git a/lib/cext/include/truffleruby/config_linux_amd64.h b/lib/cext/include/truffleruby/config_linux_amd64.h index 52e171b29ed0..180d7e1462f3 100644 --- a/lib/cext/include/truffleruby/config_linux_amd64.h +++ b/lib/cext/include/truffleruby/config_linux_amd64.h @@ -51,6 +51,7 @@ #define HAVE_LOCALE_H 1 #define HAVE_MALLOC_H 1 #define HAVE_PWD_H 1 +#define HAVE_SANITIZER_ASAN_INTERFACE_H 1 #define HAVE_STDALIGN_H 1 #define HAVE_STDIO_H 1 #define HAVE_SYS_EVENTFD_H 1 @@ -169,7 +170,8 @@ #define HAVE_FUNC_WEAK 1 #define RUBY_CXX_DEPRECATED(msg) __attribute__((__deprecated__(msg))) #define HAVE_NULLPTR 1 -#define FUNC_CDECL(x) __attribute__ ((__cdecl__)) x +#define FUNC_UNOPTIMIZED(x) __attribute__ ((__optimize__("O0"))) x +#define FUNC_MINIMIZED(x) __attribute__ ((__optimize__("-Os","-fomit-frame-pointer"))) x #define HAVE_ATTRIBUTE_FUNCTION_ALIAS 1 #define RUBY_ALIAS_FUNCTION_TYPE(type, prot, name, args) type prot __attribute__((alias(#name))); #define RUBY_ALIAS_FUNCTION_VOID(prot, name, args) RUBY_ALIAS_FUNCTION_TYPE(void, prot, name, args) @@ -409,6 +411,7 @@ #define HAVE_BUILTIN___BUILTIN_ADD_OVERFLOW 1 #define HAVE_BUILTIN___BUILTIN_SUB_OVERFLOW 1 #define HAVE_BUILTIN___BUILTIN_MUL_OVERFLOW 1 +#define HAVE_BUILTIN___BUILTIN_MUL_OVERFLOW_P 1 #define HAVE_BUILTIN___BUILTIN_CONSTANT_P 1 #define HAVE_BUILTIN___BUILTIN_CHOOSE_EXPR 1 #define HAVE_BUILTIN___BUILTIN_CHOOSE_EXPR_CONSTANT_P 1 @@ -457,8 +460,8 @@ #define DLEXT_MAXLEN 3 #define DLEXT ".so" #define HAVE__SETJMP 1 -#define RUBY_SETJMP(env) __builtin_setjmp((void **)(env)) -#define RUBY_LONGJMP(env,val) __builtin_longjmp((void **)(env),val) +#define RUBY_SETJMP(env) __builtin_setjmp((env)) +#define RUBY_LONGJMP(env,val) __builtin_longjmp((env),val) #define USE_MJIT 1 #define USE_YJIT 0 #define RUBY_PLATFORM "x86_64-linux" diff --git a/tool/generate-config-header.sh b/tool/generate-config-header.sh index 3c620855180f..1ec98e305765 100755 --- a/tool/generate-config-header.sh +++ b/tool/generate-config-header.sh @@ -22,14 +22,6 @@ arch=${arch/arm64/aarch64} mx_platform="${os}_${arch}" -if [ "$os" != "darwin" ]; then - if [ -z "$TOOLCHAIN_PATH" ]; then - echo "TOOLCHAIN_PATH must be set" - exit 1 - fi - export PATH="$TOOLCHAIN_PATH:$PATH" -fi - archive=$(basename "$url") if [ ! -e "$archive" ]; then diff --git a/tool/generate-native-config.rb b/tool/generate-native-config.rb index f5d531bc8089..1077a2b1cb03 100644 --- a/tool/generate-native-config.rb +++ b/tool/generate-native-config.rb @@ -188,7 +188,7 @@ def field(name) end def cc - ENV.fetch('CXX', 'clang++') + ENV.fetch('CXX', 'c++') end def source_file From b82e4b6d3b4bc372aaee29a95a17950ee2b343de Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 9 Nov 2023 17:42:36 +0100 Subject: [PATCH 46/50] Add ChangeLog entry * Remove entry about the LLVM toolchain since it is no longer relevant for users. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb54d23397d..62a195fbaca5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ New features: +* C/C++ extensions are now compiled using the system toolchain and executed natively instead of using GraalVM LLVM (Sulong). This leads to faster startup, no warmup, better compatibility and faster installation for C/C++ extensions (#3118, @eregon). Bug fixes: @@ -11,7 +12,6 @@ Bug fixes: Compatibility: -* Fix problems with the LLVM toolchain wrappers with Xcode 15 on macOS. * Add `Exception#detailed_message` method (#3257, @andrykonchin). * Fix `rb_enc_vsprintf` and force String encoding instead of converting it (@andrykonchin). * Add `rb_gc_mark_movable` function (@andrykonchin). From 40d2b8099d6715dfa19b4d1eddab35974f92bbf6 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 10 Nov 2023 15:19:28 +0100 Subject: [PATCH 47/50] Use the same devtoolset as GraalVM dev builds and releases * Fixes the missing definition of NO_SANITIZE on linux-aarch64 with GCC 7.3.1. With the devtoolset it's GCC 10.2.1 on linux-aarch64. --- ci.jsonnet | 20 +++++++++++-------- .../truffleruby/config_linux_aarch64.h | 1 + 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/ci.jsonnet b/ci.jsonnet index fa95361e2e9c..76d75b8c6842 100644 --- a/ci.jsonnet +++ b/ci.jsonnet @@ -232,14 +232,18 @@ local part_definitions = { }, platform: { - local common_deps = common.deps.truffleruby + common.deps.sulong, - local linux_amd64_extra_deps = { - packages+: { - binutils: ">=2.30", - }, - }, - - linux: common.linux_amd64 + common_deps + linux_amd64_extra_deps + { + local devtoolset = { # Until there is a proper object in common.jsonnet for it + packages+: if self.os == "linux" then + (if self.arch == "aarch64" then { + "00:devtoolset": "==10", + } else { + "00:devtoolset": "==11", + }) + else {}, + }, + local common_deps = common.deps.truffleruby + common.deps.sulong + devtoolset, + + linux: common.linux_amd64 + common_deps + { platform_name:: "LinuxAMD64", "$.cap":: { normal_machine: [], diff --git a/lib/cext/include/truffleruby/config_linux_aarch64.h b/lib/cext/include/truffleruby/config_linux_aarch64.h index 4155af2cf5ae..727b9bf0c070 100644 --- a/lib/cext/include/truffleruby/config_linux_aarch64.h +++ b/lib/cext/include/truffleruby/config_linux_aarch64.h @@ -156,6 +156,7 @@ #define DEPRECATED_BY(n,x) __attribute__ ((__deprecated__("by "#n))) x #define NOINLINE(x) __attribute__ ((__noinline__)) x #define ALWAYS_INLINE(x) __attribute__ ((__always_inline__)) x +#define NO_SANITIZE(san, x) __attribute__ ((__no_sanitize__(san))) x #define NO_SANITIZE_ADDRESS(x) __attribute__ ((__no_sanitize_address__)) x #define NO_ADDRESS_SAFETY_ANALYSIS(x) __attribute__ ((__no_address_safety_analysis__)) x #define WARN_UNUSED_RESULT(x) __attribute__ ((__warn_unused_result__)) x From b520b4ad328c11952d35af5309a2ab8f04d0ac68 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 13 Nov 2023 13:23:43 +0100 Subject: [PATCH 48/50] Cleanups --- lib/cext/include/ruby/internal/intern/time.h | 18 +++++++++--------- lib/cext/include/truffleruby/truffleruby.h | 2 +- src/main/c/cext/exception.c | 2 +- src/main/c/cext/globals.c | 1 - tool/generate-cext-trampoline.rb | 7 ------- tool/jt.rb | 11 ----------- 6 files changed, 11 insertions(+), 30 deletions(-) diff --git a/lib/cext/include/ruby/internal/intern/time.h b/lib/cext/include/ruby/internal/intern/time.h index 97449276caf2..1d23fc27410a 100644 --- a/lib/cext/include/ruby/internal/intern/time.h +++ b/lib/cext/include/ruby/internal/intern/time.h @@ -121,9 +121,9 @@ void rb_tr_time_interval(VALUE num, struct timeval *result); */ #ifdef TRUFFLERUBY static inline struct timeval rb_time_interval(VALUE num) { - struct timeval result; - rb_tr_time_interval(num, &result); - return result; + struct timeval result; + rb_tr_time_interval(num, &result); + return result; } #else struct timeval rb_time_interval(VALUE num); @@ -144,9 +144,9 @@ void rb_tr_time_timeval(VALUE time, struct timeval *result); */ #ifdef TRUFFLERUBY static inline struct timeval rb_time_timeval(VALUE time) { - struct timeval result; - rb_tr_time_timeval(time, &result); - return result; + struct timeval result; + rb_tr_time_timeval(time, &result); + return result; } #else struct timeval rb_time_timeval(VALUE time); @@ -165,9 +165,9 @@ void rb_tr_time_timespec(VALUE time, struct timespec *result); */ #ifdef TRUFFLERUBY static inline struct timespec rb_time_timespec(VALUE time) { - struct timespec result; - rb_tr_time_timespec(time, &result); - return result; + struct timespec result; + rb_tr_time_timespec(time, &result); + return result; } #else struct timespec rb_time_timespec(VALUE time); diff --git a/lib/cext/include/truffleruby/truffleruby.h b/lib/cext/include/truffleruby/truffleruby.h index 962f9dcc5d6d..6e2076bc12a2 100644 --- a/lib/cext/include/truffleruby/truffleruby.h +++ b/lib/cext/include/truffleruby/truffleruby.h @@ -306,7 +306,7 @@ static inline int rb_tr_scan_args_kw_int(int kw_flag, int argc, VALUE *argv, str } if (argn < argc) { - rb_error_arity(argc, n_mand, parse_data.rest ? UNLIMITED_ARGUMENTS : n_mand + n_opt); + rb_error_arity(argc, n_mand, parse_data.rest ? UNLIMITED_ARGUMENTS : n_mand + n_opt); } return argc; diff --git a/src/main/c/cext/exception.c b/src/main/c/cext/exception.c index 61e8165a69bd..27f4cc7e7b55 100644 --- a/src/main/c/cext/exception.c +++ b/src/main/c/cext/exception.c @@ -26,7 +26,7 @@ VALUE rb_exc_new_str(VALUE exception_class, VALUE message) { } void rb_exc_raise(VALUE exception) { - RUBY_CEXT_INVOKE_NO_WRAP("rb_exc_raise", exception); // TODO + RUBY_CEXT_INVOKE_NO_WRAP("rb_exc_raise", exception); UNREACHABLE; } diff --git a/src/main/c/cext/globals.c b/src/main/c/cext/globals.c index 28158f0cd77d..0513e848a3a1 100644 --- a/src/main/c/cext/globals.c +++ b/src/main/c/cext/globals.c @@ -29,7 +29,6 @@ void rb_gvar_var_setter(VALUE val, ID id, VALUE *data) { } void rb_define_hooked_variable(const char *name, VALUE *var, rb_gvar_getter_t *getter, rb_gvar_setter_t *setter) { -// void rb_define_hooked_variable(const char *name, VALUE *var, VALUE (*getter)(ANYARGS), void (*setter)(ANYARGS)) { if (!getter) { getter = rb_gvar_var_getter; } diff --git a/tool/generate-cext-trampoline.rb b/tool/generate-cext-trampoline.rb index b3bc442407b4..178289d10556 100755 --- a/tool/generate-cext-trampoline.rb +++ b/tool/generate-cext-trampoline.rb @@ -247,11 +247,4 @@ def struct_by_value?(type) f.puts " impl_#{function_name} = get_libtruffleruby_function(\"#{function_name}\");" end f.puts "}" - -# f.puts -# f.puts < Date: Tue, 31 Oct 2023 17:21:13 +0100 Subject: [PATCH 49/50] Bump ABI version --- lib/cext/include/truffleruby/truffleruby-abi-version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cext/include/truffleruby/truffleruby-abi-version.h b/lib/cext/include/truffleruby/truffleruby-abi-version.h index cd9b9087b9df..8d656f409e1f 100644 --- a/lib/cext/include/truffleruby/truffleruby-abi-version.h +++ b/lib/cext/include/truffleruby/truffleruby-abi-version.h @@ -10,6 +10,6 @@ // $RUBY_VERSION must be the same as TruffleRuby.LANGUAGE_VERSION. // $ABI_NUMBER starts at 1 and is incremented for every ABI-incompatible change. -#define TRUFFLERUBY_ABI_VERSION "3.2.2.4" +#define TRUFFLERUBY_ABI_VERSION "3.2.2.5" #endif From e76aefbbbe5d6a060ea82136cc255ba11c1c71da Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 13 Nov 2023 14:54:36 +0100 Subject: [PATCH 50/50] Add gate to check :cext specs pass under Sulong with the debug option --- ci.jsonnet | 8 ++++++++ spec/truffle/capi/unimplemented_spec.rb | 10 ++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ci.jsonnet b/ci.jsonnet index 76d75b8c6842..cd71b96f4ab8 100644 --- a/ci.jsonnet +++ b/ci.jsonnet @@ -376,6 +376,13 @@ local part_definitions = { jt(["test", "bundle"]), }, + test_cexts_sulong: { + environment+: { + TRUFFLERUBYOPT: "--experimental-options --cexts-sulong", + }, + run+: jt(["test", "specs", ":cext"]), + }, + testdownstream_aot: { run+: [["mx", "ruby_testdownstream_aot", "$RUBY_BIN"]] }, test_make_standalone_distribution: { @@ -536,6 +543,7 @@ local composition_environment = utils.add_inclusion_tracking(part_definitions, " "ruby-test-cexts-linux-aarch64": $.platform.linux_aarch64 + $.jdk.stable + $.env.jvm + gate + $.use.gem_test_pack + $.use.sqlite331 + $.run.test_cexts, "ruby-test-cexts-darwin-amd64": $.platform.darwin_amd64 + $.jdk.stable + $.env.jvm + gate + $.use.gem_test_pack + $.run.test_cexts + { timelimit: "01:30:00" }, "ruby-test-cexts-darwin-aarch64": $.platform.darwin_aarch64 + $.jdk.stable + $.env.jvm + gate + $.use.gem_test_pack + $.run.test_cexts + { timelimit: "00:40:00" }, + "ruby-test-cexts-sulong": $.platform.linux + $.jdk.stable + $.env.jvm + gate + $.run.test_cexts_sulong + { timelimit: "20:00" }, "ruby-test-gems-linux-amd64": $.platform.linux + $.jdk.stable + $.env.jvm + gate + $.use.gem_test_pack + $.run.test_gems, "ruby-test-gems-darwin-amd64": $.platform.darwin_amd64 + $.jdk.stable + $.env.jvm + gate + $.use.gem_test_pack + $.run.test_gems, "ruby-test-gems-darwin-aarch64": $.platform.darwin_aarch64 + $.jdk.stable + $.env.jvm + gate + $.use.gem_test_pack + $.run.test_gems, diff --git a/spec/truffle/capi/unimplemented_spec.rb b/spec/truffle/capi/unimplemented_spec.rb index a261d8573884..033aa2faa4c6 100644 --- a/spec/truffle/capi/unimplemented_spec.rb +++ b/spec/truffle/capi/unimplemented_spec.rb @@ -11,9 +11,11 @@ extension_path = load_extension("unimplemented") describe "Unimplemented functions in the C-API" do - it "abort the process and show an error including the function name" do - expected_status = platform_is(:darwin) ? :SIGABRT : 127 - out = ruby_exe('require ARGV[0]; CApiRbTrErrorSpecs.new.not_implemented_function("foo")', args: "#{extension_path} 2>&1", exit_status: expected_status) - out.should =~ /undefined symbol: rb_str_shared_replace|Symbol not found: _rb_str_shared_replace/ + guard_not -> { Truffle::Boot.get_option('cexts-sulong') } do + it "abort the process and show an error including the function name" do + expected_status = platform_is(:darwin) ? :SIGABRT : 127 + out = ruby_exe('require ARGV[0]; CApiRbTrErrorSpecs.new.not_implemented_function("foo")', args: "#{extension_path} 2>&1", exit_status: expected_status) + out.should =~ /undefined symbol: rb_str_shared_replace|Symbol not found: _rb_str_shared_replace/ + end end end