From: "hsbt (Hiroshi SHIBATA) via ruby-core" Date: 2026-10-08T01:11:24+00:00 Subject: [ruby-core:127002] [Ruby Bug#22305] Ruby::Box: statically linked prism loads in only one box Issue #22305 has been updated by hsbt (Hiroshi SHIBATA). This is a note to sort out the discussion. The direction in #22379 is a hybrid of options 1 and 2. In mame's PoC (https://github.com/ruby/ruby/pull/19248), the prism gem becomes an ordinary bundled gem that each box dlopens on its own, and the parser built into the interpreter becomes `Ruby::Prism`, so the gem no longer collides with it. What remains here is sharing `Ruby::Prism` across boxes, which the PoC does not do yet. As mame noted, loading the autoload registered in the root box into the root box and sharing it with every box would solve it. I'll follow #22379 and keep this ticket for that part. ---------------------------------------- Bug #22305: Ruby::Box: statically linked prism loads in only one box https://bugs.ruby-lang.org/issues/22305#change-119395 * Author: hsbt (Hiroshi SHIBATA) * Status: Open * ruby -v: ruby 4.1.0dev (2026-09-10T03:20:50Z master fe58143f12) +YJIT +MN +PRISM [arm64-darwin27] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- Under `RUBY_BOX=1`, the statically linked prism can be required in only one box. The second box raises `LoadError`. ``` $ RUBY_BOX=1 ruby --disable-gems -e 'require "prism"; p Ruby::Box.new.eval(%q{require "prism"; Prism::VERSION})' lib/prism.rb:161:in 'Kernel#require': cannot load such file -- prism/prism (LoadError) ``` Swapping the order fails the same way. `--disable-gems` matters here. With a prism gem installed, each box dlopens its own copy of the gem's `prism.bundle` and the problem disappears, which makes it easy to miss. There is no `prism.so` file. `prism_init.c:8` registers the name `prism/prism.so` with `ruby_init_ext`, pointing at the `Init_prism` already linked into the interpreter, so `require "prism/prism"` behaves as if it had loaded an extension. That entry can be used only once (`load.c:1280`). The parser itself works in every box, but loading a gem that requires prism internally, such as error_highlight, is enough to break it in every box other than the one that loaded prism first. The obvious fixes do not hold. Running the init in every box leaves one set of C globals pointing at whichever box ran it last, so nodes parsed in the other box fail `is_a?(Prism::ProgramNode)`. That trades the `LoadError` for a silent type mismatch. Initializing prism before the boxes exist and marking the feature provided makes the `require "prism/prism"` inside an activated prism gem a no-op, so the gem's Ruby half binds to the interpreter's C half. The `ruby-lsp` benchmark fails on exactly that. I see three designs and would like one chosen. 1. Treat prism as an extension. The Ruby binding is built as its own shared object while the parser stays static, so each box dlopens its own copy the way it already does for other extensions. The same principle would also fix every extension under `--with-static-linked-ext`. It changes the build and needs symbol exports on Windows. 2. Treat it as part of the interpreter, like `String`. It is initialized once before the boxes and every box shares its classes. `defined?(Prism)` becomes true without a `require`, it adds 339 classes at boot, and a prism gem can no longer take over. 3. Change Box instead. One way would be to share a statically linked extension across boxes like a builtin class without marking it provided, so that a prism gem can still take over, though I am not sure that fits where Box is heading. ## How prism came to ship this way #19741 mirrored YARP into ruby/ruby together with its Ruby API and registered the native part with `ruby_init_ext`. #19772 then moved toward mirroring only the C implementation and leaving the Ruby API to a bundled prism gem (#19772#note-29), and #19900 stated that prism would be a bundled gem with a native extension. The static registration from #19741 survived the rename, so prism ships as a default gem linked into the interpreter, and that registration is what fails here. The native extension #19900 described works in every box, and the first design comes down to it. The second design has a history of its own. Matz first approved a built-in `Ruby::Parser` that no gem could replace (#19772#note-9), and the same idea came back as a gem-independent `Ruby::Node` for `Proc#syntax_tree` (#21795#note-1). There Matz required that the parser that ran the program and the parser that returns the tree be the same (#21795#note-19), and the feature settled on the default gem prism for that. That is exactly the prism a second box cannot load, which also blocks the way #21795#note-38 suggested Box could eventually expose the interpreter's own parser. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/