Skip to content

[WIP] cl, runtime, reflect: support typed SetFinalizer and noffi method values - #2497

Open
visualfc wants to merge 8 commits into
xgo-dev:mainfrom
visualfc:experiment/finalizer-typed-wrapper
Open

visualfc wants to merge 8 commits into
xgo-dev:mainfrom
visualfc:experiment/finalizer-typed-wrapper

Conversation

@visualfc

@visualfc visualfc commented Sep 4, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR adds compiler and runtime support for statically typed runtime.SetFinalizer calls and a noffi reflect mode for builds that do not need libffi.

Implementation

  • Lower compatible statically typed runtime.SetFinalizer calls to runtime.SetFinalizerPtr.
  • Generate an internal typed wrapper that converts the runtime unsafe.Pointer back to the finalizer's pointer argument, including any and implemented interface parameters via MakeInterface.
  • Preserve the original execution timing for direct calls, defer, and go calls.
  • Lower runtime.SetFinalizer(obj, nil) to the pointer-based cancellation path.
  • Keep the boxed runtime.SetFinalizer path for capturing closures and for runtimes that do not provide SetFinalizerPtr, including WASM and collector-free runtimes.
  • Add pointer-based finalizer registration, replacement, cancellation, callback queuing, and execution handling.
  • Preserve cleanup chains installed before or after a typed finalizer when that finalizer is canceled or replaced.
  • When -check-libffi=true, scan the already-built Go SSA with RTA from init/main (or every function for libraries and tests). Restart once with llgo_noffi and llgo_methodvalue_noffi if reflect.Value.Call, CallSlice, MakeFunc, Windows NewCallback, or an unlowered SetFinalizer are unreachable. LLVM backend and LTO still run once.
  • Skip that scan on WASI Preview 1 (wasip1, -target wasi / wasip1). WASI has no host libffi and uses typed reflection bridges.
  • On js/wasm, pass -flto through to clang/emcc -c so -lto=full emits bitcode and can DCE unused $methodvalue thunks.
  • On js/wasm, noffi builds omit the libffi Asyncify bind removelist (llgo_reflect_bind*_js) so emcc does not warn about unmatched patterns.
  • Use ABI-correct $methodvalue thunks for noffi MethodByName().Interface() values so the receiver is passed as an ordinary method argument.
  • Keep reflect.MakeFunc and reflect.Value.Call unavailable when llgo_noffi is enabled.
  • Keep Windows syscall.NewCallback and syscall.NewCallbackCDecl unavailable in noffi mode.

Build Flag

llgo build -check-libffi=true opts into automatic libffi selection. It is off by default, including with -lto=full. Eligible builds scan Go SSA reachability and compile once without libffi when the package graph does not need it. WASI Preview 1 ignores the flag.

Comparison

Program: cmd/_demo/hellow (fmt.Println("hello world")). Measured on macOS arm64 against current xgo-dev/main (81a4d030f) and this PR (971590ba8 plus local follow-ups).

js/wasm32 rows use -lto=full on this PR. Current main does not pass -flto to wasm clang -c, so those compiles do not complete here; the wasm comparison is this PR with libffi versus this PR with -check-libffi.

Platform Build Flags Size Change Links libffi
macOS arm64 main -lto=full 926992 bytes - dynamic link
macOS arm64 This PR -lto=full -check-libffi 926688 bytes -304 B / -0.03% No
js/wasm32 This PR -lto=full 1205032 bytes - static
js/wasm32 This PR -lto=full -check-libffi 1139798 bytes -65234 B / -5.4% No

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: typed-wrapper lowering for runtime.SetFinalizer

This is a well-structured change. The compiler-side lowering is careful: it gates strictly on llssa.PathOf(...) == "runtime" (so a user-defined SetFinalizer is left alone — nicely covered by TestSetFinalizerLoweringTargetsRuntimeOnly), recovers concrete operands from the SSA-boxed MakeInterface nodes, handles the nil-clear case, and preserves Call/Defer/Go timing via LowerSetFinalizerCall. setFinalizerArgCompatible mirrors Go's SetFinalizer argument rules, and the m[key] == e guard in RunPointerFinalizers correctly avoids clobbering a newer registration.

The main concern is a runtime concurrency asymmetry in the new registry (see the P1 inline finding). Remaining items are minor. Inline comments have the specifics.

Comment on lines +123 to +134
func pointerFinalizerCallback(ptr unsafe.Pointer, cb unsafe.Pointer) {
e := (*pointerFinalizerEntry)(cb)
pointerFinalizers.mu.Lock()
e.arg = ptr
if pointerFinalizers.tail == nil {
pointerFinalizers.head = e
} else {
pointerFinalizers.tail.next = e
}
pointerFinalizers.tail = e
pointerFinalizers.mu.Unlock()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] pointerFinalizerCallback takes a lock in BDWGC finalizer context

pointerFinalizerCallback calls pointerFinalizers.mu.Lock(), but the sibling freeCleanupSlot in this same file carries an explicit warning that BDWGC-invoked callbacks "must not allocate or acquire a lock: BDWGC may invoke another finalizer while Go code is in a collector allocation or map operation" — and it is deliberately implemented lock-free with CAS. With the default GC_register_finalizer (no GC_set_finalize_on_demand), BDWGC can run finalizers synchronously from within GC_malloc. If a thread is inside SetFinalizerPtr holding pointerFinalizers.mu and an allocation there (e.g. &pointerFinalizerEntry{} or map growth) drives BDWGC to invoke pointerFinalizerCallback on the same thread, it self-deadlocks on the non-recursive mutex. Consider making the callback lock-free like freeCleanupSlot, or confirm/document why these callbacks can never run while pointerFinalizers.mu is held. Independently flagged by three reviewers.

Comment on lines +251 to +279
func SetFinalizerPtr(obj unsafe.Pointer, finalizer func(unsafe.Pointer)) {
if obj == nil {
panic("runtime.SetFinalizerPtr: ptr is nil")
}
pointerFinalizers.once.Do(initPointerFinalizers)
key := ^uintptr(obj)

pointerFinalizers.mu.Lock()
old := pointerFinalizers.m[key]
delete(pointerFinalizers.m, key)
if old != nil {
var ignoredFn bdwgc.FinalizerFunc
var ignoredCb unsafe.Pointer
bdwgc.RegisterFinalizer(obj, old.prevFn, old.prevCb, &ignoredFn, &ignoredCb)
}
if finalizer == nil {
pointerFinalizers.mu.Unlock()
return
}

e := &pointerFinalizerEntry{fn: finalizer}
var ignoredFn bdwgc.FinalizerFunc
var ignoredCb unsafe.Pointer
bdwgc.RegisterFinalizer(obj, pointerFinalizerCallback, unsafe.Pointer(e), &ignoredFn, &ignoredCb)
e.prevFn = ignoredFn
e.prevCb = ignoredCb
pointerFinalizers.m[key] = e
pointerFinalizers.mu.Unlock()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Global mutex + allocation on every SetFinalizer registration

SetFinalizerPtr holds pointerFinalizers.mu across a heap allocation (&pointerFinalizerEntry{...}), a map insert (potential rehash), and two bdwgc.RegisterFinalizer C-calls. This serializes every SetFinalizer call site in the program through one global lock (vs. Go's sharded finalizer tables), and the allocation-under-lock is the concrete trigger for the re-entrancy hazard in the P1 finding. Reducing the work done under the lock — or moving allocation/registration outside the critical section — would address both.

Comment thread ssa/expr.go Outdated
Comment thread ssa/expr.go
Comment on lines +2144 to +2158
wrapperSig := types.NewSignatureType(nil, nil, nil,
types.NewTuple(types.NewVar(token.NoPos, nil, "ptr", unsafePtr)),
types.NewTuple(), false)
name := fmt.Sprintf("%s$setFinalizer.%d", b.Pkg.Path(), atomic.AddUint64(&setFinalizerWrapperSeq, 1))
wrapper := b.Pkg.NewFunc(name, wrapperSig, InGo)
wb := wrapper.MakeBody(1)
converted := wb.Convert(b.Prog.rawType(ptrType), wb.Param(0))
if !types.Identical(argType, ptrType) {
converted = wb.Convert(b.Prog.rawType(argType), converted)
}
wb.Call(args[1], converted)
wb.Return()
fn := wb.MakeClosure(wrapper.Expr, nil)
obj := b.Convert(b.Prog.VoidPtr(), args[0])
return runtimeFn, []Expr{obj, fn}, true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P3] Per-call-site wrapper function increases code size

Each lowered SetFinalizer call site emits a fresh named function (%s$setFinalizer.%d, global sequence counter) plus a MakeClosure(..., nil). Since lowering is already gated to vkFuncDecl finalizers, the wrapper captures nothing and is effectively a static thunk — yet a distinct symbol is generated even for structurally identical wrappers. Consider caching wrappers keyed on the conversion shape (ptrType, argType, finalizerFn) to bound code-size growth. Runtime cost is fine (nil closure env); this is purely output size.

Comment thread runtime/internal/runtime/z_gc.go
@visualfc
visualfc marked this pull request as draft September 4, 2026 04:32
@codecov

codecov Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@visualfc
visualfc force-pushed the experiment/finalizer-typed-wrapper branch from c9e249e to 9ec1e7e Compare September 4, 2026 07:01
@visualfc visualfc changed the title [WIP] cl, runtime: lower typed SetFinalizer calls [WIP] cl, runtime, reflect: lower typed SetFinalizer calls and support noffi method values Sep 4, 2026
@visualfc visualfc changed the title [WIP] cl, runtime, reflect: lower typed SetFinalizer calls and support noffi method values [WIP] cl, runtime, reflect: support typed SetFinalizer and noffi method values Sep 4, 2026
@visualfc
visualfc marked this pull request as ready for review September 4, 2026 07:26

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: typed SetFinalizer wrapper / noffi path

Solid, carefully-designed change. The SSA lowering restricts to *types.Pointer receivers, rejects closures/bound methods (vkFuncDecl check) so the generated wrapper is a safe no-env closure, handles the nil-clear path, and gates on SetFinalizerPtr being present so wasm/other targets fall back cleanly. finalizerConcreteArgs correctly unboxes the MakeInterface nodes. The lock-free finalizer registry keeps allocation out of the critical section and uses complemented uintptr keys so the registry map does not retain objects. The llgo_noffi restart is gated by four conjunctive conditions and bounded to a single re-run via noFFIRestart.

Findings below. The most important is the unreachable direct-method-value block in value.go.

Note (out of diff, worth confirming): the entire lock-free scheme (pointerFinalizers.pending LIFO and the cleanupSlots free list) depends on runtime/internal/sync/atomic genuinely lowering to fenced atomic hardware ops. Two reviewers independently flagged this as the load-bearing assumption; if atomic.Store(&e.next, head) is not at least release-ordered relative to the CAS, a concurrent drain could observe a stale e.next.

Comment thread runtime/internal/lib/reflect/value.go Outdated
Comment thread ssa/expr.go Outdated
Comment thread ssa/expr.go Outdated
Comment thread internal/build/build.go Outdated
Comment thread internal/build/build.go Outdated
delete(pointerFinalizers.m, key)
}
pointerFinalizers.mu.Unlock()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The drain loop acquires/releases pointerFinalizers.mu once per finalized entry. Since SetFinalizerPtr holds the same mutex across its BDWGC RegisterFinalizer calls, a large finalization batch produces O(N) lock round-trips that each contend against concurrent registrations. The queue is already detached and private to the draining goroutine, so the map deletes could be batched under a single Lock/Unlock after the callbacks run — O(1) acquisitions and a shorter registration-blocking window.

Comment thread runtime/internal/lib/reflect/makefunc.go Outdated
Comment thread runtime/internal/lib/reflect/methodvalue_noffi.go Outdated
@visualfc
visualfc force-pushed the experiment/finalizer-typed-wrapper branch 2 times, most recently from 1b46348 to 1094c7d Compare September 5, 2026 14:06
@visualfc visualfc changed the title [WIP] cl, runtime, reflect: support typed SetFinalizer and noffi method values cl, runtime, reflect: support typed SetFinalizer and noffi method values Sep 5, 2026
@visualfc
visualfc force-pushed the experiment/finalizer-typed-wrapper branch from 1094c7d to e718218 Compare September 5, 2026 22:34
@github-actions

github-actions Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

LLGo WebAssembly build benchmarks

a02491ac91fc | workflow run | long-term charts

WebAssembly output sizes
Example, profile and compiler Wasm module vs base Generated JS glue vs base
cprintf/j32-emscripten/LLGo 137467 B +1721 B / +1.3% (worse) 70786 B 0 B / +0.0%
cprintf/j32-goos-js/LLGo 135718 B +1723 B / +1.3% (worse) 69150 B 0 B / +0.0%
cprintf/j64-emscripten-memory64/LLGo 125957 B +1714 B / +1.4% (worse) 73998 B 0 B / +0.0%
cprintf/w32-goos-wasip1/LLGo 139975 B +1714 B / +1.2% (worse) 0 B 0 B / 0.0%
cprintf/w32-wasi/LLGo 139981 B +1716 B / +1.2% (worse) 0 B 0 B / 0.0%
fmtprintf/j32-emscripten/LLGo 3411952 B +321106 B / +10.4% (worse) 114540 B 0 B / +0.0%
fmtprintf/j32-goos-js/Go 2526852 B 0 B / +0.0% 0 B 0 B / 0.0%
fmtprintf/j32-goos-js/LLGo 3404048 B +321199 B / +10.4% (worse) 98239 B 0 B / +0.0%
fmtprintf/j64-emscripten-memory64/LLGo 3152132 B +322827 B / +11.4% (worse) 121521 B 0 B / +0.0%
fmtprintf/w32-goos-wasip1/Go 2500019 B 0 B / +0.0% 0 B 0 B / 0.0%
fmtprintf/w32-goos-wasip1/LLGo 3106030 B +276926 B / +9.8% (worse) 0 B 0 B / 0.0%
fmtprintf/w32-wasi/LLGo 2969439 B +271781 B / +10.1% (worse) 0 B 0 B / 0.0%
j32-emscripten/LLGo 136742 B +1722 B / +1.3% (worse) 70786 B 0 B / +0.0%
j32-goos-js/Go 1895533 B 0 B / +0.0% 0 B 0 B / 0.0%
j32-goos-js/LLGo 135167 B +1726 B / +1.3% (worse) 69150 B 0 B / +0.0%
j64-emscripten-memory64/LLGo 125287 B +1714 B / +1.4% (worse) 73998 B 0 B / +0.0%
reflectcall/j32-emscripten/LLGo 1630260 B +148928 B / +10.1% (worse) 88949 B 0 B / +0.0%
reflectcall/j32-goos-js/Go 2191221 B 0 B / +0.0% 0 B 0 B / 0.0%
reflectcall/j32-goos-js/LLGo 1632332 B +149292 B / +10.1% (worse) 87313 B 0 B / +0.0%
reflectcall/j64-emscripten-memory64/LLGo 1517116 B +148796 B / +10.9% (worse) 94158 B 0 B / +0.0%
reflectcall/w32-goos-wasip1/Go 2205707 B 0 B / +0.0% 0 B 0 B / 0.0%
reflectcall/w32-goos-wasip1/LLGo 1679134 B +144927 B / +9.4% (worse) 0 B 0 B / 0.0%
reflectcall/w32-wasi/LLGo 1600283 B +139441 B / +9.5% (worse) 0 B 0 B / 0.0%
w32-goos-wasip1/Go 1909947 B 0 B / +0.0% 0 B 0 B / 0.0%
w32-goos-wasip1/LLGo 139286 B +1736 B / +1.3% (worse) 0 B 0 B / 0.0%
w32-wasi/LLGo 139356 B +1735 B / +1.3% (worse) 0 B 0 B / 0.0%
LLGo WebAssembly build measurements
Example and profile Build vs base
j32-emscripten 8.128 s +1.582 s / +24.2% (worse)
j32-goos-js 7.519 s +866.4 ms / +13.0% (worse)
j64-emscripten-memory64 6.939 s +1.333 s / +23.8% (worse)
reflectcall/w32-wasi 33.378 s -2.13 s / -6.0% (better)
w32-goos-wasip1 7.309 s +2.189 s / +42.8% (worse)
w32-wasi 6.269 s +1.364 s / +27.8% (worse)

Compared with 81a4d030f948 measured in the same runner job.

@github-actions

github-actions Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

LLGo baseline benchmarks

5cc9cac8b2e7 | workflow run | long-term charts

Program measurements

Platform Workload File size vs base Text size vs base Build vs base Run vs base
Linux cprintf 7160 B 0 B / +0.0% 387 B 0 B / +0.0% 676.714 ms -353 ms / -34.3% (better) 1.513 ms -322.5 us / -17.6% (better)
Linux cprintf-lto 6912 B 0 B / +0.0% 368 B 0 B / +0.0% 627.676 ms -316.4 ms / -33.5% (better) 1.461 ms -1.947 ms / -57.1% (better)
Linux fmtprintf 1800240 B +159912 B / +9.7% (worse) 528875 B +41583 B / +8.5% (worse) 5.279 s -2.188 s / -29.3% (better) 3.777 ms -4.981 ms / -56.9% (better)
Linux fmtprintf-lto 1649352 B +170768 B / +11.5% (worse) 471467 B +48612 B / +11.5% (worse) 16.072 s +1.114 s / +7.4% (worse) 5.182 ms +1.758 ms / +51.3% (worse)
Linux println 63984 B +1824 B / +2.9% (worse) 14931 B +284 B / +1.9% (worse) 708.314 ms -97.62 ms / -12.1% (better) 1.780 ms -326.6 us / -15.5% (better)
Linux println-lto 56024 B +1776 B / +3.3% (worse) 12349 B +234 B / +1.9% (worse) 1.112 s -22.52 ms / -2.0% (better) 1.876 ms -819.7 us / -30.4% (better)
macOS cprintf 68064 B 0 B / +0.0% 4429 B 0 B / +0.0% 578.282 ms +62.71 ms / +12.2% (worse) 2.433 ms -140.3 us / -5.4% (better)
macOS cprintf-lto 84288 B 0 B / +0.0% 193 B 0 B / +0.0% 526.854 ms -17.96 ms / -3.3% (better) 2.214 ms -213.8 us / -8.8% (better)
macOS fmtprintf 1612208 B +128384 B / +8.7% (worse) 906872 B +44836 B / +5.2% (worse) 2.586 s -93.31 ms / -3.5% (better) 3.699 ms -259.7 us / -6.6% (better)
macOS fmtprintf-lto 1242480 B +66672 B / +5.7% (worse) 888764 B +55768 B / +6.7% (worse) 6.808 s +188.5 ms / +2.8% (worse) 3.566 ms -395.8 us / -10.0% (better)
macOS println 115392 B +720 B / +0.6% (worse) 35598 B +716 B / +2.1% (worse) 491.770 ms -62.76 ms / -11.3% (better) 2.784 ms -340 us / -10.9% (better)
macOS println-lto 118736 B +16 B / +0.01348% (worse) 32992 B +672 B / +2.1% (worse) 658.081 ms -17.6 ms / -2.6% (better) 3.024 ms -2.458 us / -0.1% (better)
Windows MinGW cprintf 19456 B 0 B / +0.0% 4550 B 0 B / +0.0% 981.725 ms -1.867 ms / -0.2% (better) 3.964 ms -300.5 us / -7.0% (better)
Windows MinGW cprintf-lto 17920 B 0 B / +0.0% 4486 B 0 B / +0.0% 1.030 s +11.13 ms / +1.1% (worse) 3.793 ms +40.5 us / +1.1% (worse)
Windows MinGW fmtprintf 2057728 B +143872 B / +7.5% (worse) 634998 B +45280 B / +7.7% (worse) 3.727 s +66.68 ms / +1.8% (worse) 8.112 ms -297.2 us / -3.5% (better)
Windows MinGW fmtprintf-lto 2104320 B +169472 B / +8.8% (worse) 593142 B +57296 B / +10.7% (worse) 9.512 s +722.3 ms / +8.2% (worse) 7.564 ms -1.261 ms / -14.3% (better)
Windows MinGW println 72704 B +1536 B / +2.2% (worse) 24422 B +720 B / +3.0% (worse) 996.992 ms -17.91 ms / -1.8% (better) 7.057 ms -55.9 us / -0.8% (better)
Windows MinGW println-lto 67072 B +2048 B / +3.1% (worse) 21030 B +624 B / +3.1% (worse) 1.215 s -3.21 ms / -0.3% (better) 7.173 ms -175 us / -2.4% (better)
Windows MinGW 386 cprintf 49152 B +6144 B / +14.3% (worse) 5326 B 0 B / +0.0% 862.773 ms +7.394 ms / +0.9% (worse) 4.522 ms +197.9 us / +4.6% (worse)
Windows MinGW 386 cprintf-lto 20992 B 0 B / +0.0% 5094 B 0 B / +0.0% 954.669 ms +72.02 ms / +8.2% (worse) 3.830 ms -92.9 us / -2.4% (better)
Windows MinGW 386 fmtprintf 2085376 B +208384 B / +11.1% (worse) 508142 B +44800 B / +9.7% (worse) 3.368 s +97.53 ms / +3.0% (worse) 8.456 ms +527.9 us / +6.7% (worse)
Windows MinGW 386 fmtprintf-lto 2332160 B +179712 B / +8.3% (worse) 484322 B +43592 B / +9.9% (worse) 8.558 s +787.3 ms / +10.1% (worse) 8.457 ms +197.3 us / +2.4% (worse)
Windows MinGW 386 println 98816 B +7680 B / +8.4% (worse) 20246 B +432 B / +2.2% (worse) 885.492 ms +17.27 ms / +2.0% (worse) 6.661 ms +8.7 us / +0.1% (worse)
Windows MinGW 386 println-lto 70656 B +1536 B / +2.2% (worse) 18118 B +376 B / +2.1% (worse) 1.056 s +26.91 ms / +2.6% (worse) 6.581 ms -47 us / -0.7% (better)
Windows MinGW ARM64 cprintf 18944 B 0 B / +0.0% 4408 B 0 B / +0.0% 1.287 s +1.304 ms / +0.1% (worse) 5.924 ms -493.8 us / -7.7% (better)
Windows MinGW ARM64 cprintf-lto 17920 B 0 B / +0.0% 4340 B 0 B / +0.0% 1.289 s -6.576 ms / -0.5% (better) 5.819 ms -69.9 us / -1.2% (better)
Windows MinGW ARM64 fmtprintf 1936896 B +135168 B / +7.5% (worse) 544228 B +42224 B / +8.4% (worse) 4.150 s +84.64 ms / +2.1% (worse) 11.543 ms -709.6 us / -5.8% (better)
Windows MinGW ARM64 fmtprintf-lto 2016768 B +158208 B / +8.5% (worse) 520180 B +54036 B / +11.6% (worse) 9.949 s +873.8 ms / +9.6% (worse) 12.352 ms -236.1 us / -1.9% (better)
Windows MinGW ARM64 println 70144 B +2048 B / +3.0% (worse) 22816 B +440 B / +2.0% (worse) 1.297 s +19.33 ms / +1.5% (worse) 10.214 ms -374.5 us / -3.5% (better)
Windows MinGW ARM64 println-lto 65536 B +2048 B / +3.2% (worse) 19844 B +400 B / +2.1% (worse) 1.515 s +45.73 ms / +3.1% (worse) 10.148 ms -311.1 us / -3.0% (better)
Windows MSVC cprintf 120320 B 0 B / +0.0% 65782 B 0 B / +0.0% 986.547 ms -139.3 ms / -12.4% (better) 3.488 ms +163.7 us / +4.9% (worse)
Windows MSVC cprintf-lto 119808 B 0 B / +0.0% 65718 B 0 B / +0.0% 1.167 s +169.8 ms / +17.0% (worse) 3.715 ms +207.1 us / +5.9% (worse)
Windows MSVC fmtprintf 1705984 B +77824 B / +4.8% (worse) 730550 B +45312 B / +6.6% (worse) 3.975 s +70.65 ms / +1.8% (worse) 9.609 ms -41.6 us / -0.4% (better)
Windows MSVC fmtprintf-lto 1722368 B +105472 B / +6.5% (worse) 694534 B +58272 B / +9.2% (worse) 9.954 s +614 ms / +6.6% (worse) 9.712 ms -390.9 us / -3.9% (better)
Windows MSVC println 194048 B +1536 B / +0.8% (worse) 119846 B +720 B / +0.6% (worse) 1.006 s -98.89 ms / -9.0% (better) 7.936 ms -1.573 ms / -16.5% (better)
Windows MSVC println-lto 190464 B +512 B / +0.3% (worse) 117030 B +656 B / +0.6% (worse) 1.198 s +4.413 ms / +0.4% (worse) 7.930 ms +23.9 us / +0.3% (worse)
Windows MSVC 386 cprintf 9728 B 0 B / +0.0% 3931 B 0 B / +0.0% 966.741 ms +31.96 ms / +3.4% (worse) 5.366 ms -422.6 us / -7.3% (better)
Windows MSVC 386 cprintf-lto 9216 B 0 B / +0.0% 3853 B 0 B / +0.0% 970.616 ms -3.695 ms / -0.4% (better) 5.675 ms +9.3 us / +0.2% (worse)
Windows MSVC 386 fmtprintf 1248256 B +57856 B / +4.9% (worse) 491541 B +44848 B / +10.0% (worse) 4.031 s +151.8 ms / +3.9% (worse) 11.955 ms +433.7 us / +3.8% (worse)
Windows MSVC 386 fmtprintf-lto 1287680 B +62976 B / +5.1% (worse) 461241 B +43856 B / +10.5% (worse) 9.774 s +1.064 s / +12.2% (worse) 12.054 ms +302.2 us / +2.6% (worse)
Windows MSVC 386 println 34816 B +512 B / +1.5% (worse) 19073 B +432 B / +2.3% (worse) 957.740 ms -16.49 ms / -1.7% (better) 9.185 ms -590.3 us / -6.0% (better)
Windows MSVC 386 println-lto 32768 B +512 B / +1.6% (worse) 17239 B +384 B / +2.3% (worse) 1.154 s +5.046 ms / +0.4% (worse) 9.665 ms -464.2 us / -4.6% (better)
Windows MSVC ARM64 cprintf 11264 B 0 B / +0.0% 3976 B 0 B / +0.0% 1.064 s +11.09 ms / +1.1% (worse) 6.654 ms -237.1 us / -3.4% (better)
Windows MSVC ARM64 cprintf-lto 10752 B 0 B / +0.0% 3868 B 0 B / +0.0% 1.095 s +19.52 ms / +1.8% (worse) 6.769 ms +59.6 us / +0.9% (worse)
Windows MSVC ARM64 fmtprintf 1443328 B +70656 B / +5.1% (worse) 543924 B +42288 B / +8.4% (worse) 3.826 s +37.44 ms / +1.0% (worse) 13.835 ms +233.5 us / +1.7% (worse)
Windows MSVC ARM64 fmtprintf-lto 1480192 B +92672 B / +6.7% (worse) 520692 B +54128 B / +11.6% (worse) 9.467 s +910.3 ms / +10.6% (worse) 14.496 ms +903.1 us / +6.6% (worse)
Windows MSVC ARM64 println 43008 B +1024 B / +2.4% (worse) 22600 B +432 B / +1.9% (worse) 1.076 s -12.24 ms / -1.1% (better) 11.723 ms -127.6 us / -1.1% (better)
Windows MSVC ARM64 println-lto 39936 B +512 B / +1.3% (worse) 19756 B +400 B / +2.1% (worse) 1.255 s -18.75 ms / -1.5% (better) 11.772 ms -483.1 us / -3.9% (better)
Core language and compiler benchmarks
Platform Benchmark ns/op vs base
Linux BenchmarkLookupPCRandom 15.500 ns/op +0.71 ns/op / +4.8% (worse)
Linux BenchmarkMergeCompilerFlags 208.400 ns/op -9.7 ns/op / -4.4% (better)
Linux BenchmarkMergeLinkerFlags 130.700 ns/op -14 ns/op / -9.7% (better)
Linux BenchmarkChannelBuffered 55.210 ns/op -0.96 ns/op / -1.7% (better)
Linux BenchmarkChannelHandoff 13619 ns/op -1642 ns/op / -10.8% (better)
Linux BenchmarkDefer 60.320 ns/op -3 ns/op / -4.7% (better)
Linux BenchmarkDirectCall 1.172 ns/op -0.419 ns/op / -26.3% (better)
Linux BenchmarkGlobalRead 1.573 ns/op +0.273 ns/op / +21.0% (worse)
Linux BenchmarkGlobalWrite 8.509 ns/op +0.593 ns/op / +7.5% (worse)
Linux BenchmarkGoroutine 21660 ns/op -8474 ns/op / -28.1% (better)
Linux BenchmarkInterfaceCall 6.633 ns/op +0.377 ns/op / +6.0% (worse)
Linux BenchmarkRuntimeGetG 2.433 ns/op -0.235 ns/op / -8.8% (better)
macOS BenchmarkLookupPCRandom 12.800 ns/op +0.24 ns/op / +1.9% (worse)
macOS BenchmarkMergeCompilerFlags 103.600 ns/op -4.2 ns/op / -3.9% (better)
macOS BenchmarkMergeLinkerFlags 67.340 ns/op -1.14 ns/op / -1.7% (better)
macOS BenchmarkChannelBuffered 25.600 ns/op -0.49 ns/op / -1.9% (better)
macOS BenchmarkChannelHandoff 5039 ns/op -2345 ns/op / -31.8% (better)
macOS BenchmarkDefer 32.410 ns/op -0.08 ns/op / -0.2% (better)
macOS BenchmarkDirectCall 1.075 ns/op +0.032 ns/op / +3.1% (worse)
macOS BenchmarkGlobalRead 1.081 ns/op +0.018 ns/op / +1.7% (worse)
macOS BenchmarkGlobalWrite 1.069 ns/op +0.024 ns/op / +2.3% (worse)
macOS BenchmarkGoroutine 28325 ns/op +2848 ns/op / +11.2% (worse)
macOS BenchmarkInterfaceCall 3.977 ns/op +0.087 ns/op / +2.2% (worse)
macOS BenchmarkRuntimeGetG 2.181 ns/op +0.084 ns/op / +4.0% (worse)
Windows MinGW BenchmarkLookupPCRandom 10.090 ns/op -0.47 ns/op / -4.5% (better)
Windows MinGW BenchmarkMergeCompilerFlags 492.600 ns/op -2.7 ns/op / -0.5% (better)
Windows MinGW BenchmarkMergeLinkerFlags 434.800 ns/op +2.5 ns/op / +0.6% (worse)
Windows MinGW BenchmarkChannelBuffered 41.520 ns/op +2.22 ns/op / +5.6% (worse)
Windows MinGW BenchmarkChannelHandoff 1399 ns/op +327 ns/op / +30.5% (worse)
Windows MinGW BenchmarkDefer 51.370 ns/op +3.92 ns/op / +8.3% (worse)
Windows MinGW BenchmarkDirectCall 0.982 ns/op -0.098 ns/op / -9.1% (better)
Windows MinGW BenchmarkGlobalRead 0.997 ns/op +0.011 ns/op / +1.1% (worse)
Windows MinGW BenchmarkGlobalWrite 7.512 ns/op -0.005 ns/op / -0.1% (better)
Windows MinGW BenchmarkGoroutine 73653 ns/op +5474 ns/op / +8.0% (worse)
Windows MinGW BenchmarkInterfaceCall 6.507 ns/op +1.303 ns/op / +25.0% (worse)
Windows MinGW BenchmarkRuntimeGetG 1.246 ns/op +0.008 ns/op / +0.6% (worse)
Windows MinGW 386 BenchmarkLookupPCRandom 21.540 ns/op 0 ns/op / +0.0%
Windows MinGW 386 BenchmarkMergeCompilerFlags 577.900 ns/op +23.4 ns/op / +4.2% (worse)
Windows MinGW 386 BenchmarkMergeLinkerFlags 522.800 ns/op +5.4 ns/op / +1.0% (worse)
Windows MinGW 386 BenchmarkChannelBuffered 33.640 ns/op -0.25 ns/op / -0.7% (better)
Windows MinGW 386 BenchmarkChannelHandoff 769.100 ns/op +69.6 ns/op / +9.9% (worse)
Windows MinGW 386 BenchmarkDefer 34.610 ns/op -0.03 ns/op / -0.1% (better)
Windows MinGW 386 BenchmarkDirectCall 1.356 ns/op -0.002 ns/op / -0.1% (better)
Windows MinGW 386 BenchmarkGlobalRead 1.629 ns/op -0.001 ns/op / -0.1% (better)
Windows MinGW 386 BenchmarkGlobalWrite 6.984 ns/op -0.001 ns/op / -0.01432% (better)
Windows MinGW 386 BenchmarkGoroutine 59270 ns/op +871 ns/op / +1.5% (worse)
Windows MinGW 386 BenchmarkInterfaceCall 7.312 ns/op -0.014 ns/op / -0.2% (better)
Windows MinGW 386 BenchmarkRuntimeGetG 1.629 ns/op -0.007 ns/op / -0.4% (better)
Windows MinGW ARM64 BenchmarkLookupPCRandom 12.050 ns/op -0.03 ns/op / -0.2% (better)
Windows MinGW ARM64 BenchmarkMergeCompilerFlags 569.800 ns/op +3.6 ns/op / +0.6% (worse)
Windows MinGW ARM64 BenchmarkMergeLinkerFlags 532.700 ns/op -4.8 ns/op / -0.9% (better)
Windows MinGW ARM64 BenchmarkChannelBuffered 38.600 ns/op +0.97 ns/op / +2.6% (worse)
Windows MinGW ARM64 BenchmarkChannelHandoff 1830 ns/op +53 ns/op / +3.0% (worse)
Windows MinGW ARM64 BenchmarkDefer 52.800 ns/op -0.12 ns/op / -0.2% (better)
Windows MinGW ARM64 BenchmarkDirectCall 0.589 ns/op -0.0003 ns/op / -0.1% (better)
Windows MinGW ARM64 BenchmarkGlobalRead 0.663 ns/op +0.0732 ns/op / +12.4% (worse)
Windows MinGW ARM64 BenchmarkGlobalWrite 0.663 ns/op +0.0738 ns/op / +12.5% (worse)
Windows MinGW ARM64 BenchmarkGoroutine 54418 ns/op -439 ns/op / -0.8% (better)
Windows MinGW ARM64 BenchmarkInterfaceCall 4.237 ns/op +0.092 ns/op / +2.2% (worse)
Windows MinGW ARM64 BenchmarkRuntimeGetG 1.770 ns/op +0.001 ns/op / +0.1% (worse)
Windows MSVC BenchmarkLookupPCRandom 13.190 ns/op +0.32 ns/op / +2.5% (worse)
Windows MSVC BenchmarkMergeCompilerFlags 620.700 ns/op -0.5 ns/op / -0.1% (better)
Windows MSVC BenchmarkMergeLinkerFlags 536.700 ns/op -24.3 ns/op / -4.3% (better)
Windows MSVC BenchmarkChannelBuffered 28.610 ns/op +0.3 ns/op / +1.1% (worse)
Windows MSVC BenchmarkChannelHandoff 1184 ns/op +105 ns/op / +9.7% (worse)
Windows MSVC BenchmarkDefer 57.610 ns/op +1.78 ns/op / +3.2% (worse)
Windows MSVC BenchmarkDirectCall 1.547 ns/op -0.002 ns/op / -0.1% (better)
Windows MSVC BenchmarkGlobalRead 1.549 ns/op +0.001 ns/op / +0.1% (worse)
Windows MSVC BenchmarkGlobalWrite 2.468 ns/op -0.002 ns/op / -0.1% (better)
Windows MSVC BenchmarkGoroutine 80825 ns/op -1330 ns/op / -1.6% (better)
Windows MSVC BenchmarkInterfaceCall 8.721 ns/op +0.615 ns/op / +7.6% (worse)
Windows MSVC BenchmarkRuntimeGetG 2.174 ns/op +0.311 ns/op / +16.7% (worse)
Windows MSVC 386 BenchmarkLookupPCRandom 26.540 ns/op +0.05 ns/op / +0.2% (worse)
Windows MSVC 386 BenchmarkMergeCompilerFlags 771.800 ns/op +12.6 ns/op / +1.7% (worse)
Windows MSVC 386 BenchmarkMergeLinkerFlags 714.600 ns/op +74.7 ns/op / +11.7% (worse)
Windows MSVC 386 BenchmarkChannelBuffered 38.640 ns/op -0.41 ns/op / -1.0% (better)
Windows MSVC 386 BenchmarkChannelHandoff 875.700 ns/op +2.1 ns/op / +0.2% (worse)
Windows MSVC 386 BenchmarkDefer 50.870 ns/op +5.57 ns/op / +12.3% (worse)
Windows MSVC 386 BenchmarkDirectCall 1.549 ns/op -0.001 ns/op / -0.1% (better)
Windows MSVC 386 BenchmarkGlobalRead 1.553 ns/op 0 ns/op / +0.0%
Windows MSVC 386 BenchmarkGlobalWrite 7.781 ns/op -0.003 ns/op / -0.03854% (better)
Windows MSVC 386 BenchmarkGoroutine 86621 ns/op +529 ns/op / +0.6% (worse)
Windows MSVC 386 BenchmarkInterfaceCall 8.382 ns/op +0.015 ns/op / +0.2% (worse)
Windows MSVC 386 BenchmarkRuntimeGetG 2.170 ns/op -0.002 ns/op / -0.1% (better)
Windows MSVC ARM64 BenchmarkLookupPCRandom 12.080 ns/op +0.04 ns/op / +0.3% (worse)
Windows MSVC ARM64 BenchmarkMergeCompilerFlags 574.400 ns/op -3.4 ns/op / -0.6% (better)
Windows MSVC ARM64 BenchmarkMergeLinkerFlags 539.300 ns/op -8.6 ns/op / -1.6% (better)
Windows MSVC ARM64 BenchmarkChannelBuffered 38.350 ns/op +0.41 ns/op / +1.1% (worse)
Windows MSVC ARM64 BenchmarkChannelHandoff 2200 ns/op -144 ns/op / -6.1% (better)
Windows MSVC ARM64 BenchmarkDefer 59.160 ns/op -1.56 ns/op / -2.6% (better)
Windows MSVC ARM64 BenchmarkDirectCall 0.589 ns/op -0.0741 ns/op / -11.2% (better)
Windows MSVC ARM64 BenchmarkGlobalRead 0.663 ns/op +0.0722 ns/op / +12.2% (worse)
Windows MSVC ARM64 BenchmarkGlobalWrite 3.752 ns/op -0.001 ns/op / -0.02665% (better)
Windows MSVC ARM64 BenchmarkGoroutine 54450 ns/op +501 ns/op / +0.9% (worse)
Windows MSVC ARM64 BenchmarkInterfaceCall 4.252 ns/op +0.011 ns/op / +0.3% (worse)
Windows MSVC ARM64 BenchmarkRuntimeGetG 1.769 ns/op -0.028 ns/op / -1.6% (better)
Timer runtime benchmarks
Platform Operation and runtime ns/op vs base
Linux AfterFuncZeroDelivery/Go 928.500 ns/op -47.5 ns/op / -4.9% (better)
Linux AfterFuncZeroDelivery/LLGo 41401 ns/op -8062 ns/op / -16.3% (better)
Linux CreateStop/Go 317.100 ns/op -41.4 ns/op / -11.5% (better)
Linux CreateStop/LLGo 1724 ns/op -143 ns/op / -7.7% (better)
Linux RearmStopped/Go 117.100 ns/op -1.5 ns/op / -1.3% (better)
Linux RearmStopped/LLGo 1385 ns/op +210 ns/op / +17.9% (worse)
Linux ResetActive/Go 75.340 ns/op -5.93 ns/op / -7.3% (better)
Linux ResetActive/LLGo 726.900 ns/op -65.2 ns/op / -8.2% (better)
Linux ResetHeap1024/Go 67.460 ns/op -10.96 ns/op / -14.0% (better)
Linux ResetHeap1024/LLGo 185.900 ns/op -25.2 ns/op / -11.9% (better)
macOS AfterFuncZeroDelivery/Go 424.400 ns/op -7.3 ns/op / -1.7% (better)
macOS AfterFuncZeroDelivery/LLGo 58664 ns/op -4334 ns/op / -6.9% (better)
macOS CreateStop/Go 135.400 ns/op -1.9 ns/op / -1.4% (better)
macOS CreateStop/LLGo 405.300 ns/op +27.1 ns/op / +7.2% (worse)
macOS RearmStopped/Go 61.330 ns/op +4.76 ns/op / +8.4% (worse)
macOS RearmStopped/LLGo 325.500 ns/op +3.6 ns/op / +1.1% (worse)
macOS ResetActive/Go 45.050 ns/op +2.58 ns/op / +6.1% (worse)
macOS ResetActive/LLGo 164.700 ns/op +14.8 ns/op / +9.9% (worse)
macOS ResetHeap1024/Go 42.910 ns/op -0.12 ns/op / -0.3% (better)
macOS ResetHeap1024/LLGo 87.930 ns/op -0.47 ns/op / -0.5% (better)
Windows MinGW AfterFuncZeroDelivery/Go 595.700 ns/op -26.7 ns/op / -4.3% (better)
Windows MinGW AfterFuncZeroDelivery/LLGo 130572 ns/op +7855 ns/op / +6.4% (worse)
Windows MinGW CreateStop/Go 166 ns/op -2.3 ns/op / -1.4% (better)
Windows MinGW CreateStop/LLGo 899 ns/op +29.9 ns/op / +3.4% (worse)
Windows MinGW RearmStopped/Go 61.380 ns/op -1.46 ns/op / -2.3% (better)
Windows MinGW RearmStopped/LLGo 320.900 ns/op +6.7 ns/op / +2.1% (worse)
Windows MinGW ResetActive/Go 27.240 ns/op -0.84 ns/op / -3.0% (better)
Windows MinGW ResetActive/LLGo 158.600 ns/op -9.9 ns/op / -5.9% (better)
Windows MinGW ResetHeap1024/Go 27.200 ns/op -1.18 ns/op / -4.2% (better)
Windows MinGW ResetHeap1024/LLGo 118 ns/op +1.8 ns/op / +1.5% (worse)
Windows MinGW 386 AfterFuncZeroDelivery/Go 771.300 ns/op +8 ns/op / +1.0% (worse)
Windows MinGW 386 AfterFuncZeroDelivery/LLGo 126016 ns/op -650 ns/op / -0.5% (better)
Windows MinGW 386 CreateStop/Go 166.700 ns/op +1.2 ns/op / +0.7% (worse)
Windows MinGW 386 CreateStop/LLGo 385.900 ns/op +2 ns/op / +0.5% (worse)
Windows MinGW 386 RearmStopped/Go 56.780 ns/op +0.16 ns/op / +0.3% (worse)
Windows MinGW 386 RearmStopped/LLGo 275 ns/op -6.7 ns/op / -2.4% (better)
Windows MinGW 386 ResetActive/Go 32.580 ns/op +0.03 ns/op / +0.1% (worse)
Windows MinGW 386 ResetActive/LLGo 870.300 ns/op -36.4 ns/op / -4.0% (better)
Windows MinGW 386 ResetHeap1024/Go 32.870 ns/op +0.06 ns/op / +0.2% (worse)
Windows MinGW 386 ResetHeap1024/LLGo 149.100 ns/op -0.4 ns/op / -0.3% (better)
Windows MinGW ARM64 AfterFuncZeroDelivery/Go 661.600 ns/op -12.1 ns/op / -1.8% (better)
Windows MinGW ARM64 AfterFuncZeroDelivery/LLGo 131526 ns/op +13829 ns/op / +11.7% (worse)
Windows MinGW ARM64 CreateStop/Go 197.700 ns/op -0.3 ns/op / -0.2% (better)
Windows MinGW ARM64 CreateStop/LLGo 342.900 ns/op -12.8 ns/op / -3.6% (better)
Windows MinGW ARM64 RearmStopped/Go 70.550 ns/op -0.01 ns/op / -0.01417% (better)
Windows MinGW ARM64 RearmStopped/LLGo 251.400 ns/op +1.4 ns/op / +0.6% (worse)
Windows MinGW ARM64 ResetActive/Go 31.130 ns/op -0.05 ns/op / -0.2% (better)
Windows MinGW ARM64 ResetActive/LLGo 120.600 ns/op -2.8 ns/op / -2.3% (better)
Windows MinGW ARM64 ResetHeap1024/Go 31.170 ns/op -0.04 ns/op / -0.1% (better)
Windows MinGW ARM64 ResetHeap1024/LLGo 127 ns/op +0.1 ns/op / +0.1% (worse)
Windows MSVC AfterFuncZeroDelivery/Go 580.100 ns/op +11.5 ns/op / +2.0% (worse)
Windows MSVC AfterFuncZeroDelivery/LLGo 163468 ns/op +10250 ns/op / +6.7% (worse)
Windows MSVC CreateStop/Go 117.100 ns/op +1.1 ns/op / +0.9% (worse)
Windows MSVC CreateStop/LLGo 451.300 ns/op -20.2 ns/op / -4.3% (better)
Windows MSVC RearmStopped/Go 31.580 ns/op -0.05 ns/op / -0.2% (better)
Windows MSVC RearmStopped/LLGo 264.800 ns/op -9.5 ns/op / -3.5% (better)
Windows MSVC ResetActive/Go 20.170 ns/op -0.05 ns/op / -0.2% (better)
Windows MSVC ResetActive/LLGo 166.100 ns/op +19.7 ns/op / +13.5% (worse)
Windows MSVC ResetHeap1024/Go 20.610 ns/op +0.08 ns/op / +0.4% (worse)
Windows MSVC ResetHeap1024/LLGo 126.200 ns/op -1.2 ns/op / -0.9% (better)
Windows MSVC 386 AfterFuncZeroDelivery/Go 953.200 ns/op -2.6 ns/op / -0.3% (better)
Windows MSVC 386 AfterFuncZeroDelivery/LLGo 190272 ns/op +2352 ns/op / +1.3% (worse)
Windows MSVC 386 CreateStop/Go 195.700 ns/op -3.7 ns/op / -1.9% (better)
Windows MSVC 386 CreateStop/LLGo 465.100 ns/op -5.6 ns/op / -1.2% (better)
Windows MSVC 386 RearmStopped/Go 63.200 ns/op -0.27 ns/op / -0.4% (better)
Windows MSVC 386 RearmStopped/LLGo 316.100 ns/op -19.2 ns/op / -5.7% (better)
Windows MSVC 386 ResetActive/Go 39.030 ns/op -0.13 ns/op / -0.3% (better)
Windows MSVC 386 ResetActive/LLGo 941.200 ns/op +35.7 ns/op / +3.9% (worse)
Windows MSVC 386 ResetHeap1024/Go 39.490 ns/op 0 ns/op / +0.0%
Windows MSVC 386 ResetHeap1024/LLGo 170.100 ns/op -2 ns/op / -1.2% (better)
Windows MSVC ARM64 AfterFuncZeroDelivery/Go 671.100 ns/op +11.9 ns/op / +1.8% (worse)
Windows MSVC ARM64 AfterFuncZeroDelivery/LLGo 121901 ns/op -2568 ns/op / -2.1% (better)
Windows MSVC ARM64 CreateStop/Go 196.800 ns/op -0.5 ns/op / -0.3% (better)
Windows MSVC ARM64 CreateStop/LLGo 383.900 ns/op +8.6 ns/op / +2.3% (worse)
Windows MSVC ARM64 RearmStopped/Go 70.570 ns/op -0.06 ns/op / -0.1% (better)
Windows MSVC ARM64 RearmStopped/LLGo 271.400 ns/op +1.8 ns/op / +0.7% (worse)
Windows MSVC ARM64 ResetActive/Go 31.090 ns/op +0.03 ns/op / +0.1% (worse)
Windows MSVC ARM64 ResetActive/LLGo 138.500 ns/op +2.7 ns/op / +2.0% (worse)
Windows MSVC ARM64 ResetHeap1024/Go 31.120 ns/op +0.02 ns/op / +0.1% (worse)
Windows MSVC ARM64 ResetHeap1024/LLGo 136.200 ns/op -4.6 ns/op / -3.3% (better)

Compared with 130974dadec6 measured in the same runner job.

@visualfc
visualfc force-pushed the experiment/finalizer-typed-wrapper branch 5 times, most recently from bcc4634 to 02d2ba3 Compare September 7, 2026 12:10
@visualfc

visualfc commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed a follow-up for the Windows 386 TestRuntimeSetFinalizerCancel failure.

Capturing closures stay on the boxed SetFinalizer path, but SetFinalizer(nil) is always lowered to SetFinalizerPtr. If the typed table is empty, cancel now also drops the boxed BDWGC leftover so the canceled finalizer cannot run, without discarding an AddCleanup chain.

@visualfc
visualfc force-pushed the experiment/finalizer-typed-wrapper branch 7 times, most recently from 0e5243f to 3e76f87 Compare September 11, 2026 06:04

@cpunion cpunion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I verified that the size-reduction direction is real, but the current implementation is not safe to merge yet. The inline comments contain four deterministic correctness failures reproduced against this head and one material build-time issue. The current tests miss the named-interface-finalizer and higher-order MakeFunc paths. This branch is also 42 commits behind current main and overlaps the recent runtime cleanup/reentrancy work, so it needs a rebase and full retest after the correctness fixes. I recommend separating typed SetFinalizer lowering/runtime support from automatic noffi reflect/method-value selection if that makes the invariants and review surface easier to establish.

Comment thread ssa/expr.go Outdated
wb := wrapper.MakeBody(1)
converted := wb.Convert(b.Prog.rawType(ptrType), wb.Param(0))
if !types.Identical(argType, ptrType) {
converted = wb.Convert(b.Prog.rawType(argType), converted)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Blocking: setFinalizerArgCompatible explicitly accepts empty and implemented interfaces, but this is a pointer-to-interface conversion and Builder.Convert does not construct an interface value. A named finalizer such as func finalize(v interface{ Value() int }) passed to runtime.SetFinalizer(p, finalize) reliably aborts compilation with package job ... panicked: todo; named func(any) has the same problem. The existing assignability tests use capturing closures, so they remain on the boxed/libffi path and never exercise this wrapper. Please use the proper interface-boxing operation here and add named-function compile-and-run cases for both any and a non-empty implemented interface.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. Interface arguments now go through MakeInterface instead of Convert. Named func(any) and func(interface{ Value() int }) compile-and-run cases are in TestRuntimeSetFinalizerNamedInterfaceFunctions.

Comment thread ssa/expr.go Outdated
pkg := b.Pkg
if !pkg.NeedFFI {
switch fn.Name() {
case "reflect.Value.Call", "reflect.Value.CallSlice", "reflect.MakeFunc":

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Blocking: this detection only observes direct vkFuncDecl calls. A higher-order call such as invokeMakeFunc(reflect.MakeFunc), where invokeMakeFunc accepts the function as a value and calls it, never reaches checkFFI because the vkClosure path returns earlier. With -lto=full, automatic selection then omits libffi and the resulting program panics with reflect.MakeFunc is unavailable without libffi. Any value/address escape of an FFI-sensitive symbol must conservatively set NeedFFI, or the decision must use real post-lowering reachability. Please add this exact higher-order case as a regression test.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. checkFFI now also runs when an FFI-sensitive function escapes as a value (vkFuncDecl → closure in checkExpr, and MakeClosure). The higher-order case invokeMakeFunc(reflect.MakeFunc) is covered by TestMakeFuncValueRequiresFFI / TestCheckFFIMarksHigherOrderMakeFunc.

fv := &struct {
fn unsafe.Pointer
env unsafe.Pointer
}{fn, unsafe.Pointer(env)}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Blocking: fn is the real method entry, which expects the receiver as its ordinary first ABI argument, while env is supplied as the hidden closure context. Calling reflect.ValueOf(&T{}).MethodByName("M").Interface().(func(int) int)(2) does not pass through reflect.Value.call to decode directMethodValueEnv; it jumps to fn with the wrong ABI. I reproduced a nil-pointer panic with automatic noffi, while the same program built with -check-libffi=false correctly prints 42. This representation needs an ABI-correct thunk that extracts the receiver from env and calls the method with it as the ordinary receiver argument.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. Each method now gets an ABI-correct $methodvalue thunk that loads the receiver from the hidden env and calls the real method with it as the ordinary first argument. Thunk pointers live in a side array after the method table (abi.Method is unchanged). MethodByName("M").Interface().(func(int) int)(2) is covered by TestReflectValueMethodByNameInterfaceCall.

Comment thread runtime/internal/runtime/z_gc.go Outdated
}
var ignoredFn bdwgc.FinalizerFunc
var ignoredCb unsafe.Pointer
bdwgc.RegisterFinalizer(obj, old.prevFn, old.prevCb, &ignoredFn, &ignoredCb)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Blocking: this restores the callback snapshot captured when the typed finalizer was registered. If runtime.AddCleanup installs a newer callback chain afterward, restoring old.prevFn overwrites and discards that chain. For SetFinalizer(p, typedFinalizer); AddCleanup(p, cleanup, arg); SetFinalizer(p, nil), official Go reports cleanups 8 old 0, while this head consistently reports cleanups 0 old 0. Cancellation must preserve callbacks installed after the typed entry, and this ordering needs a runtime regression test.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. Cancellation now probes the current BDWGC slot: if a later AddCleanup chain owns it, that chain is put back instead of restoring the registration-time snapshot. Stopped typed entries also forward prevFn so earlier cleanups are not dropped. Regression: TestRuntimeSetFinalizerNamedCancelAfterCleanup (SetFinalizer(named) → AddCleanup → SetFinalizer(nil)).

Comment thread internal/build/build.go
restarted.Tags = appendBuildTag(restarted.Tags, "llgo_noffi")
restarted.Tags = appendBuildTag(restarted.Tags, "llgo_methodvalue_noffi")
restarted.noFFIRestart = true
return Build(Invocation{Args: inv.Args, Config: restarted, Dir: dir,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This recursively rebuilds the complete package graph after the first build has already finished. The benchmark attached to this PR shows full-LTO fmtprintf build-time regressions of +23.0% on Linux, +40.0% on macOS, and roughly +30% to +35% on Windows, for about 1.4% to 3.2% file-size reduction. Please select the mode before the expensive backend work or reuse the first-pass results; at minimum this cost should be documented explicitly and the default-on policy reconsidered.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. CheckFFI now does a frontend-only scan of ordinary packages and, if libffi is not needed, reloads with llgo_noffi before any package LLVM backend or LTO. -lto=full no longer enables this by default; it is opt-in via -check-libffi=true.

@visualfc
visualfc force-pushed the experiment/finalizer-typed-wrapper branch from 3e76f87 to 6a37811 Compare September 14, 2026 22:39
@visualfc

Copy link
Copy Markdown
Collaborator Author

@cpunion Thanks for the review. The four correctness failures and the CheckFFI rebuild cost are addressed on the current head:

  • Named any / implemented-interface finalizers use MakeInterface and have compile-and-run tests.
  • Higher-order invokeMakeFunc(reflect.MakeFunc) now marks NeedFFI.
  • Noffi method values use ABI thunks; MethodByName().Interface() is covered.
  • SetFinalizer(typed); AddCleanup; SetFinalizer(nil) keeps the later cleanup chain.
  • CheckFFI is a frontend-only scan, then one backend/LTO; -lto=full no longer turns it on by default (-check-libffi=true is opt-in).

@visualfc
visualfc force-pushed the experiment/finalizer-typed-wrapper branch 5 times, most recently from 37e1856 to cafdd47 Compare September 21, 2026 07:55
@visualfc visualfc changed the title cl, runtime, reflect: support typed SetFinalizer and noffi method values [WIP] cl, runtime, reflect: support typed SetFinalizer and noffi method values Sep 21, 2026
Lower compatible runtime.SetFinalizer calls to SetFinalizerPtr with an
internal typed wrapper. Keep the boxed path for capturing closures and
for runtimes that omit SetFinalizerPtr. Preserve cleanup chains when a
typed finalizer is canceled or replaced.
Split libffi-backed reflect.Call/MakeFunc from the noffi variants, add
ABI-correct $methodvalue thunks for MethodByName().Interface(), and keep
Windows NewCallback unavailable when llgo_noffi is set.
Export method-value thunks for deadcode drop, skip va_list shapes, and
use Go64 storage on wasm32 so noffi method values keep the receiver in
the ordinary argument slot.
When -check-libffi=true, scan the already-built Go SSA with RTA from
init/main (or all functions for libraries and tests). Restart once with
llgo_noffi if Call, MakeFunc, Windows NewCallback, or unlowered
SetFinalizer are unreachable. LLVM backend and LTO still run once.
The flag stays off by default, including with -lto=full.
WASI has no host libffi and uses typed reflection bridges. Restarting
with llgo_noffi would drop Call/MakeFunc instead of selecting a libffi
variant, so -check-libffi is ignored for wasip1.
llgo_noffi and llgo_methodvalue_noffi use ABI method-value thunks
instead of llgo_reflect_bind*_js. Those ASYNCIFY_REMOVE patterns do
not match, so omit them (and ffi_call_js) when compiling without libffi.
Wasm package IR is compiled with clang/emcc -c. Without -flto, unused
$methodvalue thunks survive linking, so js/wasm -check-libffi binaries
grew. Emit bitcode so -lto=full can DCE them.
Exercise the Codecov-missing branches: thunk-array DCE, wasm32 wide
pointer array storage, named-pointer SetFinalizer wrappers, Windows
NewCallback libffi detection, and the verbose -check-libffi restart.
Analyze wasm initial packages through programUse.
@visualfc
visualfc force-pushed the experiment/finalizer-typed-wrapper branch from a02491a to 5cc9cac Compare September 22, 2026 02:59
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.

2 participants