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
3 changes: 2 additions & 1 deletion class.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "internal/object.h"
#include "internal/string.h"
#include "internal/variable.h"
#include "internal/vm.h"
#include "ruby/st.h"
#include "vm_core.h"
#include "ruby/ractor.h"
Expand Down Expand Up @@ -1404,7 +1405,7 @@ rb_class_inherited(VALUE super, VALUE klass)
ID inherited;
if (!super) super = rb_cObject;
CONST_ID(inherited, "inherited");
return rb_funcall(super, inherited, 1, klass);
return rb_funcallv_uncached(super, inherited, 1, &klass);
}

#ifdef rb_define_class
Expand Down
9 changes: 9 additions & 0 deletions doc/jit/zjit.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,15 @@ Note that this disables profiling. To inject interpreter profiles into ZJIT, con
./miniruby --zjit --zjit-dump-hir -e "30.times { 1 + 1 }"
```

To write the dump to a file instead of stdout, pass an existing directory as the option value. Any value other than `all` or `debug` is treated as a directory name, and it composes with those format variants:

```bash
./miniruby --zjit --zjit-dump-hir=/tmp --zjit-call-threshold=1 -e "1 + 1"
./miniruby --zjit --zjit-dump-hir=all --zjit-dump-hir=/tmp --zjit-call-threshold=1 -e "1 + 1"
```

The file `/tmp/hir-$PID` is truncated at startup and appended to as each method compiles.

### Viewing HIR in Iongraph

Using `--zjit-dump-hir-iongraph` will dump all compiled functions into a directory named `/tmp/zjit-iongraph-{PROCESS_PID}`. Each file will be named `func_{ZJIT_FUNC_NAME}.json`. In order to use them in the Iongraph viewer, you'll need to use `jq` to collate them to a single file. An example invocation of `jq` is shown below for reference.
Expand Down
13 changes: 7 additions & 6 deletions eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "internal/object.h"
#include "internal/thread.h"
#include "internal/variable.h"
#include "internal/vm.h"
#include "ruby/fiber/scheduler.h"
#include "iseq.h"
#include "probes.h"
Expand Down Expand Up @@ -1328,8 +1329,8 @@ rb_mod_include(int argc, VALUE *argv, VALUE module)
}
}
while (argc--) {
rb_funcall(argv[argc], id_append_features, 1, module);
rb_funcall(argv[argc], id_included, 1, module);
rb_funcallv_uncached(argv[argc], id_append_features, 1, &module);
rb_funcallv_uncached(argv[argc], id_included, 1, &module);
}
return module;
}
Expand Down Expand Up @@ -1385,8 +1386,8 @@ rb_mod_prepend(int argc, VALUE *argv, VALUE module)
}
}
while (argc--) {
rb_funcall(argv[argc], id_prepend_features, 1, module);
rb_funcall(argv[argc], id_prepended, 1, module);
rb_funcallv_uncached(argv[argc], id_prepend_features, 1, &module);
rb_funcallv_uncached(argv[argc], id_prepended, 1, &module);
}
return module;
}
Expand Down Expand Up @@ -1981,8 +1982,8 @@ rb_obj_extend(int argc, VALUE *argv, VALUE obj)
}
}
while (argc--) {
rb_funcall(argv[argc], id_extend_object, 1, obj);
rb_funcall(argv[argc], id_extended, 1, obj);
rb_funcallv_uncached(argv[argc], id_extend_object, 1, &obj);
rb_funcallv_uncached(argv[argc], id_extended, 1, &obj);
}
return obj;
}
Expand Down
1 change: 1 addition & 0 deletions internal/vm.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ void rb_check_stack_overflow(void);
VALUE rb_block_call2(VALUE obj, ID mid, int argc, const VALUE *argv, rb_block_call_func_t bl_proc, VALUE data2, long flags);
struct vm_ifunc *rb_current_ifunc(void);
VALUE rb_gccct_clear_table(void);
VALUE rb_funcallv_uncached(VALUE recv, ID mid, int argc, const VALUE *argv);
VALUE rb_eval_cmd_call_kw(VALUE cmd, int argc, const VALUE *argv, int kw_splat);

#if USE_YJIT || USE_ZJIT
Expand Down
10 changes: 5 additions & 5 deletions prism/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -9064,7 +9064,7 @@ escape_write_escape_encoded(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_
}

if (width == 1) {
if (*parser->current.end == '\n') pm_line_offset_list_append(&parser->metadata_arena, &parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1);
if (parser->heredoc_end == NULL && *parser->current.end == '\n') pm_line_offset_list_append(&parser->metadata_arena, &parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1);
escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(*parser->current.end++, flags));
} else if (width > 1) {
// Valid multibyte character. Just ignore escape.
Expand Down Expand Up @@ -9381,7 +9381,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
return;
}

if (peeked == '\n') pm_line_offset_list_append(&parser->metadata_arena, &parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1);
if (parser->heredoc_end == NULL && peeked == '\n') pm_line_offset_list_append(&parser->metadata_arena, &parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1);
parser->current.end++;
escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_CONTROL));
return;
Expand Down Expand Up @@ -9440,7 +9440,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
return;
}

if (peeked == '\n') pm_line_offset_list_append(&parser->metadata_arena, &parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1);
if (parser->heredoc_end == NULL && peeked == '\n') pm_line_offset_list_append(&parser->metadata_arena, &parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1);
parser->current.end++;
escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_CONTROL));
return;
Expand Down Expand Up @@ -9494,15 +9494,15 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
return;
}

if (peeked == '\n') pm_line_offset_list_append(&parser->metadata_arena, &parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1);
if (parser->heredoc_end == NULL && peeked == '\n') pm_line_offset_list_append(&parser->metadata_arena, &parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1);
parser->current.end++;
escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_META));
return;
}
}
case '\r': {
if (peek_offset(parser, 1) == '\n') {
pm_line_offset_list_append(&parser->metadata_arena, &parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 2);
if (parser->heredoc_end == NULL) pm_line_offset_list_append(&parser->metadata_arena, &parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 2);
parser->current.end += 2;
escape_write_byte_encoded(parser, buffer, flags, escape_byte('\n', flags));
return;
Expand Down
4 changes: 4 additions & 0 deletions test/prism/fuzzer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,9 @@ def self.snippet(name, source)
a
/{/, ''\\
RUBY

snippet "escaped newline in char literal after heredoc opener", "<<i=?\\\n\n\n"
snippet "escaped newline in char literal after heredoc opener with operator", "<<T^?\\\n\n\n"
snippet "control escape consuming newline after heredoc in interpolation", "%\n\#{<<e}\\c\n\n"
end
end
5 changes: 3 additions & 2 deletions variable.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "internal/symbol.h"
#include "internal/thread.h"
#include "internal/variable.h"
#include "internal/vm.h"
#include "ruby/encoding.h"
#include "ruby/st.h"
#include "ruby/util.h"
Expand Down Expand Up @@ -3831,8 +3832,8 @@ static void
const_added(VALUE klass, ID const_name)
{
if (GET_VM()->running) {
VALUE name = ID2SYM(const_name);
rb_funcallv(klass, idConst_added, 1, &name);
VALUE arg = ID2SYM(const_name);
rb_funcallv_uncached(klass, idConst_added, 1, &arg);
}
}

Expand Down
32 changes: 31 additions & 1 deletion vm_method.c
Original file line number Diff line number Diff line change
Expand Up @@ -1704,6 +1704,8 @@ rb_check_overloaded_cme(const rb_callable_method_entry_t *cme, const struct rb_c
return cme;
}

static inline void stack_check(rb_execution_context_t *ec);

#define CALL_METHOD_HOOK(klass, hook, mid) do { \
const VALUE arg = ID2SYM(mid); \
VALUE recv_class = (klass); \
Expand All @@ -1712,7 +1714,7 @@ rb_check_overloaded_cme(const rb_callable_method_entry_t *cme, const struct rb_c
recv_class = RCLASS_ATTACHED_OBJECT((klass)); \
hook_id = singleton_##hook; \
} \
rb_funcallv(recv_class, hook_id, 1, &arg); \
rb_funcallv_uncached(recv_class, hook_id, 1, &arg); \
} while (0)

static void
Expand Down Expand Up @@ -1932,6 +1934,34 @@ prepare_callable_method_entry(VALUE defined_class, ID id, const rb_method_entry_
}
}

/* A hook like this fires from C with no call site to cache into except for the gccct table,
* which is often cleared anyway. It would leave a permanent CC behind (tied to the class) if it
* created one, so we try to avoid it. */
VALUE
rb_funcallv_uncached(VALUE recv, ID mid, int argc, const VALUE *argv)
{
VALUE defined_class;
const rb_method_entry_t *me = search_method(CLASS_OF(recv), mid, &defined_class);

if (UNLIKELY(UNDEFINED_METHOD_ENTRY_P(me))) {
return rb_funcallv(recv, mid, argc, argv);
}

const rb_callable_method_entry_t *cme;

if (UNLIKELY(me->defined_class == 0)) {
// produce a transient CME that will get collected
cme = rb_method_entry_complement_defined_class(me, me->called_id, defined_class);
}
else {
cme = (const rb_callable_method_entry_t *)me;
}

rb_execution_context_t *ec = GET_EC();
stack_check(ec);
return rb_vm_call_kw(ec, recv, mid, argc, argv, cme, RB_NO_KEYWORDS);
}

static const rb_callable_method_entry_t *
complemented_callable_method_entry(VALUE klass, ID id)
{
Expand Down
28 changes: 22 additions & 6 deletions zjit/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2525,6 +2525,22 @@ impl<'a> FunctionPrinter<'a> {
}
}

/// Write a HIR dump to the file given by --zjit-dump-hir=some_directory, or to stdout if no path
/// was given.
fn print_hir_dump(label: &str, body: &dyn std::fmt::Display) {
match crate::options::get_option_ref!(dump_hir_file) {
Some(path) => {
use std::io::Write;
let result = std::fs::OpenOptions::new().create(true).append(true).open(path)
.and_then(|mut file| writeln!(file, "{label}:\n{body}"));
if let Err(e) = result {
eprintln!("ZJIT: Failed to write HIR dump to '{}': {}", path.display(), e);
}
}
None => println!("{label}:\n{body}"),
}
}

/// Union-Find (Disjoint-Set) is a data structure for managing disjoint sets that has an interface
/// of two operations:
///
Expand Down Expand Up @@ -7488,9 +7504,9 @@ impl Function {
pub fn dump_hir(&self) {
// Dump HIR after optimization
match get_option!(dump_hir_opt) {
Some(DumpHIR::WithoutSnapshot) => println!("Optimized HIR:\n{}", FunctionPrinter::without_snapshot(self)),
Some(DumpHIR::All) => println!("Optimized HIR:\n{}", FunctionPrinter::with_snapshot(self)),
Some(DumpHIR::Debug) => println!("Optimized HIR:\n{:#?}", &self),
Some(DumpHIR::WithoutSnapshot) => print_hir_dump("Optimized HIR", &FunctionPrinter::without_snapshot(self)),
Some(DumpHIR::All) => print_hir_dump("Optimized HIR", &FunctionPrinter::with_snapshot(self)),
Some(DumpHIR::Debug) => print_hir_dump("Optimized HIR", &format_args!("{:#?}", self)),
None => {},
}
}
Expand Down Expand Up @@ -10744,9 +10760,9 @@ fn add_iseq_to_hir(
fun.infer_types();

match get_option!(dump_hir_init) {
Some(DumpHIR::WithoutSnapshot) => println!("Initial HIR:\n{}", FunctionPrinter::without_snapshot(fun)),
Some(DumpHIR::All) => println!("Initial HIR:\n{}", FunctionPrinter::with_snapshot(fun)),
Some(DumpHIR::Debug) => println!("Initial HIR:\n{:#?}", fun),
Some(DumpHIR::WithoutSnapshot) => print_hir_dump("Initial HIR", &FunctionPrinter::without_snapshot(fun)),
Some(DumpHIR::All) => print_hir_dump("Initial HIR", &FunctionPrinter::with_snapshot(fun)),
Some(DumpHIR::Debug) => print_hir_dump("Initial HIR", &format_args!("{:#?}", fun)),
None => {},
}
}
Expand Down
87 changes: 83 additions & 4 deletions zjit/src/hir/opt_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16535,9 +16535,56 @@ mod hir_opt_tests {
PatchPoint NoSingletonClass(String@0x1010)
PatchPoint MethodRedefined(String@0x1010, is_a?@0x1011, cme:0x1018)
v27:StringExact = GuardType v10, StringExact recompile
v28:BoolExact = IsA v27, v16
v29:TrueClass = Const Value(true)
CheckInterrupts
Return v28
Return v29
");
}

#[test]
fn test_specialize_is_a_class_polymorphic() {
set_call_threshold(4);
eval(r#"
def test(o) = o.is_a?(String)
test("asdf")
test(4)
"#);
assert_snapshot!(hir_string("test"), @"
fn test@<compiled>:2:
bb1():
EntryPoint interpreter
v1:BasicObject = LoadSelf
v2:CPtr = LoadSP
v3:BasicObject = LoadField v2, :o@0x1000
Jump bb3(v1, v3)
bb2():
EntryPoint JIT(0)
v6:BasicObject = LoadArg :self@0
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
PatchPoint StableConstantNames(0x1008, String)
v16:ClassSubclass[String@0x1010] = Const Value(VALUE(0x1010))
v19:CBool = HasType v10, Fixnum
CondBranch v19, bb5(), bb6()
bb5():
PatchPoint MethodRedefined(Integer@0x1018, is_a?@0x1020, cme:0x1028)
v45:FalseClass = Const Value(false)
Jump bb4(v45)
bb6():
v25:CBool = HasType v10, StringExact
CondBranch v25, bb7(), bb8()
bb7():
PatchPoint NoSingletonClass(String@0x1010)
PatchPoint MethodRedefined(String@0x1010, is_a?@0x1020, cme:0x1028)
v46:TrueClass = Const Value(true)
Jump bb4(v46)
bb8():
v31:BasicObject = Send v10, :is_a?, v16 # SendFallbackReason: Send: polymorphic call site
Jump bb4(v31)
bb4(v18:BasicObject):
CheckInterrupts
Return v18
");
}

Expand Down Expand Up @@ -16664,9 +16711,9 @@ mod hir_opt_tests {
PatchPoint NoSingletonClass(String@0x1010)
PatchPoint MethodRedefined(String@0x1010, kind_of?@0x1011, cme:0x1018)
v27:StringExact = GuardType v10, StringExact recompile
v28:BoolExact = IsA v27, v16
v29:TrueClass = Const Value(true)
CheckInterrupts
Return v28
Return v29
");
}

Expand Down Expand Up @@ -16789,6 +16836,38 @@ mod hir_opt_tests {
");
}

#[test]
fn test_fold_is_a_user_class_with_profiled_fixnum_to_false() {
eval(r#"
class C; end
def test(o) = o.is_a?(C)
test(5)
test(5)
"#);
assert_snapshot!(hir_string("test"), @"
fn test@<compiled>:3:
bb1():
EntryPoint interpreter
v1:BasicObject = LoadSelf
v2:CPtr = LoadSP
v3:BasicObject = LoadField v2, :o@0x1000
Jump bb3(v1, v3)
bb2():
EntryPoint JIT(0)
v6:BasicObject = LoadArg :self@0
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
PatchPoint StableConstantNames(0x1008, C)
v16:ClassSubclass[C@0x1010] = Const Value(VALUE(0x1010))
PatchPoint MethodRedefined(Integer@0x1018, is_a?@0x1020, cme:0x1028)
v26:Fixnum = GuardType v10, Fixnum recompile
v28:FalseClass = Const Value(false)
CheckInterrupts
Return v28
");
}

#[test]
fn test_is_a_array_subclass_folds_to_true() {
eval(r#"
Expand Down
Loading