From 4cf85fe2140d0522f924ab57c850b2f03b967390 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Fri, 13 Jun 2025 12:44:08 +0900 Subject: [PATCH 01/16] [ruby/prism] Reject `true && not true` A command-call-like `not true` must be rejected after `&&` and `||`. https://bugs.ruby-lang.org/issues/21337 https://github.com/ruby/prism/commit/0513cf22ad --- prism/config.yml | 1 + prism/prism.c | 5 +++++ prism/templates/src/diagnostic.c.erb | 1 + test/prism/errors/command_calls_31.txt | 12 ++++++++++++ 4 files changed, 19 insertions(+) create mode 100644 test/prism/errors/command_calls_31.txt diff --git a/prism/config.yml b/prism/config.yml index cb154500b3fa09..8002fff7061240 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -101,6 +101,7 @@ errors: - EXPECT_FOR_DELIMITER - EXPECT_IDENT_REQ_PARAMETER - EXPECT_IN_DELIMITER + - EXPECT_LPAREN_AFTER_NOT - EXPECT_LPAREN_REQ_PARAMETER - EXPECT_MESSAGE - EXPECT_RBRACKET diff --git a/prism/prism.c b/prism/prism.c index ffe874fe08b475..02fbc658257c44 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -19775,6 +19775,11 @@ 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 (!accepts_command_call && !match1(parser, PM_TOKEN_PARENTHESIS_LEFT)) { + pm_parser_err_current(parser, PM_ERR_EXPECT_LPAREN_AFTER_NOT); + return (pm_node_t *) pm_missing_node_create(parser, parser->current.start, parser->current.end); + } + accept1(parser, PM_TOKEN_NEWLINE); if (accept1(parser, PM_TOKEN_PARENTHESIS_LEFT)) { diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index ce98dc5acd5163..389b1dc484e1e9 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -184,6 +184,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_EXPECT_FOR_DELIMITER] = { "unexpected %s; expected a 'do', newline, or ';' after the 'for' loop collection", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_IDENT_REQ_PARAMETER] = { "expected an identifier for the required parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_IN_DELIMITER] = { "expected a delimiter after the patterns of an `in` clause", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_LPAREN_AFTER_NOT] = { "expected a `(` after `not`", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_LPAREN_REQ_PARAMETER] = { "expected a `(` to start a required parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_MESSAGE] = { "unexpected %s; expecting a message to send to the receiver", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_RBRACKET] = { "expected a matching `]`", PM_ERROR_LEVEL_SYNTAX }, diff --git a/test/prism/errors/command_calls_31.txt b/test/prism/errors/command_calls_31.txt new file mode 100644 index 00000000000000..72d5fc588f8a5c --- /dev/null +++ b/test/prism/errors/command_calls_31.txt @@ -0,0 +1,12 @@ +true && not true + ^~~~ expected a `(` after `not` + ^~~~ unexpected 'true', expecting end-of-input + +true || not true + ^~~~ expected a `(` after `not` + ^~~~ unexpected 'true', expecting end-of-input + +true && not (true) + ^ expected a `(` after `not` + ^ unexpected '(', expecting end-of-input + From 4eb0a6cd4daa2ea53b53aa14d463040912c0838c Mon Sep 17 00:00:00 2001 From: ydah Date: Mon, 7 Jul 2025 19:06:26 +0900 Subject: [PATCH 02/16] [ruby/prism] Improve error handling for missing parentheses after 'not' in command calls https://github.com/ruby/prism/commit/d9151b8a82 --- prism/config.yml | 3 ++- prism/prism.c | 11 ++++++++++- prism/templates/src/diagnostic.c.erb | 3 ++- test/prism/errors/command_calls_31.txt | 7 ++++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/prism/config.yml b/prism/config.yml index 8002fff7061240..257bd389ed02ab 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -101,7 +101,8 @@ errors: - EXPECT_FOR_DELIMITER - EXPECT_IDENT_REQ_PARAMETER - EXPECT_IN_DELIMITER - - EXPECT_LPAREN_AFTER_NOT + - EXPECT_LPAREN_AFTER_NOT_LPAREN + - EXPECT_LPAREN_AFTER_NOT_OTHER - EXPECT_LPAREN_REQ_PARAMETER - EXPECT_MESSAGE - EXPECT_RBRACKET diff --git a/prism/prism.c b/prism/prism.c index 02fbc658257c44..3d026f3009bc36 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -19775,8 +19775,17 @@ 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)) { - pm_parser_err_current(parser, PM_ERR_EXPECT_LPAREN_AFTER_NOT); + if (match1(parser, PM_TOKEN_PARENTHESIS_LEFT_PARENTHESES)) { + pm_parser_err(parser, parser->previous.end, parser->previous.end + 1, PM_ERR_EXPECT_LPAREN_AFTER_NOT_LPAREN); + } else { + accept1(parser, PM_TOKEN_NEWLINE); + pm_parser_err_current(parser, PM_ERR_EXPECT_LPAREN_AFTER_NOT_OTHER); + } + return (pm_node_t *) pm_missing_node_create(parser, parser->current.start, parser->current.end); } diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index 389b1dc484e1e9..9a30a57e3b0eca 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -184,7 +184,8 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_EXPECT_FOR_DELIMITER] = { "unexpected %s; expected a 'do', newline, or ';' after the 'for' loop collection", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_IDENT_REQ_PARAMETER] = { "expected an identifier for the required parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_IN_DELIMITER] = { "expected a delimiter after the patterns of an `in` clause", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_EXPECT_LPAREN_AFTER_NOT] = { "expected a `(` after `not`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_LPAREN_AFTER_NOT_LPAREN] = { "expected a `(` immediately after `not`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_LPAREN_AFTER_NOT_OTHER] = { "expected a `(` after `not`", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_LPAREN_REQ_PARAMETER] = { "expected a `(` to start a required parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_MESSAGE] = { "unexpected %s; expecting a message to send to the receiver", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_RBRACKET] = { "expected a matching `]`", PM_ERROR_LEVEL_SYNTAX }, diff --git a/test/prism/errors/command_calls_31.txt b/test/prism/errors/command_calls_31.txt index 72d5fc588f8a5c..e662b254444821 100644 --- a/test/prism/errors/command_calls_31.txt +++ b/test/prism/errors/command_calls_31.txt @@ -7,6 +7,11 @@ true || not true ^~~~ unexpected 'true', expecting end-of-input true && not (true) - ^ expected a `(` after `not` + ^ expected a `(` immediately after `not` ^ unexpected '(', expecting end-of-input +true && not +true +^~~~ expected a `(` after `not` +^~~~ unexpected 'true', expecting end-of-input + From 2250a66aa85c7561d4b7e520f9a7b583eeea60d4 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 15 Jul 2025 13:24:37 -0700 Subject: [PATCH 03/16] ZJIT: Fix SP alignment on JIT entry for x86_64 --- test/ruby/test_zjit.rb | 20 ++++++++++++++++++++ zjit/src/codegen.rs | 23 +++++++++++------------ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index fdcee3cafd858e..299600eef95d53 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -999,6 +999,26 @@ def test_require_rubygems_with_auto_compact }, call_threshold: 2 end + def test_profile_under_nested_jit_call + assert_compiles '[nil, nil, 3]', %q{ + def profile + 1 + 2 + end + + def jit_call(flag) + if flag + profile + end + end + + def entry(flag) + jit_call(flag) + end + + [entry(false), entry(false), entry(true)] + }, call_threshold: 2 + end + def test_bop_redefinition assert_runs '[3, :+, 100]', %q{ def test diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index ce3decd56f319b..917528ca0a5c9f 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -156,10 +156,6 @@ fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_pt // Restore registers for CFP, EC, and SP after use asm_comment!(asm, "exit to the interpreter"); - // On x86_64, maintain 16-byte stack alignment - if cfg!(target_arch = "x86_64") { - asm.cpop_into(SP); - } asm.cpop_into(SP); asm.cpop_into(EC); asm.cpop_into(CFP); @@ -511,10 +507,6 @@ fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) { asm.cpush(CFP); asm.cpush(EC); asm.cpush(SP); - // On x86_64, maintain 16-byte stack alignment - if cfg!(target_arch = "x86_64") { - asm.cpush(SP); - } // EC and CFP are passed as arguments asm.mov(EC, C_ARG_OPNDS[0]); @@ -1157,12 +1149,19 @@ fn max_num_params(function: &Function) -> usize { /// the function needs to allocate on the stack for the stack frame. fn aligned_stack_bytes(num_slots: usize) -> usize { // Both x86_64 and arm64 require the stack to be aligned to 16 bytes. - // Since SIZEOF_VALUE is 8 bytes, we need to round up the size to the nearest even number. - let num_slots = if num_slots % 2 == 0 { - num_slots - } else { + let num_slots = if cfg!(target_arch = "x86_64") && num_slots % 2 == 0 { + // On x86_64, since the call instruction bumps the stack pointer by 8 bytes on entry, + // we need to round up `num_slots` to an odd number. num_slots + 1 + } else if cfg!(target_arch = "aarch64") && num_slots % 2 == 1 { + // On arm64, the stack pointer is always aligned to 16 bytes, so we need to round up + // `num_slots`` to an even number. + num_slots + 1 + } else { + num_slots }; + + const _: () = assert!(SIZEOF_VALUE == 8, "aligned_stack_bytes() assumes SIZEOF_VALUE == 8"); num_slots * SIZEOF_VALUE } From 8668e4dd072114c5af37481789cc28a23891d1b4 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 15 Jul 2025 13:24:43 -0700 Subject: [PATCH 04/16] ZJIT: Restore SP on side-exit chains --- zjit/src/backend/lir.rs | 68 +++++++++++++++++++++++++---------------- zjit/src/codegen.rs | 15 +++++---- zjit/src/hir.rs | 1 + 3 files changed, 51 insertions(+), 33 deletions(-) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index e5453e4d554b67..94c53569b4e95c 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -270,6 +270,14 @@ impl From for Opnd { } } +/// Set of things we need to restore for side exits. +#[derive(Clone, Debug)] +pub struct SideExitContext { + pub pc: *const VALUE, + pub stack: Vec, + pub locals: Vec, +} + /// Branch target (something that we can jump to) /// for branch instructions #[derive(Clone, Debug)] @@ -281,12 +289,14 @@ pub enum Target Label(Label), /// Side exit to the interpreter SideExit { - pc: *const VALUE, - stack: Vec, - locals: Vec, - c_stack_bytes: usize, + /// Context to restore on regular side exits. None for side exits right + /// after JIT-to-JIT calls because we restore them before the JIT call. + context: Option, + /// We use this to enrich asm comments. reason: SideExitReason, - // Some if the side exit should write this label. We use it for patch points. + /// The number of bytes we need to adjust the C stack pointer by. + c_stack_bytes: usize, + /// Some if the side exit should write this label. We use it for patch points. label: Option