Skip to content

runtime: restore native process-entry caller tail - #2314

Open
cpunion wants to merge 4 commits into
xgo-dev:mainfrom
cpunion:fix-caller-logical-tail
Open

runtime: restore native process-entry caller tail#2314
cpunion wants to merge 4 commits into
xgo-dev:mainfrom
cpunion:fix-caller-logical-tail

Conversation

@cpunion

@cpunion cpunion commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add a startup-only runtime.main frame for native executables and map the required C main entry to the logical Go name runtime.goexit;
  • publish both generated frames through the existing funcinfo and entry-site pipeline, without changing the PCLN data layout or adding per-Caller synthesis;
  • keep Wasm and builds without PCLN on the existing direct entry path;
  • remove the inline_caller.go and fixedbugs/issue29919.go xfails.

The actual native symbols remain main.main -> @runtime.main -> @main. Funcinfo presents the final C ABI entry as runtime.goexit, so Go stack inspection observes the gc-compatible logical tail main.main -> runtime.main -> runtime.goexit.

Generated LLVM IR shape

Previously, the C ABI entry directly ran the complete startup sequence:

define i32 @main(i32 %argc, ptr %argv) {
  store i32 %argc, ptr @__llgo_argc
  store ptr %argv, ptr @__llgo_argv
  call void @Py_Initialize()
  call void @"runtime.init"()
  call void @"main.init"()
  call void @"main.main"()
  call void @Py_Finalize()
  ret i32 0
}

The native PCLN path now separates the C entry from the Go startup driver:

define i32 @main(i32 %argc, ptr %argv) {
  ; EnterLocalContext when required
  store i32 %argc, ptr @__llgo_argc
  store ptr %argv, ptr @__llgo_argv
  call void @runtime.main()
  ; LeaveLocalContext when required
  ret i32 0
}

define void @runtime.main() #attrs {
  call void @Py_Initialize()
  call void @"runtime.init"()
  call void @"main.init"()
  call void @"main.main"()
  call void @Py_Finalize()
  ret void
}

attributes #attrs = { noinline "disable-tail-calls"="true" }

Package and ABI initialization calls omitted above remain in their previous order. noinline and disable-tail-calls ensure LLVM cannot remove the required runtime.main frame.

The generated funcinfo mappings are:

physical @runtime.main -> logical runtime.main
physical @main         -> logical runtime.goexit

The linker-facing symbol is still the required C main; this PR does not generate a separate physical @runtime.goexit function.

Runtime impact

  • Initialization, main.main, Python initialization/finalization, argc/argv handling, and local-context ordering are unchanged.
  • A native process enters one additional non-inlined wrapper call. It remains below package initialization and main.main, then returns once when startup execution finishes.
  • runtime.Caller, runtime.CallersFrames, and panic tracebacks now see the Go-compatible process-entry tail, including during imported-package initialization.
  • The cost is one call/return per process plus two static funcinfo/entry-site records. There is no additional work on ordinary function calls and no per-Caller or per-traceback synthesis.
  • The change applies only to non-Wasm executable builds with PCLN enabled. Wasm, builds without PCLN, and library build modes retain the direct entry path.

Validation

  • go test ./internal/build -run TestGenMainModule -count=1
  • go test -vet=off ./test/go -run TestCallerAcceptanceLogicalRuntimeTail -count=1 -timeout=20m
  • direct Go 1.26.5 GOROOT run of inline_caller.go without xfail on macOS/arm64
  • direct Go 1.26.5 GOROOT rundir of fixedbugs/issue29919.go without xfail on macOS/arm64
  • go test ./test/goroot -run "TestRepositoryExpectationsAreSeparated|TestXFailMatch" -count=1

@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 summary

This is a clean, well-motivated change: extracting emitRuntimeMainBody cleanly shares the init/main call sequence between the native runtime.main wrapper and the inline non-native path, and TestGenMainModuleExecutable pins the call ordering so the non-native path's behavior is verified. The cross-check of the same module under gc (go run) before LLGo in TestCallerAcceptanceLogicalRuntimeTail directly validates the gc-compatibility goal, and the removed xfail.yaml entries are consistent with the new capability.

No security or performance concerns were found: the extra runtime.main frame runs exactly once at startup (not a hot path), and the append(funcInfo, ...) is safe today because cfg.funcInfo is a fresh single-owner slice from collectFuncInfo.

Findings below are maintainability/accuracy observations, not blockers. See inline comments for the concrete diff-line findings.

Additional (no reliable inline location)

  • internal/build/main_module.go:267-273 (defineEntryFunction doc comment) — The doc comment is now stale for the native path. It states the entry "runs initialization hooks (Python, runtime, package init), calls main.main, finalizes Python ... and returns 0", but for native executables with PCLN enabled those steps now run inside the generated runtime.main frame (defineRuntimeMainFunctionemitRuntimeMainBody), not directly in the entry. The comment also omits the runtime.init stub and abi-types init that emitRuntimeMainBody actually calls. Consider a sentence describing the two-path structure (inline vs. wrapped in runtime.main).

View job run

Comment thread internal/build/main_module.go Outdated
Comment thread internal/build/main_module_test.go Outdated
@cpunion

cpunion commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the additional doc-comment review in 21ff1e0. defineEntryFunction now describes the wrapped native-PCLN path versus the inline path and lists the runtime stub/package, ABI-type, package, main, and Python-finalization stages.

@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.96970% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/build/main_module.go 96.96% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@cpunion cpunion added the go-test-compat Go standard-library and GOROOT test compatibility label Aug 13, 2026
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

LLGo baseline benchmarks

d776b533a663 | workflow run | long-term charts

Program measurements

Platform Workload File size vs base Build vs base Run vs base
Linux cprintf 19384 B +0.7% (worse) 361.989 ms +0.6% (worse) 1.358 ms +3.4% (worse)
Linux fmtprintf 1880136 B +0.0% (worse) 2.927 s +2.1% (worse) 4.369 ms +2.6% (worse)
Linux println 68760 B +0.2% (worse) 349.571 ms -4.6% (better) 1.703 ms -0.3% (better)
macOS cprintf 84672 B +0.1% (worse) 362.639 ms -17.3% (better) 2.508 ms -26.7% (better)
macOS fmtprintf 1891456 B +0.0% (worse) 2.223 s -24.5% (better) 10.547 ms -15.9% (better)
macOS println 121232 B +0.1% (worse) 384.566 ms -4.0% (better) 4.798 ms +10.9% (worse)
Core language and compiler benchmarks
Platform Benchmark ns/op vs base
Linux BenchmarkLookupPCRandom 13.330 ns/op -0.1% (better)
Linux BenchmarkMergeCompilerFlags 151.500 ns/op +0.3% (worse)
Linux BenchmarkMergeLinkerFlags 95.260 ns/op +0.9% (worse)
Linux BenchmarkChannelBuffered 33.700 ns/op -4.3% (better)
Linux BenchmarkChannelHandoff 27084 ns/op -5.2% (better)
Linux BenchmarkDefer 45.690 ns/op -4.0% (better)
Linux BenchmarkDirectCall 1.557 ns/op +0.1% (worse)
Linux BenchmarkGlobalRead 1.558 ns/op +0.0%
Linux BenchmarkGlobalWrite 2.482 ns/op -0.2% (better)
Linux BenchmarkGoroutine 32192 ns/op -0.6% (better)
Linux BenchmarkInterfaceCall 8.095 ns/op +4.0% (worse)
Linux BenchmarkRuntimeGetG 1.869 ns/op -25.0% (better)
macOS BenchmarkLookupPCRandom 10.650 ns/op -31.0% (better)
macOS BenchmarkMergeCompilerFlags 108.500 ns/op -17.7% (better)
macOS BenchmarkMergeLinkerFlags 64.840 ns/op -19.9% (better)
macOS BenchmarkChannelBuffered 20.930 ns/op -12.6% (better)
macOS BenchmarkChannelHandoff 6571 ns/op +32.1% (worse)
macOS BenchmarkDefer 28.320 ns/op -22.3% (better)
macOS BenchmarkDirectCall 1.039 ns/op -18.7% (better)
macOS BenchmarkGlobalRead 1.225 ns/op +7.4% (worse)
macOS BenchmarkGlobalWrite 1.077 ns/op -3.7% (better)
macOS BenchmarkGoroutine 48501 ns/op +73.2% (worse)
macOS BenchmarkInterfaceCall 5.704 ns/op -12.2% (better)
macOS BenchmarkRuntimeGetG 2.069 ns/op -2.0% (better)

Compared with 7d9f052513f2 measured in the same runner job.

@cpunion
cpunion force-pushed the fix-caller-logical-tail branch 3 times, most recently from 818a125 to 91f21d3 Compare August 16, 2026 22:39
@cpunion
cpunion force-pushed the fix-caller-logical-tail branch from 91f21d3 to d776b53 Compare August 17, 2026 07:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

go-test-compat Go standard-library and GOROOT test compatibility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant