Skip to content

codegen: store constant union members inline instead of boxing#62191

Closed
maleadt wants to merge 2 commits into
masterfrom
tb/const_union_inline
Closed

codegen: store constant union members inline instead of boxing#62191
maleadt wants to merge 2 commits into
masterfrom
tb/const_union_inline

Conversation

@maleadt

@maleadt maleadt commented Jun 23, 2026

Copy link
Copy Markdown
Member

Since #55045 a small isbits Union built from a constant (e.g. x = cond ? a::Int32 : 0) is represented fully boxed: the unboxed payload slot is never written and consumers read the value back through a literal_pointer_val of the boxed constant. mark_julia_const marks constants as isboxed, so the union-phi path keeps them boxed rather than storing them inline as it did before. The embedded object pointer is dead weight and breaks consumers that can't dereference host addresses (e.g. GPU back-ends). Detect a constant whose type is an unboxed leaf of the target union and store its bits inline with an unmarked tindex, restoring the pre-#55045 representation.

@maleadt
maleadt requested a review from topolarity June 23, 2026 16:42
@gbaraldi

Copy link
Copy Markdown
Member

Maybe a codegen test?

@maleadt

maleadt commented Jun 23, 2026

Copy link
Copy Markdown
Member Author

Added a test.

Comment thread Compiler/test/codegen.jl
@maleadt
maleadt force-pushed the tb/const_union_inline branch from 0927608 to 02b31ff Compare June 23, 2026 20:47
@topolarity

topolarity commented Jun 25, 2026

Copy link
Copy Markdown
Member

This is a de-optimization for normal targets, since it means that a boxed(...) downstream of this ϕ will now be forced to allocate at runtime, even though it had a (boxed) constant object already available at codegen.

Do we need a codegen param to disable carrying those opportunistic boxes around on GPU targets?

@vtjnash

vtjnash commented Jun 25, 2026

Copy link
Copy Markdown
Member

Agreed,though furthermore this feels conceptually illogical and integrated in the wrong place

…mbers

Since #55045 a small isbits Union built from a constant (e.g.
`x = cond ? a::Int32 : Int64(0)`) is represented fully boxed: the payload
slot is never written and consumers read the value through an embedded
pointer to the boxed constant. This breaks back-ends that cannot
dereference host addresses (e.g. GPU). Add an `embed_pointers` CodegenParam
(default true); when disabled, store a constant whose type is an unboxed
leaf of the target union inline with an unmarked tindex. The default
retains the boxed representation, so CPU codegen is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@maleadt
maleadt force-pushed the tb/const_union_inline branch from 02b31ff to 3ae7db5 Compare June 25, 2026 09:51
@maleadt

maleadt commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

Okay. Implemented as a cgparam instead.

@gbaraldi

Copy link
Copy Markdown
Member

This isn't illogical because GPUs operate under different logic (they can't access our globals). But a cgparam seems like a good 2nd option

@topolarity

Copy link
Copy Markdown
Member

Is it possible to apply this as a transform pass instead?

Having to maintain all of the places we eagerly emit a boxed pointer is not going to age very well - We expect to do this more in future, and I don't think this covers all of the cases that we do it already

@maleadt

maleadt commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

Is it possible to apply this as a transform pass instead?

And how would that work?

@topolarity

topolarity commented Jun 25, 2026

Copy link
Copy Markdown
Member

It's certainly awkward. We can lift a jl_global# to a Module-local (constant) global for immutable pointer-free constants, which I think is all you need. The problem would be that you are now pretending that the value is boxed when it is not - you can copy the typetag for any typeof logic we do, but if it interacts with the GC at all it would not work. Of course GPUs do not interact with the GC. Maybe that idea is crazy though - only trying to find a good solution here

The alternative is branching everywhere that we eagerly try to forward boxes from .constant - that happens here in the PhiNode logic but it's also done in emit_ifelse, fieldtype, and convert_julia_type_to_union (which notably eagerly boxes when we return a .constant from a function with a Union return type). I think we'd probably want to re-factor this to a maybe_boxed(...) primitive or similar that can decide whether it is appropriate to eagerly forward a box, but I'm just worried it's quite easy to break GPUCompiler given how frequently we want this optimization on the host side.

@maleadt

maleadt commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

Maybe I'm misunderstanding, but I think I'd rather simply emit the isbits value rather than have to deal with copying CPU boxes to the GPU and rewrite the IR to make them addressable? We already have to deal with this in GPUCompiler for e.g. type tags, and having a cgparam could be useful to tie other emission patterns to.

I pushed a commit that prototypes your mabye_boxed idea; let me know what you think.

Route constant values through a centralized maybe_boxed helper before split-union construction can opportunistically forward boxed constants. This preserves the host default while letting pointer-free constants materialize as inline bits when embedded object pointers are disabled.

Co-authored-by: OpenAI Codex <codex@openai.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@maleadt
maleadt force-pushed the tb/const_union_inline branch from cb73785 to 8a33c0f Compare June 25, 2026 20:33
Comment thread base/reflection.jl
Comment on lines +194 to +198
If enabled, generated code may embed pointers to host-resident boxed Julia
objects. Disable this for targets that cannot dereference such pointers
when codegen can instead use an unboxed representation.
"""
embed_pointers::Cint

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wording wise, it does mention boxed objects, but embed_pointers implies something about singleton values like Symbol and Type which we do want to support on the GPU.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but I was hoping we could also avoid some of that pointer embedding over time, hence keeping the language more neutral. Although to be honest I haven't really considered how we'd do that.

@adienes adienes added the compiler:codegen Generation of LLVM IR and native code label Jul 2, 2026
@maleadt
maleadt marked this pull request as draft July 10, 2026 17:00
@maleadt

maleadt commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

Trying something out in GPUCompiler.jl instead: JuliaGPU/GPUCompiler.jl#874
@topolarity Would love to hear your thoughts on that.

@maleadt

maleadt commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

Done in GPUCompiler.jl instead: JuliaGPU/GPUCompiler.jl#874

@maleadt maleadt closed this Jul 14, 2026
@giordano
giordano deleted the tb/const_union_inline branch July 18, 2026 10:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compiler:codegen Generation of LLVM IR and native code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants