Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .gdbinit
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ define rp
else
if ($flags & RUBY_T_MASK) == RUBY_T_IMEMO
printf "%sT_IMEMO%s(", $color_type, $color_end
output (enum imemo_type)(($flags>>RUBY_FL_USHIFT)&RUBY_IMEMO_MASK)
output (enum imemo_type)(($flags&RUBY_IMEMO_MASK)>>RUBY_FL_USHIFT)
printf "): "
rp_imemo $arg0
else
Expand Down Expand Up @@ -539,7 +539,7 @@ document rp_class
end

define rp_imemo
set $flags = (enum imemo_type)((((struct RBasic *)($arg0))->flags >> RUBY_FL_USHIFT) & RUBY_IMEMO_MASK)
set $flags = (enum imemo_type)((((struct RBasic *)($arg0))->flags & RUBY_IMEMO_MASK) >> RUBY_FL_USHIFT)
if $flags == imemo_cref
printf "(rb_cref_t *) %p\n", (void*)$arg0
print *(rb_cref_t *)$arg0
Expand Down Expand Up @@ -1100,7 +1100,7 @@ define rb_ps_thread
while $cfp < $cfpend
if $cfp->_iseq
set $iseq = rb_get_cfp_iseq($cfp)
if !((VALUE)$iseq & RUBY_IMMEDIATE_MASK) && (((imemo_ifunc << RUBY_FL_USHIFT) | RUBY_T_IMEMO)==$iseq->flags & ((RUBY_IMEMO_MASK << RUBY_FL_USHIFT) | RUBY_T_MASK))
if !((VALUE)$iseq & RUBY_IMMEDIATE_MASK) && (((imemo_ifunc << RUBY_FL_USHIFT) | RUBY_T_IMEMO)==$iseq->flags & (RUBY_IMEMO_MASK | RUBY_T_MASK))
printf "%d:ifunc ", $cfpend-$cfp
set print symbol-filename on
output/a $iseq.body
Expand Down
2 changes: 1 addition & 1 deletion ext/objspace/objspace.c
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ count_tdata_objects(int argc, VALUE *argv, VALUE self)
return hash;
}

static ID imemo_type_ids[IMEMO_MASK+1];
static ID imemo_type_ids[(IMEMO_MASK >> FL_USHIFT) + 1];

static void
count_imemo_objects_i(VALUE v, void *data)
Expand Down
26 changes: 13 additions & 13 deletions internal/imemo.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@
#include "ruby/internal/stdbool.h" /* for bool */
#include "ruby/ruby.h" /* for rb_block_call_func_t */

#define IMEMO_MASK 0x0f

/* FL_USER0 to FL_USER3 is for type */
#define IMEMO_FL_USHIFT (FL_USHIFT + 4)
#define IMEMO_FL_USER0 FL_USER4
#define IMEMO_FL_USER1 FL_USER5
#define IMEMO_FL_USER2 FL_USER6
#define IMEMO_FL_USER3 FL_USER7
#define IMEMO_FL_USER4 FL_USER8
#define IMEMO_FL_USER5 FL_USER9
#define IMEMO_FL_USER6 FL_USER10
#define IMEMO_MASK (FL_USER0 | FL_USER1 | FL_USER2 | FL_USER3 | FL_USER4)

/* FL_USER0 to FL_USER4 is for type */
#define IMEMO_FL_USHIFT (FL_USHIFT + 5)
#define IMEMO_FL_USER0 FL_USER5
#define IMEMO_FL_USER1 FL_USER6
#define IMEMO_FL_USER2 FL_USER7
#define IMEMO_FL_USER3 FL_USER8
#define IMEMO_FL_USER4 FL_USER9
#define IMEMO_FL_USER5 FL_USER10
#define IMEMO_FL_USER6 FL_USER11

enum imemo_type {
imemo_env = 0,
Expand Down Expand Up @@ -171,15 +171,15 @@ RUBY_SYMBOL_EXPORT_END
static inline enum imemo_type
imemo_type(VALUE imemo)
{
return (RBASIC(imemo)->flags >> FL_USHIFT) & IMEMO_MASK;
return (RBASIC(imemo)->flags & IMEMO_MASK) >> FL_USHIFT;
}

static inline int
imemo_type_p(VALUE imemo, enum imemo_type imemo_type)
{
if (LIKELY(!RB_SPECIAL_CONST_P(imemo))) {
/* fixed at compile time if imemo_type is given. */
const VALUE mask = (IMEMO_MASK << FL_USHIFT) | RUBY_T_MASK;
const VALUE mask = IMEMO_MASK | RUBY_T_MASK;
const VALUE expected_type = (imemo_type << FL_USHIFT) | T_IMEMO;
/* fixed at runtime. */
return expected_type == (RBASIC(imemo)->flags & mask);
Expand Down
13 changes: 13 additions & 0 deletions jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,19 @@ rb_jit_array_len(VALUE a)
return rb_array_len(a);
}

// Return non-zero when `obj` is an array and its last item is a
// `ruby2_keywords` hash. The JITs don't support this kind of splat.
size_t
rb_jit_ruby2_keywords_splat_p(VALUE obj)
{
if (!RB_TYPE_P(obj, T_ARRAY)) return 0;
long len = RARRAY_LEN(obj);
if (len == 0) return 0;
VALUE last = RARRAY_AREF(obj, len - 1);
if (!RB_TYPE_P(last, T_HASH)) return 0;
return FL_TEST_RAW(last, RHASH_PASS_AS_KEYWORDS);
}

void
rb_set_cfp_pc(struct rb_control_frame_struct *cfp, const VALUE *pc)
{
Expand Down
2 changes: 1 addition & 1 deletion misc/lldb_cruby.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ def lldb_inspect(debugger, target, result, val):
append_expression(debugger, "*(struct RMatch *) %0#x" % val.GetValueAsUnsigned(), result)
elif flType == RUBY_T_IMEMO:
# I'm not sure how to get IMEMO_MASK out of lldb. It's not in globals()
imemo_type = (flags >> RUBY_FL_USHIFT) & 0x0F # IMEMO_MASK
imemo_type = (flags >> RUBY_FL_USHIFT) & 0x1F # IMEMO_MASK

print("T_IMEMO: ", file=result)
append_expression(debugger, "(enum imemo_type) %d" % imemo_type, result)
Expand Down
2 changes: 1 addition & 1 deletion misc/lldb_rb/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
HEAP_PAGE_ALIGN = (1 << HEAP_PAGE_ALIGN_LOG)
HEAP_PAGE_SIZE = HEAP_PAGE_ALIGN

IMEMO_MASK = 0x0F
IMEMO_MASK = 0x1F
2 changes: 1 addition & 1 deletion tool/timeline/lib/converter_defs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def self.FL_USER_N(n)
})

# Keep in sync with `IMEMO_MASK` in `internal/imemo.h`.
IMEMO_MASK = 0x0f
IMEMO_MASK = 0x1f

# Keep in sync with both `internal/string.h` and `include/ruby/internal/core/rstring.h`.
StringFlags = FlagsConverter.new({
Expand Down
13 changes: 0 additions & 13 deletions yjit.c
Original file line number Diff line number Diff line change
Expand Up @@ -242,19 +242,6 @@ rb_yjit_rb_ary_subseq_length(VALUE ary, long beg)
return rb_ary_subseq(ary, beg, len);
}

// Return non-zero when `obj` is an array and its last item is a
// `ruby2_keywords` hash. We don't support this kind of splat.
size_t
rb_yjit_ruby2_keywords_splat_p(VALUE obj)
{
if (!RB_TYPE_P(obj, T_ARRAY)) return 0;
long len = RARRAY_LEN(obj);
if (len == 0) return 0;
VALUE last = RARRAY_AREF(obj, len - 1);
if (!RB_TYPE_P(last, T_HASH)) return 0;
return FL_TEST_RAW(last, RHASH_PASS_AS_KEYWORDS);
}

// Checks to establish preconditions for rb_yjit_splat_varg_cfunc()
VALUE
rb_yjit_splat_varg_checks(VALUE *sp, VALUE splat_array, rb_control_frame_t *cfp)
Expand Down
2 changes: 1 addition & 1 deletion yjit/bindgen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ fn main() {
.allowlist_function("rb_yarv_str_eql_internal")
.allowlist_function("rb_str_neq_internal")
.allowlist_function("rb_yarv_ary_entry_internal")
.allowlist_function("rb_yjit_ruby2_keywords_splat_p")
.allowlist_function("rb_jit_ruby2_keywords_splat_p")
.allowlist_function("rb_jit_fix_div_fix")
.allowlist_function("rb_jit_fix_mod_fix")
.allowlist_function("rb_FL_TEST")
Expand Down
4 changes: 2 additions & 2 deletions yjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7031,7 +7031,7 @@ fn gen_send_cfunc(
if variable_splat {
let splat_array_idx = i32::from(kw_splat) + i32::from(block_arg);
let comptime_splat_array = jit.peek_at_stack(&asm.ctx, splat_array_idx as isize);
if unsafe { rb_yjit_ruby2_keywords_splat_p(comptime_splat_array) } != 0 {
if unsafe { rb_jit_ruby2_keywords_splat_p(comptime_splat_array) } != 0 {
gen_counter_incr(jit, asm, Counter::send_cfunc_splat_varg_ruby2_keywords);
return None;
}
Expand Down Expand Up @@ -7932,7 +7932,7 @@ fn gen_send_iseq(
// All splats need to guard for ruby2_keywords hash. Check with a function call when
// splatting into a rest param since the index for the last item in the array is dynamic.
asm_comment!(asm, "guard no ruby2_keywords hash in splat");
let bad_splat = asm.ccall(rb_yjit_ruby2_keywords_splat_p as _, vec![asm.stack_opnd(splat_pos)]);
let bad_splat = asm.ccall(rb_jit_ruby2_keywords_splat_p as _, vec![asm.stack_opnd(splat_pos)]);
asm.cmp(bad_splat, 0.into());
asm.jnz(Target::side_exit(Counter::guard_send_splatarray_last_ruby2_keywords));
}
Expand Down
2 changes: 1 addition & 1 deletion yjit/src/cruby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ mod manual_defs {
pub const RSTRUCT_EMBED_LEN_MASK: usize = (RUBY_FL_USER7 | RUBY_FL_USER6 | RUBY_FL_USER5 | RUBY_FL_USER4 | RUBY_FL_USER3 |RUBY_FL_USER2 | RUBY_FL_USER1) as usize;

// From iseq.h - via a different constant, which seems to confuse bindgen
pub const ISEQ_TRANSLATED: usize = RUBY_FL_USER7 as usize;
pub const ISEQ_TRANSLATED: usize = RUBY_FL_USER8 as usize;

// We'll need to encode a lot of Ruby struct/field offsets as constants unless we want to
// redeclare all the Ruby C structs and write our own offsetof macro. For now, we use constants.
Expand Down
2 changes: 1 addition & 1 deletion yjit/src/cruby_bindings.inc.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def stats_string
:empty_inline_frame_count,
:non_variadic_cfunc_optimized_send_count,
:variadic_cfunc_optimized_send_count,
:caller_splat_optimized,
], buf:, stats:, right_align: true, base: :send_count)
print_counters([
:dynamic_setivar_count,
Expand Down
1 change: 1 addition & 0 deletions zjit/bindgen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ fn main() {
.allowlist_function("rb_jit_mark_unused")
.allowlist_function("rb_jit_get_page_size")
.allowlist_function("rb_jit_array_len")
.allowlist_function("rb_jit_ruby2_keywords_splat_p")
.allowlist_function("rb_jit_fix_div_fix")
.allowlist_function("rb_jit_iseq_builtin_attrs")
.allowlist_function("rb_jit_str_concat_codepoint")
Expand Down
103 changes: 99 additions & 4 deletions zjit/src/codegen_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7123,13 +7123,108 @@ fn test_send_on_heap_object_in_spilled_arg() {
}

#[test]
fn test_send_splat() {
assert_snapshot!(inspect("
fn test_send_caller_splat_arguments() {
eval("
def test(a, b) = [a, b]
def entry(arr) = test(*arr)
def entry(args) = test(*args)
entry([1, 2])
");
assert_snapshot!(assert_compiles("entry([1, 2])"), @"[1, 2]");
}

#[test]
fn test_send_empty_caller_splat_arguments() {
eval("
def test(a = 1) = a
def entry(args) = test(*args)
entry([])
");
assert_snapshot!(assert_compiles("entry([])"), @"1");
}

#[test]
fn test_send_caller_splat_arguments_with_positional_prefix() {
eval("
def test(a, b, c) = [a, b, c]
def entry(args) = test(1, *args)
entry([2, 3])
");
assert_snapshot!(assert_compiles("entry([2, 3])"), @"[1, 2, 3]");
}

#[test]
fn test_send_many_caller_splat_arguments_to_rest_parameter() {
eval("
def test(*args) = args.length
def entry(args) = test(*args)
entry([1, 2, 3, 4, 5, 6, 7])
");
assert_snapshot!(assert_compiles("entry([1, 2, 3, 4, 5, 6, 7])"), @"7");
}

#[test]
fn test_send_caller_splat_arguments_to_complex_parameters() {
eval("
def test(a, b = 2, *rest, z, k: 40) = [a, b, rest, z, k]
def entry(args) = test(1, *args)
entry([3, 4, 5])
");
assert_snapshot!(assert_compiles("entry([3, 4, 5])"), @"[1, 3, [4], 5, 40]");
}

#[test]
fn test_send_caller_splat_arguments_with_required_keyword() {
eval("
def test(*args, k:) = [args, k]
def entry(args) = test(*args, k: 40)
entry([1, 2])
"), @"[1, 2]");
");
assert_snapshot!(assert_compiles("entry([1, 2])"), @"[[1, 2], 40]");
}

#[test]
fn test_send_caller_splat_arguments_with_block_literal() {
eval("
def test(*args) = yield args.length
def entry(args) = test(*args) { |n| n + 4 }
entry([1, 2, 3])
");
assert_snapshot!(assert_compiles("entry([1, 2, 3])"), @"7");
}

#[test]
fn test_send_caller_splat_length_mismatch_side_exits() {
eval("
def test(*args) = args
def entry(args) = test(*args)
entry([1, 2])
");
assert_snapshot!(assert_compiles_allowing_exits("entry([1, 2, 3])"), @"[1, 2, 3]");
}

#[test]
fn test_send_caller_splat_with_ruby2_keywords_hash_side_exits() {
eval("
def capture(*args) = args
ruby2_keywords(:capture)
def test(arg = :default, k: nil) = [arg, k]
def entry(args) = test(*args)
entry(capture(k: 1))
");
assert_snapshot!(assert_compiles_allowing_exits("entry(capture(k: 1))"), @"[:default, 1]");
}

#[test]
fn test_send_caller_splat_result_used_by_hash_aset() {
eval("
def test(value) = value
def entry(args)
hash = {}
hash[:value] = test(*args)
end
entry([1])
");
assert_snapshot!(assert_compiles("entry([2])"), @"2");
}

#[test]
Expand Down
3 changes: 2 additions & 1 deletion zjit/src/cruby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1224,7 +1224,7 @@ mod manual_defs {
pub const RSTRUCT_EMBED_LEN_MASK: usize = (RUBY_FL_USER7 | RUBY_FL_USER6 | RUBY_FL_USER5 | RUBY_FL_USER4 | RUBY_FL_USER3 |RUBY_FL_USER2 | RUBY_FL_USER1) as usize;

// From iseq.h - via a different constant, which seems to confuse bindgen
pub const ISEQ_TRANSLATED: usize = RUBY_FL_USER7 as usize;
pub const ISEQ_TRANSLATED: usize = RUBY_FL_USER8 as usize;

// We'll need to encode a lot of Ruby struct/field offsets as constants unless we want to
// redeclare all the Ruby C structs and write our own offsetof macro. For now, we use constants.
Expand Down Expand Up @@ -1736,6 +1736,7 @@ pub(crate) mod ids {
name: aref content: b"[]"
name: rb_obj_is_proc
name: rb_ivar_get_at_no_ractor_check
name: rb_jit_ruby2_keywords_splat_p
name: RUBY_FL_FREEZE
name: RUBY_ELTS_SHARED
name: RubyVM
Expand Down
1 change: 1 addition & 0 deletions zjit/src/cruby_bindings.inc.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading