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
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -3974,7 +3974,7 @@ AS_CASE(["${ZJIT_SUPPORT}"],
[yes], [
],
[dev], [
rb_cargo_features="$rb_cargo_features,disasm"
rb_cargo_features="$rb_cargo_features,disasm,runtime_checks"
JIT_CARGO_SUPPORT=dev
AC_DEFINE(RUBY_DEBUG, 1)
],
Expand Down
157 changes: 128 additions & 29 deletions lib/erb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,111 @@
#
# ## Bindings
#
# The first example above passed no argument to method `result`;
# the second example passed argument `binding`.
# A call to method #result, which produces the formatted result string,
# requires a [Binding object][binding object] as its argument.
#
# Here's why:
# The binding object provides the bindings for expressions in [expression tags][expression tags].
#
# - The first example has tag `<%= Time.now %>`,
# which cites *globally-defined* constant `Time`;
# the default `binding` (details not needed here) includes the binding of global constant `Time` to its value.
# - The second example has tag `<%= magic_word %>`,
# which cites *locally-defined* variable `magic_word`;
# the passed argument `binding` (which is simply a call to method [Kernel#binding][kernel#binding])
# includes the binding of local variable `magic_word` to its value.
# There are three ways to provide the required binding:
#
# - [Default binding][default binding].
# - [Local binding][local binding].
# - [Augmented binding][augmented binding]
#
# ### Default Binding
#
# When you pass no `binding` argument to method #result,
# the method uses its default binding: the one returned by method #new_toplevel.
# This binding has the bindings defined by Ruby itself,
# which are those for Ruby's constants and variables.
#
# That binding is sufficient for an expression tag that refers only to Ruby's constants and variables;
# these expression tags refer only to Ruby's global constant `RUBY_COPYRIGHT` and global variable `$0`:
#
# ```
# s = <<EOT
# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>.
# The current process is <%= $0 %>.
# EOT
# puts ERB.new(s).result
# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto".
# The current process is irb.
# ```
#
# (The current process is `irb` because that's where we're doing these examples!)
#
#
# ### Local Binding
#
# The default binding is *not* sufficient for an expression
# that refers to a a constant or variable that is not defined there:
#
# ```
# Foo = 1 # Defines local constant Foo.
# foo = 2 # Defines local variable foo.
# s = <<EOT
# The current value of constant Foo is <%= Foo %>.
# The current value of variable foo is <%= foo %>.
# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>.
# The current process is <%= $0 %>.
# EOT
# ```
#
# This call raises `NameError` because although `Foo` and `foo` are defined locally,
# they are not defined in the default binding:
#
# ```
# ERB.new(s).result # Raises NameError.
# ```
#
# To make the locally-defined constants and variables available,
# you can call #result with the local binding:
#
# ```
# puts ERB.new(s).result(binding)
# The current value of constant Foo is 1.
# The current value of variable foo is 2.
# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto".
# The current process is irb.
# ```
#
# ### Augmented Binding
#
# Another way to make variable bindings (but not constant bindings) available
# is to use method #result_with_hash(hash);
# the passed hash has name/value pairs that are to be used to define and assign variables
# in a copy of the default binding:
#
# ```
# s = <<EOT
# The current value of variable bar is <%= bar %>.
# The current value of variable baz is <%= baz %>.
# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>.
# The current process is <%= $0 %>.
# ```
#
# Both of these calls raise `NameError`, because `bar` and `baz`
# are not defined in either the default binding or the local binding.
#
# ```
# puts ERB.new(s).result # Raises NameError.
# puts ERB.new(s).result(binding) # Raises NameError.
# ```
#
# This call passes a hash that causes `bar` and `baz` to be defined
# in a new binding (derived from #new_toplevel):
#
# ```
# hash = {bar: 3, baz: 4}
# # => {bar: 3, baz: 4}
# ERB.new(s).result_with_hash(hash)
# puts ERB.new(s).result_with_hash(variables)
# The current value of variable bar is 3.
# The current value of variable baz is 4.
# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto".
# The current process is irb.
# EOT
# ```
#
# ## Tags
#
Expand Down Expand Up @@ -608,13 +701,16 @@
# Other popular template processors may found in the [Template Engines][template engines] page
# of the Ruby Toolbox.
#
# [%q literals]: https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-25q-3A+Non-Interpolable+String+Literals
# [augmented binding]: rdoc-ref:ERB@Augmented+Binding
# [binding object]: https://docs.ruby-lang.org/en/master/Binding.html
# [comment tags]: rdoc-ref:ERB@Comment+Tags
# [default binding]: rdoc-ref:ERB@Default+Binding
# [encoding]: https://docs.ruby-lang.org/en/master/Encoding.html
# [execution tags]: rdoc-ref:ERB@Execution+Tags
# [expression tags]: rdoc-ref:ERB@Expression+Tags
# [kernel#binding]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-binding
# [%q literals]: https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-25q-3A+Non-Interpolable+String+Literals
# [local binding]: rdoc-ref:ERB@Local+Binding
# [magic comments]: https://docs.ruby-lang.org/en/master/syntax/comments_rdoc.html#label-Magic+Comments
# [rdoc]: https://ruby.github.io/rdoc
# [sprintf]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-sprintf
Expand Down Expand Up @@ -785,27 +881,20 @@ def run(b=new_toplevel)
# :markup: markdown
#
# :call-seq:
# result(binding = top_level) -> string
# result(binding = new_toplevel) -> new_string
#
# Formats the string stored in template `self`;
# returns the string result:
# Returns the new string formed by processing \ERB tags found in the stored string in `self`.
#
# - Each [expression tag][expression tags] is replaced by the value of the embedded expression.
# - Each [execution tag][execution tags] is removed, and its embedded code is executed.
# - Each [comment tag][comment tags] is removed.
# With no argument given, uses the default binding;
# see [Default Binding][default binding].
#
# See examples at the links.
# With argument `binding` given, uses the local binding;
# see [Local Binding][local binding].
#
# Argument `binding` is a [binding object],
# which should contain the bindings needed for all expression tags;
# the default is #top_level.
# See [Bindings][bindings].
# See also #result_with_hash.
#
# [binding object]: https://docs.ruby-lang.org/en/master/Binding.html
# [bindings]: rdoc-ref:ERB@Bindings
# [comment tags]: rdoc-ref:ERB@Comment+Tags
# [execution tags]: rdoc-ref:ERB@Execution+Tags
# [expression tags]: rdoc-ref:ERB@Expression+Tags
# [default binding]: rdoc-ref:ERB@Default+Binding
# [local binding]: rdoc-ref:ERB@Local+Binding
#
def result(b=new_toplevel)
unless @_init.equal?(self.class.singleton_class)
Expand All @@ -814,8 +903,18 @@ def result(b=new_toplevel)
eval(@src, b, (@filename || '(erb)'), @lineno)
end

# Render a template on a new toplevel binding with local variables specified
# by a Hash object.
# :markup: markdown
#
# :call-seq:
# result_with_hash(hash) -> string
#
# Returns the new string formed by processing \ERB tags found in the stored string in `self`;
# see [Augmented Binding][augmented binding].
#
# See also #result.
#
# [augmented binding]: rdoc-ref:ERB@Augmented+Binding
#
def result_with_hash(hash)
b = new_toplevel(hash.keys)
hash.each_pair do |key, value|
Expand Down
6 changes: 6 additions & 0 deletions zjit.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ rb_zjit_insn_leaf(int insn, const VALUE *opes)
return insn_leaf(insn, opes);
}

ID
rb_zjit_local_id(const rb_iseq_t *iseq, unsigned idx)
{
return ISEQ_BODY(iseq)->local_table[idx];
}

// Primitives used by zjit.rb. Don't put other functions below, which wouldn't use them.
VALUE rb_zjit_assert_compiles(rb_execution_context_t *ec, VALUE self);
VALUE rb_zjit_stats(rb_execution_context_t *ec, VALUE self, VALUE target_key);
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 @@ -271,6 +271,7 @@ fn main() {
.allowlist_function("rb_zjit_iseq_builtin_attrs")
.allowlist_function("rb_zjit_iseq_inspect")
.allowlist_function("rb_zjit_iseq_insn_set")
.allowlist_function("rb_zjit_local_id")
.allowlist_function("rb_set_cfp_(pc|sp)")
.allowlist_function("rb_c_method_tracing_currently_enabled")
.allowlist_function("rb_full_cfunc_return")
Expand Down
30 changes: 29 additions & 1 deletion zjit/src/backend/lir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::fmt;
use std::mem::take;
use crate::codegen::local_size_and_idx_to_ep_offset;
use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32};
use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, vm_stack_canary};
use crate::hir::SideExitReason;
use crate::options::{debug, get_option};
use crate::cruby::VALUE;
Expand Down Expand Up @@ -1171,6 +1171,9 @@ pub struct Assembler {

/// Names of labels
pub(super) label_names: Vec<String>,

/// If Some, the next ccall should verify its leafness
leaf_ccall_stack_size: Option<usize>
}

impl Assembler
Expand All @@ -1190,9 +1193,32 @@ impl Assembler
insns: Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY),
live_ranges,
label_names,
leaf_ccall_stack_size: None,
}
}

pub fn expect_leaf_ccall(&mut self, stack_size: usize) {
self.leaf_ccall_stack_size = Some(stack_size);
}

fn set_stack_canary(&mut self) -> Option<Opnd> {
if cfg!(feature = "runtime_checks") {
if let Some(stack_size) = self.leaf_ccall_stack_size.take() {
let canary_addr = self.lea(Opnd::mem(64, SP, (stack_size as i32) * SIZEOF_VALUE_I32));
let canary_opnd = Opnd::mem(64, canary_addr, 0);
self.mov(canary_opnd, vm_stack_canary().into());
return Some(canary_opnd)
}
}
None
}

fn clear_stack_canary(&mut self, canary_opnd: Option<Opnd>){
if let Some(canary_opnd) = canary_opnd {
self.store(canary_opnd, 0.into());
};
}

/// Build an Opnd::VReg and initialize its LiveRange
pub(super) fn new_vreg(&mut self, num_bits: u8) -> Opnd {
let vreg = Opnd::VReg { idx: self.live_ranges.len(), num_bits };
Expand Down Expand Up @@ -1657,8 +1683,10 @@ impl Assembler {

/// Call a C function without PosMarkers
pub fn ccall(&mut self, fptr: *const u8, opnds: Vec<Opnd>) -> Opnd {
let canary_opnd = self.set_stack_canary();
let out = self.new_vreg(Opnd::match_num_bits(&opnds));
self.push_insn(Insn::CCall { fptr, opnds, start_marker: None, end_marker: None, out });
self.clear_stack_canary(canary_opnd);
out
}

Expand Down
Loading