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
10 changes: 8 additions & 2 deletions .github/workflows/yjit-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,10 @@ jobs:
ruby -ne 'raise "Disassembly seems broken in dev build (output has too few lines)" unless $_.to_i > 10'
if: ${{ contains(matrix.configure, 'jit=dev') }}

- name: Enable YJIT through ENV
run: echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV
- name: Set ENV for YJIT
run: |
echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV
echo "RUBY_CRASH_REPORT=$(pwd)/rb_crash_%p.txt" >> $GITHUB_ENV

- name: Set test options for skipped tests
run: |
Expand Down Expand Up @@ -166,6 +168,10 @@ jobs:
if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }}
continue-on-error: ${{ matrix.continue-on-skipped_tests || false }}

- if: ${{ failure() }}
continue-on-error: true
run: tail --verbose --lines=+1 rb_crash_*.txt

- uses: ./.github/actions/slack
with:
label: ${{ matrix.test_task }} ${{ matrix.configure }} ${{ matrix.yjit_opts }}
Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/yjit-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,10 @@ jobs:
ruby -ne 'raise "Disassembly seems broken in dev build (output has too few lines)" unless $_.to_i > 10'
if: ${{ contains(matrix.configure, 'jit=dev') }}

- name: Enable YJIT through ENV
run: echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV
- name: Set ENV for YJIT
run: |
echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV
echo "RUBY_CRASH_REPORT=$(pwd)/rb_crash_%p.txt" >> $GITHUB_ENV

# Check that the binary was built with YJIT
- name: Check YJIT enabled
Expand Down Expand Up @@ -208,6 +210,11 @@ jobs:
LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }}
continue-on-error: ${{ matrix.continue-on-test_task || false }}

- name: Dump crash logs
if: ${{ failure() }}
continue-on-error: true
run: tail --verbose --lines=+1 rb_crash_*.txt

- uses: ./.github/actions/slack
with:
label: ${{ matrix.test_task }} ${{ matrix.configure }}
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/zjit-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ jobs:
ruby -ne 'raise "Disassembly seems broken in dev build (output has too few lines)" unless $_.to_i > 10'
if: ${{ contains(matrix.configure, 'jit=dev') }}

- name: Set ENV for ZJIT
run: |
echo "RUBY_CRASH_REPORT=$(pwd)/rb_crash_%p.txt" >> $GITHUB_ENV

- name: make ${{ matrix.test_task }}
run: >-
make -s ${{ matrix.test_task }} ${TESTS:+TESTS="$TESTS"}
Expand All @@ -122,6 +126,11 @@ jobs:
TESTS: ${{ matrix.test_all_opts }}
continue-on-error: ${{ matrix.continue-on-test_task || false }}

- name: Dump crash logs
if: ${{ failure() }}
continue-on-error: true
run: tail --verbose --lines=+1 rb_crash_*.txt

- uses: ./.github/actions/slack
with:
label: ${{ matrix.test_task }} ${{ matrix.configure }}
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/zjit-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ jobs:
run: ./miniruby --zjit -v | grep "+ZJIT"
if: ${{ matrix.configure != '--disable-zjit' }}

- name: Set ENV for ZJIT
run: |
echo "RUBY_CRASH_REPORT=$(pwd)/rb_crash_%p.txt" >> $GITHUB_ENV

- name: make ${{ matrix.test_task }}
run: >-
make -s ${{ matrix.test_task }} ${TESTS:+TESTS="$TESTS"}
Expand All @@ -164,6 +168,11 @@ jobs:
TESTS: ${{ matrix.test_all_opts }}
continue-on-error: ${{ matrix.continue-on-test_task || false }}

- name: Dump crash logs
if: ${{ failure() }}
continue-on-error: true
run: tail --verbose --lines=+1 rb_crash_*.txt

- uses: ./.github/actions/slack
with:
label: ${{ matrix.test_task }} ${{ matrix.configure }}
Expand Down
78 changes: 62 additions & 16 deletions lib/erb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,49 @@
# # => #<Encoding:Big5>
# ```
#
# ## Error Reporting
#
# Consider this template (containing an error):
#
# ```
# s = '<%= nosuch %>'
# template = ERB.new(s)
# ```
#
# When \ERB reports an error,
# it includes a file name (if available) and a line number;
# the file name comes from method #filename, the line number from method #lineno.
#
# Initially, those values are `nil` and `0`, respectively;
# these initial values are reported as `'(erb)'` and `1`, respectively:
#
# ```
# template.filename # => nil
# template.lineno # => 0
# template.result
# (erb):1:in '<main>': undefined local variable or method 'nosuch' for main (NameError)
# ```
#
# You can use methods #filename= and #lineno= to assign values
# that are more meaningful in your context:
#
# ```
# template.filename = 't.txt'
# # => "t.txt"
# template.lineno = 555
# # => 555
# template.result
# t.txt:556:in '<main>': undefined local variable or method 'nosuch' for main (NameError)
# ```
#
# You can use method #location= to set both values:
#
# ```
# template.location = ['u.txt', 999]
# template.result
# u.txt:1000:in '<main>': undefined local variable or method 'nosuch' for main (NameError)
# ```
#
# ## Plain Text Example
#
# Here's a plain-text string;
Expand Down Expand Up @@ -833,29 +876,32 @@ def make_compiler(trim_mode)
# The encoding to eval
attr_reader :encoding

# The optional _filename_ argument passed to Kernel#eval when the ERB code
# is run
# :markup: markdown
#
# Sets or returns the file name to be used in reporting errors;
# see [Error Reporting][error reporting].
#
# [error reporting]: rdoc-ref:ERB@Error+Reporting
attr_accessor :filename

# The optional _lineno_ argument passed to Kernel#eval when the ERB code
# is run
# :markup: markdown
#
# Sets or returns the line number to be used in reporting errors;
# see [Error Reporting][error reporting].
#
# [error reporting]: rdoc-ref:ERB@Error+Reporting
attr_accessor :lineno

# :markup: markdown
#
# Sets optional filename and line number that will be used in ERB code
# evaluation and error reporting. See also #filename= and #lineno=
#
# erb = ERB.new('<%= some_x %>')
# erb.render
# # undefined local variable or method `some_x'
# # from (erb):1
# :call-seq:
# location = [filename, lineno] => [filename, lineno]
# location = filename -> filename
#
# erb.location = ['file.erb', 3]
# # All subsequent error reporting would use new location
# erb.render
# # undefined local variable or method `some_x'
# # from file.erb:4
# Sets the values of #filename and, if given, #lineno;
# see [Error Reporting][error reporting].
#
# [error reporting]: rdoc-ref:ERB@Error+Reporting
def location=((filename, lineno))
@filename = filename
@lineno = lineno if lineno
Expand Down
2 changes: 1 addition & 1 deletion prism/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -20909,7 +20909,7 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding
bool permitted = true;
if (previous_binding_power != PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_USTAR)) permitted = false;

pm_node_t *value = parse_starred_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MATCH, diag_id, (uint16_t) (depth + 1));
pm_node_t *value = parse_starred_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MODIFIER, diag_id, (uint16_t) (depth + 1));
if (!permitted) pm_parser_err_node(parser, value, PM_ERR_UNEXPECTED_MULTI_WRITE);

parse_assignment_value_local(parser, value);
Expand Down
2 changes: 1 addition & 1 deletion test/-ext-/bug_reporter/test_bug_reporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_bug_reporter_add
# We want the printed description to match this process's RUBY_DESCRIPTION
args.push("--yjit") if JITSupport.yjit_enabled?
args.push("--zjit") if JITSupport.zjit_enabled?
args.unshift({"RUBY_ON_BUG" => nil})
args.unshift({"RUBY_ON_BUG" => nil, "RUBY_CRASH_REPORT" => nil})
stdin = "#{no_core}register_sample_bug_reporter(12345); Process.kill :SEGV, $$"
assert_in_out_err(args, stdin, [], expected_stderr, encoding: "ASCII-8BIT")
ensure
Expand Down
4 changes: 4 additions & 0 deletions test/objspace/test_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

class TestObjSpaceRactor < Test::Unit::TestCase
def test_tracing_does_not_crash
# https://ci.rvm.jp/results/trunk-random1@ruby-sp2-noble-docker/5954509
# https://ci.rvm.jp/results/trunk-random0@ruby-sp2-noble-docker/5954501
omit "crashes frequently on CI but not able to reproduce locally"

assert_ractor(<<~RUBY, require: 'objspace')
ObjectSpace.trace_object_allocations do
r = Ractor.new do
Expand Down
6 changes: 6 additions & 0 deletions test/prism/errors/command_calls_33.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
1 if foo = bar baz
^~~ unexpected local variable or method, expecting end-of-input

1 and foo = bar baz
^~~ unexpected local variable or method, expecting end-of-input

1 change: 1 addition & 0 deletions test/ruby/test_rubyoptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,7 @@ def assert_segv(args, message=nil, list: SEGVTest::ExpectedStderrList, **opt, &b
args.unshift("--yjit") if JITSupport.yjit_enabled?
args.unshift("--zjit") if JITSupport.zjit_enabled?
env.update({'RUBY_ON_BUG' => nil})
env['RUBY_CRASH_REPORT'] ||= nil # default to not passing down parent setting
# ASAN registers a segv handler which prints out "AddressSanitizer: DEADLYSIGNAL" when
# catching sigsegv; we don't expect that output, so suppress it.
env.update({'ASAN_OPTIONS' => 'handle_segv=0'})
Expand Down
3 changes: 2 additions & 1 deletion test/ruby/test_vm_dump.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

class TestVMDump < Test::Unit::TestCase
def assert_darwin_vm_dump_works(args, timeout=nil)
args.unshift({"RUBY_ON_BUG" => nil, "RUBY_CRASH_REPORT" => nil})
assert_in_out_err(args, "", [], /^\[IMPORTANT\]/, timeout: timeout || 300)
end

Expand All @@ -13,7 +14,7 @@ def test_darwin_invalid_call
end

def test_darwin_segv_in_syscall
assert_darwin_vm_dump_works('-e1.times{Process.kill :SEGV,$$}')
assert_darwin_vm_dump_works(['-e1.times{Process.kill :SEGV,$$}'])
end

def test_darwin_invalid_access
Expand Down
6 changes: 3 additions & 3 deletions tool/test/testunit/test_parallel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ def test_quit
end

class TestParallel < Test::Unit::TestCase
def spawn_runner(*opt_args, jobs: "t1")
def spawn_runner(*opt_args, jobs: "t1", env: {})
@test_out, o = IO.pipe
@test_pid = spawn(*@__runner_options__[:ruby], TESTS+"/runner.rb",
@test_pid = spawn(env, *@__runner_options__[:ruby], TESTS+"/runner.rb",
"--ruby", @__runner_options__[:ruby].join(" "),
"-j", jobs, *opt_args, out: o, err: o)
o.close
Expand Down Expand Up @@ -214,7 +214,7 @@ def test_separate
end

def test_hungup
spawn_runner "--worker-timeout=1", "--retry", "test4test_hungup.rb"
spawn_runner("--worker-timeout=1", "--retry", "test4test_hungup.rb", env: {"RUBY_CRASH_REPORT"=>nil})
buf = ::TestParallel.timeout(TIMEOUT) {@test_out.read}
assert_match(/^Retrying hung up testcases\.+$/, buf)
assert_match(/^2 tests,.* 0 failures,/, buf)
Expand Down
1 change: 1 addition & 0 deletions zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def stats_string
print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20)
print_counters_with_prefix(prefix: 'exit_', prompt: 'side exit reasons', buf:, stats:, limit: 20)
print_counters_with_prefix(prefix: 'dynamic_send_type_', prompt: 'dynamic send types', buf:, stats:, limit: 20)
print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback def_types', buf:, stats:, limit: 20)

# Show the most important stats ratio_in_zjit at the end
print_counters([
Expand Down
21 changes: 12 additions & 9 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ use crate::invariants::{track_bop_assumption, track_cme_assumption, track_no_ep_
use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqPayload, IseqStatus};
use crate::state::ZJITState;
use crate::stats::{exit_counter_for_compile_error, incr_counter, incr_counter_by, CompileError};
use crate::stats::{counter_ptr, with_time_stat, Counter, Counter::{compile_time_ns, exit_compile_error}};
use crate::stats::{counter_ptr, with_time_stat, Counter, send_fallback_counter, Counter::{compile_time_ns, exit_compile_error}};
use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr};
use crate::backend::lir::{self, asm_comment, asm_ccall, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, NATIVE_BASE_PTR, SCRATCH_OPND, SP};
use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, Invariant, RangeType, SideExitReason, SideExitReason::*, SpecialObjectType, SpecialBackrefSymbol, SELF_PARAM_IDX};
use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, Invariant, RangeType, SideExitReason, SideExitReason::*, MethodType, SpecialObjectType, SpecialBackrefSymbol, SELF_PARAM_IDX};
use crate::hir::{Const, FrameState, Function, Insn, InsnId};
use crate::hir_type::{types, Type};
use crate::options::get_option;
Expand Down Expand Up @@ -364,10 +364,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::IfTrue { val, target } => no_output!(gen_if_true(jit, asm, opnd!(val), target)),
Insn::IfFalse { val, target } => no_output!(gen_if_false(jit, asm, opnd!(val), target)),
&Insn::Send { cd, blockiseq, state, .. } => gen_send(jit, asm, cd, blockiseq, &function.frame_state(state)),
Insn::SendWithoutBlock { cd, state, .. } => gen_send_without_block(jit, asm, *cd, &function.frame_state(*state)),
Insn::SendWithoutBlock { cd, state, def_type, .. } => gen_send_without_block(jit, asm, *cd, *def_type, &function.frame_state(*state)),
// Give up SendWithoutBlockDirect for 6+ args since asm.ccall() doesn't support it.
Insn::SendWithoutBlockDirect { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self
gen_send_without_block(jit, asm, *cd, &function.frame_state(*state)),
gen_send_without_block(jit, asm, *cd, None, &function.frame_state(*state)),
Insn::SendWithoutBlockDirect { cme, iseq, recv, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state)),
&Insn::InvokeSuper { cd, blockiseq, state, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state)),
Insn::InvokeBlock { cd, state, .. } => gen_invokeblock(jit, asm, *cd, &function.frame_state(*state)),
Expand All @@ -393,14 +393,14 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)),
Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)),
Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state)),
&Insn::GuardBlockParamProxy { level, state } => no_output!(gen_guard_block_param_proxy(jit, asm, level, &function.frame_state(state))),
Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))),
Insn::CCall { cfun, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfun, opnds!(args)),
Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id),
Insn::SetGlobal { id, val, state } => no_output!(gen_setglobal(jit, asm, *id, opnd!(val), &function.frame_state(*state))),
Insn::GetGlobal { id, state } => gen_getglobal(jit, asm, *id, &function.frame_state(*state)),
&Insn::GetLocal { ep_offset, level } => gen_getlocal_with_ep(asm, ep_offset, level),
&Insn::SetLocal { val, ep_offset, level } => no_output!(gen_setlocal_with_ep(asm, opnd!(val), function.type_of(val), ep_offset, level)),
&Insn::GetBlockParamProxy { level, state } => gen_get_block_param_proxy(jit, asm, level, &function.frame_state(state)),
Insn::GetConstantPath { ic, state } => gen_get_constant_path(jit, asm, *ic, &function.frame_state(*state)),
Insn::SetIvar { self_val, id, val, state: _ } => no_output!(gen_setivar(asm, opnd!(self_val), *id, opnd!(val))),
Insn::SideExit { state, reason } => no_output!(gen_side_exit(jit, asm, reason, &function.frame_state(*state))),
Expand Down Expand Up @@ -551,7 +551,7 @@ fn gen_setlocal_with_ep(asm: &mut Assembler, val: Opnd, val_type: Type, local_ep
}
}

fn gen_get_block_param_proxy(jit: &JITState, asm: &mut Assembler, level: u32, state: &FrameState) -> lir::Opnd {
fn gen_guard_block_param_proxy(jit: &JITState, asm: &mut Assembler, level: u32, state: &FrameState) {
// Bail out if the `&block` local variable has been modified
let ep = gen_get_ep(asm, level);
let flags = Opnd::mem(64, ep, SIZEOF_VALUE_I32 * (VM_ENV_DATA_INDEX_FLAGS as i32));
Expand All @@ -569,9 +569,6 @@ fn gen_get_block_param_proxy(jit: &JITState, asm: &mut Assembler, level: u32, st
let block_handler = asm.load(Opnd::mem(64, ep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL));
asm.test(block_handler, 0x1.into());
asm.jz(side_exit(jit, state, SideExitReason::BlockParamProxyNotIseqOrIfunc));

// Return the rb_block_param_proxy instance (GC root, so put as a number to avoid unnecessary GC tracing)
unsafe { rb_block_param_proxy }.as_u64().into()
}

fn gen_get_constant_path(jit: &JITState, asm: &mut Assembler, ic: *const iseq_inline_constant_cache, state: &FrameState) -> Opnd {
Expand Down Expand Up @@ -999,10 +996,16 @@ fn gen_send_without_block(
jit: &mut JITState,
asm: &mut Assembler,
cd: *const rb_call_data,
def_type: Option<MethodType>,
state: &FrameState,
) -> lir::Opnd {
gen_incr_counter(asm, Counter::dynamic_send_count);
gen_incr_counter(asm, Counter::dynamic_send_type_send_without_block);

if let Some(def_type) = def_type {
gen_incr_counter(asm, send_fallback_counter(def_type));
}

gen_prepare_non_leaf_call(jit, asm, state);
asm_comment!(asm, "call #{} with dynamic dispatch", ruby_call_method_name(cd));
unsafe extern "C" {
Expand Down
Loading
Loading