Skip to content

Route zero- and one-argument instance sends through shared helpers - #93

Merged
maleadt merged 1 commit into
mainfrom
tb/shared-sends
Oct 29, 2026
Merged

maleadt merged 1 commit into
mainfrom
tb/shared-sends

Conversation

@maleadt

@maleadt maleadt commented Oct 28, 2026

Copy link
Copy Markdown
Member

Every @objc send expands its argument conversion, autorelease pool, tracing hook and result wrapping inline, and @objcproperties puts one such send in every branch of the generated objc_getproperty and objc_setproperty! methods. For Metal.jl that's 690 methods holding 5.6 MB of compressed source, which Julia decompresses at every load to scan for bindings: 53 ms on Julia 1.13, 170 ms on 1.12.

On Julia 1.12 and later, instance sends with at most one argument now call @inline helpers that do the same steps in the same order, with the objc_msgSend call itself generated per signature from @ccall expansions made at precompile time (and stored as immutable templates, so the generator only substitutes types). Tracing mode, struct returns through objc_msgSend_stret, Out arguments, sends with more arguments and older Julia versions keep the expanded code; both paths share the result-wrapping logic. The generated code is the same as before (@code_llvm identical for getters, setters, adopting and one-argument sends, apart from a slightly different block layout in cascades with nullable-object properties), and steady-state property access is unchanged.

For Metal.jl, the property methods' source shrinks to 0.7 MB and the image by 5.5 MB, and it loads 40 to 75 ms faster on Julia 1.12 and 1.13. The cost: inferring a property method that wasn't precompiled takes about 0.5 ms more per property, and the first send with a new signature generates its ccall (about 0.7 ms).

The added tests cover properties of each return-type shape, setters, adoption of init/mutableCopy results with deallocation checked, nil returns, the order in which the receiver, availability and argument are handled, and that the autorelease pool stays balanced when a conversion throws.

Every `@objc` send used to expand its argument conversion, autorelease
pool, tracing hook and result wrapping inline. `@objcproperties` puts
one such send in every branch of the generated `objc_getproperty` and
`objc_setproperty!` methods, so these methods get large: for Metal.jl,
690 methods holding 5.6 MB of compressed source, which Julia decompresses
at every load to scan for bindings.

On Julia 1.12 and later, instance sends with at most one argument now
call `@inline` helpers that implement the same steps in the same order,
with the `objc_msgSend` call generated per signature from `@ccall`
expansions made at precompile time. Steady-state performance is
unchanged. Tracing mode, struct returns through `objc_msgSend_stret`,
`Out` arguments, sends with more arguments, and older Julia versions
keep the expanded code. Both code paths share the result-wrapping logic.

For Metal.jl, this shrinks the property methods' source to 0.7 MB and
the image by 5.5 MB, and loads it 50 to 75 ms faster on Julia 1.12 and
1.13. Inferring a property method that wasn't precompiled takes slightly
longer, about 0.5 ms per property.
@maleadt
maleadt merged commit 645794a into main Oct 29, 2026
12 checks passed
@maleadt
maleadt deleted the tb/shared-sends branch October 29, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant