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
5 changes: 2 additions & 3 deletions gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -991,8 +991,7 @@ newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, bool wb_protected, size_t s
gc_validate_pc();

if (UNLIKELY(rb_gc_event_hook_required_p(RUBY_INTERNAL_EVENT_NEWOBJ))) {
unsigned int lev;
RB_VM_LOCK_ENTER_CR_LEV(cr, &lev);
int lev = RB_GC_VM_LOCK_NO_BARRIER();
{
memset((char *)obj + RVALUE_SIZE, 0, rb_gc_obj_slot_size(obj) - RVALUE_SIZE);

Expand All @@ -1007,7 +1006,7 @@ newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, bool wb_protected, size_t s
}
if (!gc_disabled) rb_gc_enable();
}
RB_VM_LOCK_LEAVE_CR_LEV(cr, &lev);
RB_GC_VM_UNLOCK_NO_BARRIER(lev);
}

#if RGENGC_CHECK_MODE
Expand Down
109 changes: 94 additions & 15 deletions lib/erb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -777,12 +777,7 @@ class ERB
# :call-seq:
# self.version -> string
#
# Returns the string revision for \ERB:
#
# ```
# ERB.version # => "4.0.4"
# ```
#
# Returns the string \ERB version.
def self.version
VERSION
end
Expand Down Expand Up @@ -815,7 +810,9 @@ def self.version
# **Keyword Argument `eoutvar`**
#
# The string value of keyword argument `eoutvar` specifies the name of the variable
# that method #result uses to construct its result string.
# that method #result uses to construct its result string;
# see #src.
#
# This is useful when you need to run multiple \ERB templates through the same binding
# and/or when you want to control where output ends up.
#
Expand Down Expand Up @@ -868,25 +865,82 @@ def initialize(str, safe_level=NOT_GIVEN, legacy_trim_mode=NOT_GIVEN, legacy_eou
@lineno = 0
@_init = self.class.singleton_class
end

# :markup: markdown
#
# Placeholder constant; used as default value for certain method arguments.
NOT_GIVEN = defined?(Ractor) ? Ractor.make_shareable(Object.new) : Object.new
private_constant :NOT_GIVEN

##
# Creates a new compiler for ERB. See ERB::Compiler.new for details
# :markup: markdown
#
# :call-seq:
# make_compiler -> erb_compiler
#
# Returns a new ERB::Compiler with the given `trim_mode`;
# for `trim_mode` values, see ERB.new:
#
# ```
# ERB.new('').make_compiler(nil)
# # => #<ERB::Compiler:0x000001cff9467678 @insert_cmd="print", @percent=false, @post_cmd=[], @pre_cmd=[], @put_cmd="print", @trim_mode=nil>
# ```
#

def make_compiler(trim_mode)
ERB::Compiler.new(trim_mode)
end

# The Ruby code generated by ERB
# :markup: markdown
#
# Returns a string containing the Ruby code that, when executed, generates the result;
# the code is executed by method #result,
# and by its wrapper methods #result_with_hash and #run:
#
# ```
# s = 'The time is <%= Time.now %>.'
# template = ERB.new(s)
# template.src
# # => "#coding:UTF-8\n_erbout = +''; _erbout.<< \"The time is \".freeze; _erbout.<<(( Time.now ).to_s); _erbout.<< \".\".freeze; _erbout"
# template.result
# # => "The time is 2025-09-18 15:58:08 -0500."
# ```
#
# In a more readable format:
#
# ```
# # puts template.src.split('; ')
# # #coding:UTF-8
# # _erbout = +''
# # _erbout.<< "The time is ".freeze
# # _erbout.<<(( Time.now ).to_s)
# # _erbout.<< ".".freeze
# # _erbout
# ```
#
# Variable `_erbout` is used to store the intermediate results in the code;
# the name `_erbout` is the default in ERB.new,
# and can be changed via keyword argument `eoutvar`:
#
# ```
# template = ERB.new(s, eoutvar: '_foo')
# puts template.src.split('; ')
# #coding:UTF-8
# _foo = +''
# _foo.<< "The time is ".freeze
# _foo.<<(( Time.now ).to_s)
# _foo.<< ".".freeze
# _foo
# ```
#
attr_reader :src

# :markup: markdown
#
# Returns the encoding of `self`;
# see [encoding][encoding].
# see [Encodings][encodings]:
#
# [encodings]: rdoc-ref:ERB@Encodings
#
# [encoding]: https://docs.ruby-lang.org/en/master/Encoding.html
attr_reader :encoding

# :markup: markdown
Expand Down Expand Up @@ -920,10 +974,35 @@ def location=((filename, lineno))
@lineno = lineno if lineno
end

# :markup: markdown
#
# Can be used to set _eoutvar_ as described in ERB::new. It's probably
# easier to just use the constructor though, since calling this method
# requires the setup of an ERB _compiler_ object.
# :call-seq:
# set_eoutvar(compiler, eoutvar = '_erbout') -> [eoutvar]
#
# Sets the `eoutvar` value in the ERB::Compiler object `compiler`;
# returns a 1-element array containing the value of `eoutvar`:
#
# ```
# template = ERB.new('')
# compiler = template.make_compiler(nil)
# pp compiler
# #<ERB::Compiler:0x000001cff8a9aa00
# @insert_cmd="print",
# @percent=false,
# @post_cmd=[],
# @pre_cmd=[],
# @put_cmd="print",
# @trim_mode=nil>
# template.set_eoutvar(compiler, '_foo') # => ["_foo"]
# pp compiler
# #<ERB::Compiler:0x000001cff8a9aa00
# @insert_cmd="_foo.<<",
# @percent=false,
# @post_cmd=["_foo"],
# @pre_cmd=["_foo = +''"],
# @put_cmd="_foo.<<",
# @trim_mode=nil>
# ```
#
def set_eoutvar(compiler, eoutvar = '_erbout')
compiler.put_cmd = "#{eoutvar}.<<"
Expand Down
2 changes: 1 addition & 1 deletion lib/erb/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true
class ERB
# The version string
# The string \ERB version.
VERSION = '5.0.2'
end
4 changes: 0 additions & 4 deletions test/objspace/test_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

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
2 changes: 1 addition & 1 deletion test/ruby/test_zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ def test(...) = 1
end

def test_sendforward
assert_runs '[1, 2]', %q{
assert_compiles '[1, 2]', %q{
def callee(a, b) = [a, b]
def test(...) = callee(...)
test(1, 2)
Expand Down
24 changes: 24 additions & 0 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ 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::SendForward { cd, blockiseq, state, .. } => gen_send_forward(jit, asm, cd, blockiseq, &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
Expand Down Expand Up @@ -1041,6 +1042,29 @@ fn gen_send(
)
}

/// Compile a dynamic dispatch with `...`
fn gen_send_forward(
jit: &mut JITState,
asm: &mut Assembler,
cd: *const rb_call_data,
blockiseq: IseqPtr,
state: &FrameState,
) -> lir::Opnd {
gen_incr_counter(asm, Counter::dynamic_send_count);
gen_incr_counter(asm, Counter::dynamic_send_type_send_forward);

gen_prepare_non_leaf_call(jit, asm, state);

asm_comment!(asm, "call #{} with dynamic dispatch", ruby_call_method_name(cd));
unsafe extern "C" {
fn rb_vm_sendforward(ec: EcPtr, cfp: CfpPtr, cd: VALUE, blockiseq: IseqPtr) -> VALUE;
}
asm.ccall(
rb_vm_sendforward as *const u8,
vec![EC, CFP, (cd as usize).into(), VALUE(blockiseq as usize).into()],
)
}

/// Compile a dynamic dispatch without block
fn gen_send_without_block(
jit: &mut JITState,
Expand Down
87 changes: 76 additions & 11 deletions zjit/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,7 @@ pub enum Insn {
state: InsnId,
},
Send { recv: InsnId, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec<InsnId>, state: InsnId },
SendForward { recv: InsnId, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec<InsnId>, state: InsnId },
InvokeSuper { recv: InsnId, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec<InsnId>, state: InsnId },
InvokeBlock { cd: *const rb_call_data, args: Vec<InsnId>, state: InsnId },

Expand Down Expand Up @@ -902,6 +903,13 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
}
Ok(())
}
Insn::SendForward { cd, args, blockiseq, .. } => {
write!(f, "SendForward {:p}, :{}", self.ptr_map.map_ptr(blockiseq), ruby_call_method_name(*cd))?;
for arg in args {
write!(f, ", {arg}")?;
}
Ok(())
}
Insn::InvokeSuper { recv, blockiseq, args, .. } => {
write!(f, "InvokeSuper {recv}, {:p}", self.ptr_map.map_ptr(blockiseq))?;
for arg in args {
Expand Down Expand Up @@ -1422,6 +1430,13 @@ impl Function {
args: find_vec!(args),
state,
},
&SendForward { recv, cd, blockiseq, ref args, state } => SendForward {
recv: find!(recv),
cd,
blockiseq,
args: find_vec!(args),
state,
},
&InvokeSuper { recv, cd, blockiseq, ref args, state } => InvokeSuper {
recv: find!(recv),
cd,
Expand Down Expand Up @@ -1552,6 +1567,7 @@ impl Function {
Insn::SendWithoutBlock { .. } => types::BasicObject,
Insn::SendWithoutBlockDirect { .. } => types::BasicObject,
Insn::Send { .. } => types::BasicObject,
Insn::SendForward { .. } => types::BasicObject,
Insn::InvokeSuper { .. } => types::BasicObject,
Insn::InvokeBlock { .. } => types::BasicObject,
Insn::InvokeBuiltin { return_type, .. } => return_type.unwrap_or(types::BasicObject),
Expand Down Expand Up @@ -2430,6 +2446,7 @@ impl Function {
worklist.push_back(state);
}
&Insn::Send { recv, ref args, state, .. }
| &Insn::SendForward { recv, ref args, state, .. }
| &Insn::SendWithoutBlock { recv, ref args, state, .. }
| &Insn::CCallVariadic { recv, ref args, state, .. }
| &Insn::SendWithoutBlockDirect { recv, ref args, state, .. }
Expand Down Expand Up @@ -3774,13 +3791,44 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let send = fun.push_insn(block, Insn::Send { recv, cd, blockiseq, args, state: exit_id });
state.stack_push(send);

// Reload locals that may have been modified by the blockiseq.
// TODO: Avoid reloading locals that are not referenced by the blockiseq
// or not used after this. Max thinks we could eventually DCE them.
for local_idx in 0..state.locals.len() {
let ep_offset = local_idx_to_ep_offset(iseq, local_idx) as u32;
let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0 });
state.setlocal(ep_offset, val);
if !blockiseq.is_null() {
// Reload locals that may have been modified by the blockiseq.
// TODO: Avoid reloading locals that are not referenced by the blockiseq
// or not used after this. Max thinks we could eventually DCE them.
for local_idx in 0..state.locals.len() {
let ep_offset = local_idx_to_ep_offset(iseq, local_idx) as u32;
let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0 });
state.setlocal(ep_offset, val);
}
}
}
YARVINSN_sendforward => {
let cd: *const rb_call_data = get_arg(pc, 0).as_ptr();
let blockiseq: IseqPtr = get_arg(pc, 1).as_iseq();
let call_info = unsafe { rb_get_call_data_ci(cd) };
let flags = unsafe { rb_vm_ci_flag(call_info) };
let forwarding = (flags & VM_CALL_FORWARDING) != 0;
if let Err(call_type) = unhandled_call_type(flags) {
// Can't handle the call type; side-exit into the interpreter
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) });
break; // End the block
}
let argc = unsafe { vm_ci_argc((*cd).ci) };

let args = state.stack_pop_n(argc as usize + usize::from(forwarding))?;
let recv = state.stack_pop()?;
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
let send_forward = fun.push_insn(block, Insn::SendForward { recv, cd, blockiseq, args, state: exit_id });
state.stack_push(send_forward);

if !blockiseq.is_null() {
// Reload locals that may have been modified by the blockiseq.
for local_idx in 0..state.locals.len() {
let ep_offset = local_idx_to_ep_offset(iseq, local_idx) as u32;
let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0 });
state.setlocal(ep_offset, val);
}
}
}
YARVINSN_invokesuper => {
Expand Down Expand Up @@ -5315,7 +5363,6 @@ mod tests {
fn test@<compiled>:2:
bb0(v0:BasicObject, v1:BasicObject):
v6:BasicObject = Send v0, 0x1000, :foo, v1
v7:BasicObject = GetLocal l0, EP@3
CheckInterrupts
Return v6
");
Expand Down Expand Up @@ -5457,14 +5504,33 @@ mod tests {
}

#[test]
fn test_cant_compile_forwarding() {
fn test_compile_forwarding() {
eval("
def test(...) = foo(...)
");
assert_snapshot!(hir_string("test"), @r"
fn test@<compiled>:2:
bb0(v0:BasicObject, v1:BasicObject):
SideExit UnhandledYARVInsn(sendforward)
v6:BasicObject = SendForward 0x1000, :foo, v1
CheckInterrupts
Return v6
");
}

#[test]
fn test_compile_triple_dots_with_positional_args() {
eval("
def test(a, ...) = foo(a, ...)
");
assert_snapshot!(hir_string("test"), @r"
fn test@<compiled>:2:
bb0(v0:BasicObject, v1:BasicObject, v2:ArrayExact, v3:BasicObject, v4:BasicObject):
v5:NilClass = Const Value(nil)
v10:ArrayExact = ToArray v2
PatchPoint NoEPEscape(test)
GuardBlockParamProxy l0
v15:BasicObject[BlockParamProxy] = Const Value(VALUE(0x1000))
SideExit UnhandledYARVInsn(splatkw)
");
}

Expand Down Expand Up @@ -8274,7 +8340,6 @@ mod opt_tests {
GuardBlockParamProxy l0
v7:BasicObject[BlockParamProxy] = Const Value(VALUE(0x1000))
v9:BasicObject = Send v0, 0x1008, :tap, v7
v10:BasicObject = GetLocal l0, EP@3
CheckInterrupts
Return v9
");
Expand Down
1 change: 1 addition & 0 deletions zjit/src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ make_counters! {
dynamic_send_count,
dynamic_send_type_send_without_block,
dynamic_send_type_send,
dynamic_send_type_send_forward,
dynamic_send_type_invokeblock,
dynamic_send_type_invokesuper,

Expand Down