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: 10 additions & 0 deletions .github/workflows/check_misc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ jobs:
# Skip 'push' events because post_push.yml fixes them on push
if: ${{ github.repository == 'ruby/ruby' && startsWith(github.event_name, 'pull') }}

- name: Check if date in man pages is up-to-date
run: |
git fetch origin --depth=1 "${GITHUB_OLD_SHA}"
git diff --exit-code --name-only "${GITHUB_OLD_SHA}" HEAD -- man ||
make V=1 GIT=git BASERUBY=ruby update-man-date
git diff --color --no-ext-diff --ignore-submodules --exit-code -- man
env:
GITHUB_OLD_SHA: ${{ github.event.pull_request.base.sha }}
if: ${{ startsWith(github.event_name, 'pull') }}

- name: Check for bash specific substitution in configure.ac
run: |
git grep -n '\${[A-Za-z_0-9]*/' -- configure.ac && exit 1 || :
Expand Down
3 changes: 2 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ releases.
* RubyGems 4.1.0.dev
* bundler 4.1.0.dev
* json 2.19.0
* 2.18.0 to [v2.18.1][json-v2.18.1]
* 2.18.0 to [v2.18.1][json-v2.18.1], [v2.19.0][json-v2.19.0]
* openssl 4.0.1
* 4.0.0 to [v4.0.1][openssl-v4.0.1]
* prism 1.9.0
Expand Down Expand Up @@ -134,6 +134,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable.
[Feature #21390]: https://bugs.ruby-lang.org/issues/21390
[Feature #21785]: https://bugs.ruby-lang.org/issues/21785
[json-v2.18.1]: https://github.com/ruby/json/releases/tag/v2.18.1
[json-v2.19.0]: https://github.com/ruby/json/releases/tag/v2.19.0
[openssl-v4.0.1]: https://github.com/ruby/openssl/releases/tag/v4.0.1
[prism-v1.9.0]: https://github.com/ruby/prism/releases/tag/v1.9.0
[resolv-v0.7.1]: https://github.com/ruby/resolv/releases/tag/v0.7.1
Expand Down
2 changes: 1 addition & 1 deletion common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -1900,7 +1900,7 @@ sudo-precheck: PHONY
update-man-date: PHONY
$(Q) $(BASERUBY) -I"$(tooldir)/lib" -rvcs -i -p \
-e 'BEGIN{@vcs=VCS.detect(ARGV.shift)}' \
-e '$$_.sub!(/^(\.Dd ).*/){$$1+@vcs.modified(ARGF.path).strftime("%B %d, %Y")}' \
-e '$$_.sub!(/^(\.Dd ).*/){$$1+@vcs.author_date(@vcs.relative_to(ARGF.path)).strftime("%B %d, %Y")}' \
"$(srcdir)" "$(srcdir)"/man/*.1

.PHONY: ChangeLog
Expand Down
3 changes: 3 additions & 0 deletions parse.y
Original file line number Diff line number Diff line change
Expand Up @@ -6978,6 +6978,9 @@ peek_word_at(struct parser_params *p, const char *str, size_t len, int at)
if (lex_eol_ptr_n_p(p, ptr, len-1)) return false;
if (memcmp(ptr, str, len)) return false;
if (lex_eol_ptr_n_p(p, ptr, len)) return true;
switch (ptr[len]) {
case '!': case '?': return false;
}
return !is_identchar(p, ptr+len, p->lex.pend, p->enc);
}

Expand Down
27 changes: 22 additions & 5 deletions prism/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -19195,10 +19195,12 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
pm_arguments_t arguments = { 0 };
pm_node_t *receiver = NULL;

// If we do not accept a command call, then we also do not accept a
// not without parentheses. In this case we need to reject this
// syntax.
if (!accepts_command_call && !match1(parser, PM_TOKEN_PARENTHESIS_LEFT)) {
// The `not` keyword without parentheses is only valid in contexts
// where it would be parsed as an expression (i.e., at or below
// the `not` binding power level). In other contexts (e.g., method
// arguments, array elements, assignment right-hand sides),
// parentheses are required: `not(x)`.
if (binding_power > PM_BINDING_POWER_NOT && !match1(parser, PM_TOKEN_PARENTHESIS_LEFT)) {
if (match1(parser, PM_TOKEN_PARENTHESIS_LEFT_PARENTHESES)) {
pm_parser_err(parser, PM_TOKEN_END(parser, &parser->previous), 1, PM_ERR_EXPECT_LPAREN_AFTER_NOT_LPAREN);
} else {
Expand Down Expand Up @@ -21621,7 +21623,7 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc
return node;
}
break;
case PM_CALL_NODE:
case PM_CALL_NODE: {
// A do-block can attach to a command-style call
// produced by infix operators (e.g., dot-calls like
// `obj.method args do end`).
Expand All @@ -21635,7 +21637,22 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc
if (PM_NODE_FLAG_P(node, PM_CALL_NODE_FLAGS_IMPLICIT_ARRAY) && pm_binding_powers[parser->current.type].left > PM_BINDING_POWER_MODIFIER) {
return node;
}

// Command-style calls (calls with arguments but without
// parentheses) only accept composition (and/or) and modifier
// (if/unless/etc.) operators. We need to exclude operator calls
// (e.g., a + b) which also satisfy pm_call_node_command_p but
// are not commands.
const pm_call_node_t *cast = (const pm_call_node_t *) node;
if (
(pm_binding_powers[parser->current.type].left > PM_BINDING_POWER_COMPOSITION) &&
(cast->receiver == NULL || cast->call_operator_loc.length > 0) &&
pm_call_node_command_p(cast)
) {
return node;
}
break;
}
case PM_RESCUE_MODIFIER_NODE:
// A rescue modifier whose handler is a one-liner pattern match
// (=> or in) produces a statement. That means it cannot be
Expand Down
4 changes: 4 additions & 0 deletions test/prism/errors/command_call_in_2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
a.b x in pattern
^~ unexpected 'in', expecting end-of-input
^~ unexpected 'in', ignoring it

4 changes: 4 additions & 0 deletions test/prism/errors/command_call_in_3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
a.b x: in pattern
^~ unexpected 'in', expecting end-of-input
^~ unexpected 'in', ignoring it

4 changes: 4 additions & 0 deletions test/prism/errors/command_call_in_4.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
a.b &x in pattern
^~ unexpected 'in', expecting end-of-input
^~ unexpected 'in', ignoring it

4 changes: 4 additions & 0 deletions test/prism/errors/command_call_in_5.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
a.b *x => pattern
^~ unexpected '=>', expecting end-of-input
^~ unexpected '=>', ignoring it

4 changes: 4 additions & 0 deletions test/prism/errors/command_call_in_6.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
a.b x: => pattern
^~ unexpected '=>', expecting end-of-input
^~ unexpected '=>', ignoring it

4 changes: 4 additions & 0 deletions test/prism/errors/command_call_in_7.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
a.b &x => pattern
^~ unexpected '=>', expecting end-of-input
^~ unexpected '=>', ignoring it

4 changes: 4 additions & 0 deletions test/prism/errors/not_without_parens_assignment.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
x = not y
^ expected a `(` after `not`
^ unexpected local variable or method, expecting end-of-input

7 changes: 7 additions & 0 deletions test/prism/errors/not_without_parens_call.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
foo(not y)
^ expected a `(` after `not`
^ unexpected local variable or method; expected a `)` to close the arguments
^ unexpected local variable or method, expecting end-of-input
^ unexpected ')', expecting end-of-input
^ unexpected ')', ignoring it

4 changes: 4 additions & 0 deletions test/prism/errors/not_without_parens_command.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
foo not y
^ expected a `(` after `not`
^ unexpected local variable or method, expecting end-of-input

4 changes: 4 additions & 0 deletions test/prism/errors/not_without_parens_command_call.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
a.b not y
^ expected a `(` after `not`
^ unexpected local variable or method, expecting end-of-input

4 changes: 4 additions & 0 deletions test/prism/errors/not_without_parens_return.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
return not y
^ expected a `(` after `not`
^ unexpected local variable or method, expecting end-of-input

4 changes: 0 additions & 4 deletions test/prism/errors_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@ def test_unterminated_empty_string_closing
assert_nil statement.closing
end

def test_invalid_message_name
assert_equal :"", Prism.parse_statement("+.@foo,+=foo").write_name
end

def test_regexp_encoding_option_mismatch_error
# UTF-8 char with ASCII-8BIT modifier
result = Prism.parse('/Ȃ/n')
Expand Down
52 changes: 52 additions & 0 deletions test/ripper/test_lexer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,58 @@ def test_spaces_at_eof
assert_lexer(expected, code)
end

def test_fluent_and
code = "foo\n" "and"
expected = [
[[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)],
[[1, 3], :on_ignored_nl, "\n", state(:EXPR_CMDARG)],
[[2, 0], :on_kw, "and", state(:EXPR_BEG)],
]
assert_lexer(expected, code)

code = "foo\n" "and?"
expected = [
[[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)],
[[1, 3], :on_nl, "\n", state(:EXPR_BEG)],
[[2, 0], :on_ident, "and?", state(:EXPR_CMDARG)],
]
assert_lexer(expected, code)

code = "foo\n" "and!"
expected = [
[[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)],
[[1, 3], :on_nl, "\n", state(:EXPR_BEG)],
[[2, 0], :on_ident, "and!", state(:EXPR_CMDARG)],
]
assert_lexer(expected, code)
end

def test_fluent_or
code = "foo\n" "or"
expected = [
[[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)],
[[1, 3], :on_ignored_nl, "\n", state(:EXPR_CMDARG)],
[[2, 0], :on_kw, "or", state(:EXPR_BEG)],
]
assert_lexer(expected, code)

code = "foo\n" "or?"
expected = [
[[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)],
[[1, 3], :on_nl, "\n", state(:EXPR_BEG)],
[[2, 0], :on_ident, "or?", state(:EXPR_CMDARG)],
]
assert_lexer(expected, code)

code = "foo\n" "or!"
expected = [
[[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)],
[[1, 3], :on_nl, "\n", state(:EXPR_BEG)],
[[2, 0], :on_ident, "or!", state(:EXPR_CMDARG)],
]
assert_lexer(expected, code)
end

def assert_lexer(expected, code)
assert_equal(code, Ripper.tokenize(code).join(""))
assert_equal(expected, result = Ripper.lex(code),
Expand Down
37 changes: 22 additions & 15 deletions tool/lib/vcs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,19 +169,7 @@ def get_revisions(path)
)
last or raise VCS::NotFoundError, "last revision not found"
changed or raise VCS::NotFoundError, "changed revision not found"
if modified
/\A(\d+)-(\d+)-(\d+)\D(\d+):(\d+):(\d+(?:\.\d+)?)\s*(?:Z|([-+]\d\d)(\d\d))\z/ =~ modified or
raise "unknown time format - #{modified}"
match = $~[1..6].map { |x| x.to_i }
off = $7 ? "#{$7}:#{$8}" : "+00:00"
match << off
begin
modified = Time.new(*match)
rescue ArgumentError
modified = Time.utc(*$~[1..6]) + $7.to_i * 3600 + $8.to_i * 60
end
modified = modified.getlocal(@zone)
end
modified &&= parse_iso_date(modified)
return last, changed, modified, *rest
end

Expand All @@ -190,9 +178,9 @@ def modified(path)
modified
end

def relative_to(path)
def relative_to(path, srcdir = @srcdir)
if path
srcdir = File.realpath(@srcdir)
srcdir = File.realpath(srcdir || @srcdir)
path = File.realdirpath(path)
list1 = srcdir.split(%r{/})
list2 = path.split(%r{/})
Expand All @@ -210,6 +198,20 @@ def relative_to(path)
end
end

def parse_iso_date(date)
/\A(\d+)-(\d+)-(\d+)\D(\d+):(\d+):(\d+(?:\.\d+)?)\s*(?:Z|([-+]\d\d)(\d\d))\z/ =~ date or
raise "unknown time format - #{date}"
match = $~[1..6].map { |x| x.to_i }
off = $7 ? "#{$7}:#{$8}" : "+00:00"
match << off
begin
date = Time.new(*match)
rescue ArgumentError
date = Time.utc(*$~[1..6]) + $7.to_i * 3600 + $8.to_i * 60
end
date.getlocal(@zone)
end

def after_export(dir)
FileUtils.rm_rf(Dir.glob("#{dir}/.git*"))
FileUtils.rm_rf(Dir.glob("#{dir}/.mailmap"))
Expand Down Expand Up @@ -362,6 +364,11 @@ def _get_revisions(path, srcdir = nil)
[last, changed, modified, branch, title]
end

def author_date(path, srcdir = @srcdir)
log = cmd_read_at(srcdir, [[COMMAND, 'log', '-n1', '--pretty=%at', path]])
Time.at(log.to_i, in: @zone)
end

def self.revision_name(rev)
short_revision(rev)
end
Expand Down
1 change: 1 addition & 0 deletions zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ def stats_string
# Show counters independent from exit_* or dynamic_send_*
print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20)
print_counters_with_prefix(prefix: 'ccall_', prompt: 'calls to C functions from JIT code', buf:, stats:, limit: 20)
print_counters_with_prefix(prefix: 'iseq_calls_count_', prompt: 'most called JIT functions', buf:, stats:, limit: 20)
# Don't show not_annotated_cfuncs right now because it mostly duplicates not_inlined_cfuncs
# print_counters_with_prefix(prefix: 'not_annotated_cfuncs_', prompt: 'not annotated C methods', buf:, stats:, limit: 20)

Expand Down
12 changes: 12 additions & 0 deletions zjit/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4446,6 +4446,15 @@ impl Function {
self.push_insn(block, Insn::IncrCounterPtr { counter_ptr });
}

fn count_iseq_calls(&mut self, block: BlockId) {
let iseq_name = iseq_get_location(self.iseq, 0);
let access_counter_ptrs = crate::state::ZJITState::get_iseq_calls_count_pointers();
let counter_ptr = access_counter_ptrs.entry(iseq_name.to_string()).or_insert_with(|| Box::new(0));
let counter_ptr: &mut u64 = counter_ptr.as_mut();

self.push_insn(block, Insn::IncrCounterPtr { counter_ptr });
}

fn count_not_annotated_cfunc(&mut self, block: BlockId, cme: *const rb_callable_method_entry_t) {
let owner = unsafe { (*cme).owner };
let called_id = unsafe { (*cme).called_id };
Expand Down Expand Up @@ -8050,6 +8059,9 @@ fn compile_jit_entry_block(fun: &mut Function, jit_entry_idx: usize, target_bloc
// Prepare entry_state with basic block params
let (self_param, entry_state) = compile_jit_entry_state(fun, jit_entry_block, jit_entry_idx);

if get_option!(stats) {
fun.count_iseq_calls(jit_entry_block);
}
// Jump to target_block
fun.push_insn(jit_entry_block, Insn::Jump(BranchEdge { target: target_block, args: entry_state.as_args(self_param) }));
}
Expand Down
9 changes: 9 additions & 0 deletions zjit/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ pub struct ZJITState {
/// Counter pointers for all calls to any kind of C function from JIT code
ccall_counter_pointers: HashMap<String, Box<u64>>,

/// Counter pointers for access counts of ISEQs accessed by JIT code
iseq_calls_count_pointers: HashMap<String, Box<u64>>,

/// Locations of side exists within generated code
exit_locations: Option<SideExitLocations>,
}
Expand Down Expand Up @@ -139,6 +142,7 @@ impl ZJITState {
full_frame_cfunc_counter_pointers: HashMap::new(),
not_annotated_frame_cfunc_counter_pointers: HashMap::new(),
ccall_counter_pointers: HashMap::new(),
iseq_calls_count_pointers: HashMap::new(),
exit_locations,
};
unsafe { ZJIT_STATE = Enabled(zjit_state); }
Expand Down Expand Up @@ -224,6 +228,11 @@ impl ZJITState {
&mut ZJITState::get_instance().ccall_counter_pointers
}

/// Get a mutable reference to iseq access count pointers
pub fn get_iseq_calls_count_pointers() -> &'static mut HashMap<String, Box<u64>> {
&mut ZJITState::get_instance().iseq_calls_count_pointers
}

/// Was --zjit-save-compiled-iseqs specified?
pub fn should_log_compiled_iseqs() -> bool {
get_option!(log_compiled_iseqs).is_some()
Expand Down
11 changes: 11 additions & 0 deletions zjit/src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,10 @@ pub extern "C" fn rb_zjit_reset_stats_bang(_ec: EcPtr, _self: VALUE) -> VALUE {
ZJITState::get_ccall_counter_pointers().iter_mut()
.for_each(|b| { **(b.1) = 0; });

// Reset iseq call counters
ZJITState::get_iseq_calls_count_pointers().iter_mut()
.for_each(|b| { **(b.1) = 0; });

Qnil
}

Expand Down Expand Up @@ -937,6 +941,13 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) ->
set_stat_usize!(hash, &key_string, **counter);
}

// Set iseq access counters
let iseq_access_counts = ZJITState::get_iseq_calls_count_pointers();
for (iseq_name, counter) in iseq_access_counts.iter() {
let key_string = format!("iseq_calls_count_{iseq_name}");
set_stat_usize!(hash, &key_string, **counter);
}

hash
}

Expand Down