1057 Commits
Author SHA1 Message Date
nagachika cf53d1a728 merge revision(s) 5831948a66: [Backport #22303]
[PATCH] Fix the `intern` VM instruction to be `leaf=false` (also in YJIT, ZJIT)

	rb_str_intern can raise EncodingError and call its initializer.
	This call can push a Ruby frame.

	Fix in the VM, YJIT, and ZJIT.
2026-09-20 14:26:37 +09:00
Takashi Kokubun 599ed107f1 YJIT: Fix version_map use-after-free from mutable aliasing UB
Multiple YJIT functions created overlapping `&'static mut IseqPayload`
references by calling `get_iseq_payload()` multiple times for the same
iseq. LLVM's noalias optimizations could then cache stale version_map
Vec headers, causing the version_map to point to freed backing storage.

This manifested as crashes in various YJIT operations (block lookup,
GC marking, block removal) that dereference the stale pointer.

Fix by moving `delayed_deallocation` and `get_or_create_version_list`
from free functions (which each call `get_iseq_payload()` internally)
to methods on `IseqPayload` that operate through `&mut self`. This
lets callers obtain a single payload reference and use it for all
operations without creating overlapping mutable borrows.

The three fixed call sites:

1. `rb_yjit_tracing_invalidate_all` (invariants.rs): The loop called
   `delayed_deallocation()` which internally called `get_iseq_payload()`,
   creating a second `&mut` overlapping with the outer `payload` reference.
   Fix: call `payload.delayed_deallocation()` method instead.

2. `add_block_version` (core.rs): Called `get_or_create_version_list()`
   then later `get_iseq_payload()` for pages, creating two references.
   Fix: use a single `get_or_create_iseq_payload()` call then call the
   `get_or_create_version_list()` method on it for both version_map and
   pages access.

3. `invalidate_block_version` (core.rs): Called `remove_block_version()`
   (which calls `get_iseq_payload()`) then `delayed_deallocation()`
   (which also calls `get_iseq_payload()`), creating two references.
   Fix: get the payload once, inline the version_map removal, and call
   `payload.delayed_deallocation()` method for the rest.

Also adds regression tests exercising tracing invalidation with
on-stack methods and suspended fibers.

(cherry picked from commit a6fcad276a)
2026-07-09 22:51:30 +09:00
nagachika 2bef7f7edd merge revision(s) c34cc402a3: [Backport #22074]
[PATCH] YJIT: Fix local register mapping overflow

	Previously, local indices greater than 255 were truncated when
	converted to RegOpnd::Local. This could make a high-index local alias
	a tracked low-index local in the register mapping and produce incorrect
	values after compilation.

	Cap overflowing indices to MAX_CTX_LOCALS. RegMapping treats that index
	as untrackable because it is just outside the tracked local range.

	Fixes [Bug #22074].

	Co-authored-by: Codex <199175422+chatgpt-connector[bot]@users.noreply.github.com>
2026-06-14 12:27:22 +09:00
nagachika 8a434effca merge revision(s) 8f98abfc46: [Backport #21941]
[PATCH] YJIT: Fix not reading locals from `cfp->ep` after `YJIT.enable` and exceptional entry
	MIME-Version: 1.0
	Content-Type: text/plain; charset=UTF-8
	Content-Transfer-Encoding: 8bit

	Fix for [Bug #21941].

	In case of `--yjit-disable`, YJIT only starts to record environment
	escapes after `RubyVM::YJIT.enable`. Previously we falsely assumed that
	we always have a full history all the way back to VM boot. This had YJIT
	install and run code that assume EP=BP when EP≠BP for some exceptional
	entry into the middle of a running frame, if the environment escaped
	before `YJIT.enable`.

	The fix is to reject exceptional entry with an escaped environment.
	Rename things and explain in more detail how the predicate for deciding
	to assume EP=BP works. It's quite subtle since it reasons about all
	parties in the system that push a control frame and then run JIT code.

	Note that while can_assume_on_stack_env() checks the currently running
	environment if it so happens to be the one YJIT is compiling against, it
	can return true for any ISEQ. The check isn't necessary for fixing the
	bug, and the load bearing part of this patch is the change to
	exceptional entries.

	This fix is flat on speed and space on ruby-bench headline benchmarks.

	Many thanks for the community effort to create a small test case for
	this bug.
2026-05-09 14:57:47 +09:00
Takashi Kokubun d8f087b581 merge revision(s) 9168cad4d6: [Backport #21266]
[PATCH] YJIT: Bail out if proc would be stored above stack top

	Fixes [Bug #21266].
2025-12-16 15:53:40 -08:00
Alan Wu 70a7c551fc YJIT: Print Rc strong and weak count on assert failure
For <https://bugs.ruby-lang.org/issues/21716>, the panic is looking like
some sort of third party memory corruption, with YJIT taking the fall.
At the point of this assert, the assembler has dropped, so there's
nothing in YJIT's code other than JITState that could be holding on to
these transient `PendingBranchRef`.

The strong count being more than a handful or the weak count is non-zero
shows that someone in the process (likely some native extension)
corrupted the Rc's counts.
2025-12-16 11:55:31 -08:00
Alan Wu f19e9c6699 YJIT: Fix panic from overly loose filtering in identity method inlining
Credits to @rwstauner for noticing this issue in GH-15533.
2025-12-12 15:43:41 -08:00
Alan Wu 628a941047 YJIT: Add missing local variable type update for fallback setlocal blocks
Previously, the chain_depth>0 version of setlocal blocks did not
update the type of the local variable in the context. This can leave
the context with stale type information and trigger or lead to miscompilation.

To trigger the issue, YJIT needs to see the same ISEQ before and after
environment escape and have tracked type info before the escape. To
trigger in ISEQs that do not send with a block, it probably requires
Kernel#binding or the use of include/ruby/debug.h APIs.

[Backport #21772]
2025-12-12 15:43:41 -08:00
Randy Stauner 28b2e2ede9 YJIT: Abort expandarray optimization if method_missing is defined
Fixes: [Bug #21707]
[AW: rewrote comments]
Co-authored-by: Alan Wu <alanwu@ruby-lang.org>
2025-12-01 09:45:35 -08:00
John HawthornandLuke Gruber 0b559eab04 YJIT: Fix stack handling in rb_str_dup
Previously because we did a stack_push before ccall, in some cases we
could end up pushing an uninitialized value to the VM stack when
spilling regs as part of the ccall.

Co-authored-by: Luke Gruber <luke.gru@gmail.com>
2025-11-14 10:07:34 -08:00
Alan Wu 0b7ea9c795 YJIT: Prevent making a branch from a dead block to a live block
I'm seeing some memory corruption in the wild on blocks in
`IseqPayload::dead_blocks`. While I unfortunately can't recreate the
issue, (For all I know, it could be some external code corrupting YJIT's
memory.) establishing a link between dead blocks and live blocks seems
fishy enough that we ought to prevent it. When it did happen, it might've
had bad interacts with Code GC and the optimization to immediately
free empty blocks.
2025-10-29 16:35:14 -07:00
Alan Wu 3afa38d0ee YJIT: Fix defined?(yield) and block_given? at top level
Previously, YJIT returned truthy for the block given query at the top
level. That's incorrect because the top level script never receives a
block, and `yield` is a syntax error there.

Inside methods, the number of hops to get from `iseq` to
`iseq->body->local_iseq` is the same as the number of
`VM_ENV_PREV_EP(ep)` hops to get to an environment with
`VM_ENV_FLAG_LOCAL`. YJIT and the interpreter both rely on this as can
be seen in get_lvar_level(). However, this identity does not hold for
the top level frame because of `vm_set_eval_stack()`, which sets up
`TOPLEVEL_BINDING`.

Since only methods can take a block that `yield` goes to, have ISEQs
that are the child of a non-method ISEQ return falsy for the block given
query. This fixes the issue for the top level script and is an
optimization for non-method contexts such as inside `ISEQ_TYPE_CLASS`.
2025-10-22 13:53:20 -07:00
Alan Wu 9cd1ac404e YJIT: Print more disassembly in release builds
These `#[cfg(feature = "disasm")]` were unnecessary and we can provide
the information like ruby source location regardless of the availability
of capstone.
2025-09-10 15:35:06 -07:00
Alan Wu c866b9027b YJIT: Remove dead code: asm_comment! checks --yjit-dump-disasm 2025-09-10 15:35:06 -07:00
Takashi KokubunandAlan Wu ecebf1687d YJIT: Add more information to an assert message (#14481)
YJIT: Add more information to an assert message in jit_guard_known_class (#14480)

---------

Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
2025-09-08 13:30:57 -07:00
Alan Wu c6614d4cdc YJIT: Fix mismatched_lifetime_syntaxes, new in Rust 1.89.0 2025-08-11 12:21:19 -07:00
Alan Wu d713896f19 YJIT: Explicitly specify C ABI to fix Rust warning
Backport of 7e733ca551 to
fix [Bug #21514].
2025-07-17 09:23:16 -07:00
Takashi Kokubun d8e7ab99a3 YJIT: Split the block on optimized getlocal/setlocal (#13331) 2025-05-13 20:10:15 -07:00
Takashi Kokubun 7afa08ce8d YJIT: End the block after OPTIMIZE_METHOD_TYPE_CALL 2025-05-06 15:34:58 -07:00
Rian McGuire 50b1759be0 YJIT: Fix potential infinite loop when OOM (GH-13186)
Avoid generating an infinite loop in the case where:
1. Block `first` is adjacent to block `second`, and the branch from `first` to
   `second` is a fallthrough, and
2. Block `second` immediately exits to the interpreter, and
3. Block `second` is invalidated and YJIT is OOM

While pondering how to fix this, I think I've stumbled on another related edge case:
1. Block `incoming_one` and `incoming_two` both branch to block `second`. Block
   `incoming_one` has a fallthrough
2. Block `second` immediately exits to the interpreter (so it starts with its exit)
3. When Block `second` is invalidated, the incoming fallthrough branch from
   `incoming_one` might be rewritten first, which overwrites the start of block
   `second` with a jump to a new branch stub.
4. YJIT runs of out memory
5. The incoming branch from `incoming_two` is then rewritten, but because we're
   OOM we can't generate a new stub, so we use `second`'s exit as the branch
   target. However `second`'s exit was already overwritten with a jump to the
   branch stub for `incoming_one`, so `incoming_two` will end up jumping to
   `incoming_one`'s branch stub.

Backport [Bug #21257]
2025-04-28 09:23:33 -07:00
Alan Wu 294aef53b8 YJIT: Filter & calls from specialized C method codegen
Evident with the crash reported in [Bug #20997], the C replacement
codegen functions aren't authored to handle block arguments (nor
should they because the extra code from the complexity defeats
optimization). Filter sites with VM_CALL_ARGS_BLOCKARG.
2025-02-13 17:46:14 -08:00
Alan Wu 706f1d0573 YJIT: Initialize locals in ISeqs defined with ...
Backport of GH-12660:

    Previously, callers of forwardable ISeqs moved the stack pointer up
    without writing to the stack. If there happens to be a stale value in
    the area skipped over, it could crash due to "try to mark T_NONE". Also,
    the uninitialized local variables were observable through `binding`.

    Initialize the locals to nil.

    [Bug #21021]
2025-02-13 17:44:58 -08:00
Alan Wu 73690b520d YJIT: Fix crash when yielding keyword arguments
Previously, the code for dropping surplus arguments when yielding
into blocks erroneously attempted to drop keyword arguments when there
is in fact no surplus arguments. Fix the condition and test that
supplying the exact number of keyword arguments as require compiles
without fallback.
2025-02-13 17:37:38 -08:00
Takashi Kokubun 527cc73282 YJIT: Return None if entry block compilation fails (#12445) 2024-12-23 22:12:08 +00:00
Takashi Kokubun 6bf7a1765f YJIT: Load registers on JIT entry to reuse blocks (#12355) 2024-12-17 12:32:42 -05:00
f3a117605c YJIT: Speculate block arg for c_func_method(&nil) calls (#12326)
A good amount of call sites always pass nil as block argument, but the
nil doesn't show up in the context. Put a runtime guard for those
cases to handle it. Particular relevant for the `ruby-lsp` benchmark in
`yjit-bench`. Up to a 2% speedup across headline benchmarks.

Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com>
Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com>
Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>
Co-authored-by: Kevin Menard <kevin@nirvdrum.com>
Co-authored-by: Randy Stauner <randy.stauner@shopify.com>
2024-12-13 10:41:04 -05:00
Alan Wu e7ee7d43f3 YJIT: Allow then-unknown static_mut_refs on older Rusts [ci skip] 2024-12-12 18:52:51 -05:00
Alan Wu d53e4545f4 YJIT: Fix unread field lint in release builds
```
warning: fields `blue_begin` and `blue_end` are never read
```
2024-12-11 17:44:43 -05:00
Alan Wu 9fe06cc035 YJIT: Disable static_mut_refs for now 2024-12-11 17:44:43 -05:00
Alan Wu 6cb75564f9 YJIT: Use the correct size constant 2024-12-11 17:44:43 -05:00
Takashi Kokubun 14e0a40cd0 YJIT: Add a comment about a lazy frame call
jit_prepare_lazy_frame_call is a complicated trick and comes with memory
overhead. Every use of the function should come with justification.
2024-12-09 10:09:40 -08:00
Takashi Kokubun cff031253f YJIT: Spill/load argument registers to reuse blocks (#12287)
* YJIT: Spill/load argument registers to reuse blocks

* Mention the immediate function name

* Explain the context behind spill/load operations
2024-12-09 10:02:40 -08:00
Max Bernstein 8010d79bb4 YJIT: Only enable disassembly colors for tty (#12283)
* YJIT: Use fully-qualified name for OPTIONS in get_options!

* YJIT: Only enable disassembly colors for tty
2024-12-09 10:36:17 -05:00
1c4dbb133e YJIT: Generate specialized code for Symbol for objtostring (#12247)
* YJIT: Generate specialized code for Symbol for objtostring

Co-authored-by: John Hawthorn <john@hawthorn.email>

* Update yjit/src/codegen.rs

---------

Co-authored-by: John Hawthorn <john@hawthorn.email>
Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com>
2024-12-04 21:34:16 +00:00
Maxime Chevalier-Boisvert 4b4d52ef50 YJIT: track time since initialization (#12263) 2024-12-04 21:24:36 +00:00
Alan Wu 2fc357c16d YJIT: Avoid std::ffi::CString with rb_intern2() during boot
Fewer allocations on boot, too.

Suggested-by: https://github.com/ruby/ruby/pull/12217
2024-11-29 16:45:22 -05:00
John Hawthorn a5119a3f27 YJIT: Add missing prepare before calling str_dup 2024-11-28 15:04:12 -05:00
Randy Stauner 8f9b9aecd0 YJIT: Implement opt_reverse insn (#12175) 2024-11-26 16:49:24 -05:00
Randy StaunerandAndrew Novoselac 1dd40ec18a Optimize instructions when creating an array just to call include? (#12123)
* Add opt_duparray_send insn to skip the allocation on `#include?`

If the method isn't going to modify the array we don't need to copy it.
This avoids the allocation / array copy for things like `[:a, :b].include?(x)`.

This adds a BOP for include? and tracks redefinition for it on Array.

Co-authored-by: Andrew Novoselac <andrew.novoselac@shopify.com>

* YJIT: Implement opt_duparray_send include_p

Co-authored-by: Andrew Novoselac <andrew.novoselac@shopify.com>

* Update opt_newarray_send to support simple forms of include?(arg)

Similar to opt_duparray_send but for non-static arrays.

* YJIT: Implement opt_newarray_send include_p

---------

Co-authored-by: Andrew Novoselac <andrew.novoselac@shopify.com>
2024-11-26 14:31:08 -05:00
Maxime Chevalier-Boisvert 081bdc5125 YJIT: fix small typo in command line options help (#12167) 2024-11-25 19:32:19 +00:00
Alan Wu bf718cef59 YJIT: Make compilation_failure a default stat (#12128)
It's good to monitor compilation failures.
2024-11-20 17:13:31 -05:00
Alan Wu 350b544468 YJIT: Refactor to forward jump_to_next_insn() return value
It's more concise this way and since `return Some(EndBlock)` is the only
correct answer, no point repeating it everywhere.
2024-11-20 10:06:14 -05:00
Alan Wu 199877d258 YJIT: Abandon block when gen_outlined_exit() fails
When CodeBlock::set_page fails (part of next_page(), see their docs for
exact conditions), it can cause gen_outlined_exit() to fail while there
is still plenty of memory available. Previously, this can have YJIT
running incomplete code due to taking the early return in
end_block_with_jump() that manifested as crashes with SIGILL.

Add and use a wrapper with error handling.
2024-11-20 10:06:14 -05:00
Takashi Kokubun 31bdffb5b9 YJIT: Specialize String#dup (#12090) 2024-11-14 18:15:39 -05:00
Takashi Kokubun 7e2f9eaccd YJIT: Specialize Integer#pred (#12082) 2024-11-14 12:04:48 -05:00
Takashi Kokubun 30e1d6b5a8 YJIT: Add inline_block_count stat (#12081) 2024-11-13 16:17:29 -05:00
beafae9750 YJIT: Specialize String#[] (String#slice) with fixnum arguments (#12069)
* YJIT: Specialize `String#[]` (`String#slice`) with fixnum arguments

String#[] is in the top few C calls of several YJIT benchmarks:
liquid-compile rubocop mail sudoku

This speeds up these benchmarks by 1-2%.

* YJIT: Try harder to get type info for `String#[]`

In the large generated code of the mail gem the context doesn't have
the type info.  In that case if we peek at the stack and add a guard
we can still apply the specialization
and it speeds up the mail benchmark by 5%.

Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com>
Co-authored-by: Takashi Kokubun (k0kubun) <takashikkbn@gmail.com>

---------

Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com>
Co-authored-by: Takashi Kokubun (k0kubun) <takashikkbn@gmail.com>
2024-11-13 12:25:09 -05:00
6deeec5d45 Mark strings returned by Symbol#to_s as chilled (#12065)
* Use FL_USER0 for ELTS_SHARED

This makes space in RString for two bits for chilled strings.

* Mark strings returned by `Symbol#to_s` as chilled

[Feature #20350]

`STR_CHILLED` now spans on two user flags. If one bit is set it
marks a chilled string literal, if it's the other it marks a
`Symbol#to_s` chilled string.

Since it's not possible, and doesn't make much sense to include
debug info when `--debug-frozen-string-literal` is set, we can't
include allocation source, but we can safely include the symbol
name in the warning message, making it much easier to find the source
of the issue.

Co-Authored-By: Étienne Barrié <etienne.barrie@gmail.com>

---------

Co-authored-by: Étienne Barrié <etienne.barrie@gmail.com>
Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
2024-11-13 09:20:00 -05:00
Peter Zhu 1d1c80e644 Fix false-positive memory leak using Valgrind in YJIT (#12057)
When we run with RUBY_FREE_AT_EXIT, there's a false-positive memory leak
reported in YJIT because the METHOD_CODEGEN_TABLE is never freed. This
commit adds rb_yjit_free_at_exit that is called at shutdown when
RUBY_FREE_AT_EXIT is set.

Reported memory leak:

    ==699816== 1,104 bytes in 1 blocks are possibly lost in loss record 1 of 1
    ==699816==    at 0x484680F: malloc (vg_replace_malloc.c:446)
    ==699816==    by 0x155B3E: UnknownInlinedFun (unix.rs:14)
    ==699816==    by 0x155B3E: UnknownInlinedFun (stats.rs:36)
    ==699816==    by 0x155B3E: UnknownInlinedFun (stats.rs:27)
    ==699816==    by 0x155B3E: alloc (alloc.rs:98)
    ==699816==    by 0x155B3E: alloc_impl (alloc.rs:181)
    ==699816==    by 0x155B3E: allocate (alloc.rs:241)
    ==699816==    by 0x155B3E: do_alloc<alloc::alloc::Global> (alloc.rs:15)
    ==699816==    by 0x155B3E: new_uninitialized<alloc::alloc::Global> (mod.rs:1750)
    ==699816==    by 0x155B3E: fallible_with_capacity<alloc::alloc::Global> (mod.rs:1788)
    ==699816==    by 0x155B3E: prepare_resize<alloc::alloc::Global> (mod.rs:2864)
    ==699816==    by 0x155B3E: resize_inner<alloc::alloc::Global> (mod.rs:3060)
    ==699816==    by 0x155B3E: reserve_rehash_inner<alloc::alloc::Global> (mod.rs:2950)
    ==699816==    by 0x155B3E: hashbrown::raw::RawTable<T,A>::reserve_rehash (mod.rs:1231)
    ==699816==    by 0x5BC39F: UnknownInlinedFun (mod.rs:1179)
    ==699816==    by 0x5BC39F: find_or_find_insert_slot<(usize, fn(&mut yjit::codegen::JITState, &mut yjit::backend::ir::Assembler, *const yjit::cruby::autogened::rb_callinfo, *const yjit::cruby::autogened::rb_callable_method_entry_struct, core::option::Option<yjit::codegen::BlockHandler>, i32, core::option::Option<yjit::cruby::VALUE>) -> bool), alloc::alloc::Global, hashbrown::map::equivalent_key::{closure_env#0}<usize, usize, fn(&mut yjit::codegen::JITState, &mut yjit::backend::ir::Assembler, *const yjit::cruby::autogened::rb_callinfo, *const yjit::cruby::autogened::rb_callable_method_entry_struct, core::option::Option<yjit::codegen::BlockHandler>, i32, core::option::Option<yjit::cruby::VALUE>) -> bool>, hashbrown::map::make_hasher::{closure_env#0}<usize, fn(&mut yjit::codegen::JITState, &mut yjit::backend::ir::Assembler, *const yjit::cruby::autogened::rb_callinfo, *const yjit::cruby::autogened::rb_callable_method_entry_struct, core::option::Option<yjit::codegen::BlockHandler>, i32, core::option::Option<yjit::cruby::VALUE>) -> bool, std::hash::random::RandomState>> (mod.rs:1413)
    ==699816==    by 0x5BC39F: hashbrown::map::HashMap<K,V,S,A>::insert (map.rs:1754)
    ==699816==    by 0x57C5C6: insert<usize, fn(&mut yjit::codegen::JITState, &mut yjit::backend::ir::Assembler, *const yjit::cruby::autogened::rb_callinfo, *const yjit::cruby::autogened::rb_callable_method_entry_struct, core::option::Option<yjit::codegen::BlockHandler>, i32, core::option::Option<yjit::cruby::VALUE>) -> bool, std::hash::random::RandomState> (map.rs:1104)
    ==699816==    by 0x57C5C6: yjit::codegen::reg_method_codegen (codegen.rs:10521)
    ==699816==    by 0x57C295: yjit::codegen::yjit_reg_method_codegen_fns (codegen.rs:10464)
    ==699816==    by 0x5C6B07: rb_yjit_init (yjit.rs:40)
    ==699816==    by 0x393723: ruby_opt_init (ruby.c:1820)
    ==699816==    by 0x393723: ruby_opt_init (ruby.c:1767)
    ==699816==    by 0x3957D4: prism_script (ruby.c:2215)
    ==699816==    by 0x3957D4: process_options (ruby.c:2538)
    ==699816==    by 0x396065: ruby_process_options (ruby.c:3166)
    ==699816==    by 0x236E56: ruby_options (eval.c:117)
    ==699816==    by 0x15BAED: rb_main (main.c:43)
    ==699816==    by 0x15BAED: main (main.c:62)

After this patch, there are no more memory leaks reported when running
RUBY_FREE_AT_EXIT with Valgrind on an empty Ruby script:

    $ RUBY_FREE_AT_EXIT=1 valgrind --leak-check=full ruby -e ""
    ...
    ==700357== HEAP SUMMARY:
    ==700357==     in use at exit: 0 bytes in 0 blocks
    ==700357==   total heap usage: 36,559 allocs, 36,559 frees, 6,064,783 bytes allocated
    ==700357==
    ==700357== All heap blocks were freed -- no leaks are possible
2024-11-11 20:45:11 +00:00
Alan Wu dccfab0c53 YJIT: Always abandon the block when gen_branch() or defer_compilation() fails
In [1], we started checking for gen_branch failures, but I made two
crucial mistakes. One, defer_compilation() had the same issue as
gen_branch() but wasn't checked. Two, returning None from a codegen
function does not throw away the block. Checking how gen_single_block()
handles codegen functions, you can see that None terminates the block
with an exit, but does not overall return an Err. This handling is fine
for unimplemented instructions, for example, but incorrect in case
gen_branch() fails. The missing branch essentially corrupts the
block; adding more code after a missing branch doesn't correct the code.

Always abandon the block when defer_compilation() or gen_branch() fails.

[1]: cb661d7d82
Fixup: [1]
2024-11-08 14:09:55 -05:00