feat: support CGO_ENABLED=1 alongside CGO_ENABLED=0 (closes #13)#37
Conversation
|
Thank you for a well-structured PR. Code review and deep research analysis are complete. Review SummaryThe PR is structurally sound, follows purego's established CI Failure — Not Your CodeThe lint failure is a pre-existing issue in One Verification QuestionOur analysis shows that under Question: Did you test MaintainershipIf we merge this, would you be willing to be the ongoing maintainer for the Minor Suggestions (non-blocking)
Overall: excellent work. Clean, isolated, well-documented. Looking forward to CI green + your confirmation on callbacks. |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
@jiyeyuran One more note on commit This commit touches files outside the CGO_ENABLED=1 scope — Specifically, three concerns: 1.
|
Address PR go-webgpu#37 reviewer feedback by adding an explicit end-to-end exercise of the C-thread -> trampoline -> crosscall2 -> Go closure path. The new test uses goffi's own FFI to call pthread_create with a Go callback as the start_routine, then pthread_join to observe the callback's return value. The assertion is identical in both cgo modes; only the source of crosscall2 differs (internal/fakecgo vs runtime/cgo). Verified locally on darwin/arm64, Go 1.26.1: CGO_ENABLED=0 go test -run TestCallback_FromCThread -count=20 PASS CGO_ENABLED=1 go test -run TestCallback_FromCThread -count=20 PASS Also acts on the two non-blocking comment suggestions: - internal/dl/cgo.go: spell out that the runtime/cgo blank import is what makes our cgo_import_dynamic directives effective under CGO_ENABLED=1, by activating the external linker. - internal/dl/dl_linux.go: note that on glibc >= 2.34 libdl.so.2 is a stub mapping to libc.so.6, and that we still target libdl.so.2 on purpose for backwards compatibility with older glibc and musl. No production-code behaviour changes. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Thanks for the careful review! All three items addressed in commit c260888 (rebased on top of the merge from main): 1. Verification — callback from a C-created thread under
|
|
@jiyeyuran Thank you for the thorough callback verification and the maintainership commitment — both are exactly what we needed. The It looks like our messages crossed — your reply addressed the first review comment (callbacks, maintainership, minor suggestions), but the second comment about commit |
) Lets goffi co-exist in binaries that already link other CGO bindings (gocv, ffmpeg-go wrappers, libavcodec, etc.) by making CGO_ENABLED=1 a fully supported build mode while keeping the existing CGO_ENABLED=0 / fakecgo path 100% unchanged (the primary path used by the gogpu ecosystem). What changes ------------ internal/dl - dl_unix.go and dl_{stubs,wrappers}_{unix,arm64}.s: drop the `!cgo` constraint so the goffi-owned dlopen/dlsym implementation compiles in both cgo modes. The runtime.cgocall it uses works under CGO_ENABLED=1 as long as runtime/cgo is linked (see cgo.go below). - dl_{darwin,linux,freebsd}.go: fold the cgo_import_dynamic directives in from the deleted dl_*_nocgo.go siblings; the directives are valid under both cgo modes, so the `!cgo` split is no longer needed. - cgo.go (new, `cgo` only): `import _ "runtime/cgo"` so iscgo=true and runtime.cgocall is wired up. Without this we hit "fatal error: cgocall unavailable" in CGO_ENABLED=1 mode. internal/syscall - cgo.go (new, `cgo` only): same `import _ "runtime/cgo"` trick. The arch packages depend on internal/syscall (not internal/dl), so the dl import alone doesn't drag runtime/cgo into binaries that only use the syscall layer (e.g. internal/arch/arm64 unit tests). internal/fakecgo (defensive gating, also addresses go-webgpu#22 root cause) - asm_amd64.s, asm_arm64.s: add the same `!cgo && (darwin || freebsd || linux || netbsd)` build tag the rest of the package already has. Without it the `crosscall2` symbol could collide with runtime/cgo's own crosscall2 in cgo mode. - netbsd.go: tag was previously just `netbsd`, missing `!cgo`. Aligned with every other file in the package. internal/arch/arm64 - abi_capture_test.go: drop the unconditional `_ "internal/fakecgo"` import; under CGO_ENABLED=1 fakecgo has zero Go files and the import fails with "build constraints exclude all Go files". - fakecgo_test.go (new, `arm64 && !cgo`): hosts the fakecgo blank import so it stays in the test binary in CGO_ENABLED=0 mode only. ffi - cgo_unsupported.go: removed. Its sole purpose was to make CGO=1 builds panic, which is no longer the desired behaviour now that CGO=1 is supported. - dl_unix.go, dl_darwin.go: drop the `!cgo` constraint so LoadLibrary / GetSymbol / FreeLibrary are available under CGO=1 too. CI (.github/workflows/ci.yml) - test job: add a `cgo: ['0', '1']` matrix dimension so every OS runs both cgo modes. Coverage artefacts disambiguated as `coverage-<os>-cgo<0|1>`. Codecov upload still pinned to ubuntu-latest + cgo=0 to keep history compatible. - Quality-gate parsing updated for the new artefact name. - Removed the stale "race detector not supported" comment block; the cgo=1 matrix entry now makes the race detector feasible if someone wants to layer it on later. Verification ------------ Local (darwin/arm64, Go 1.26.1): CGO_ENABLED=0 go build ./... PASS CGO_ENABLED=1 go build ./... PASS CGO_ENABLED=0 go test ./ffi ./types ./internal/... PASS CGO_ENABLED=1 go test ./ffi ./types ./internal/... PASS (10/10 stable) Cross-compile CGO=0 on linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64, windows/arm64, freebsd/amd64 PASS Notes ----- - CGO_ENABLED=0 is still the primary path. fakecgo is still gated by `!cgo` everywhere (the changes here only *complete* the gating that was already present on most files), and the existing nofakecgo build tag keeps working. - No new dependencies. Only stdlib `runtime/cgo` is pulled in, and only under the `cgo` build tag. Co-authored-by: Cursor <cursoragent@cursor.com>
Update the Requirements section to reflect that goffi now works under both CGO_ENABLED=0 (the default, fakecgo path) and CGO_ENABLED=1 (real runtime/cgo path), and explain how each mode supplies the cgo machinery. CGO_ENABLED=0 remains the primary, recommended mode for the gogpu ecosystem. Also note that building with CGO_ENABLED=1 is an alternative workaround for the goffi+purego duplicate-symbol issue (go-webgpu#22): in cgo mode both libraries' internal/fakecgo packages are gated out by //go:build !cgo, so there is nothing to collide. Co-authored-by: Cursor <cursoragent@cursor.com>
Address PR go-webgpu#37 reviewer feedback by adding an explicit end-to-end exercise of the C-thread -> trampoline -> crosscall2 -> Go closure path. The new test uses goffi's own FFI to call pthread_create with a Go callback as the start_routine, then pthread_join to observe the callback's return value. The assertion is identical in both cgo modes; only the source of crosscall2 differs (internal/fakecgo vs runtime/cgo). Verified locally on darwin/arm64, Go 1.26.1: CGO_ENABLED=0 go test -run TestCallback_FromCThread -count=20 PASS CGO_ENABLED=1 go test -run TestCallback_FromCThread -count=20 PASS Also acts on the two non-blocking comment suggestions: - internal/dl/cgo.go: spell out that the runtime/cgo blank import is what makes our cgo_import_dynamic directives effective under CGO_ENABLED=1, by activating the external linker. - internal/dl/dl_linux.go: note that on glibc >= 2.34 libdl.so.2 is a stub mapping to libc.so.6, and that we still target libdl.so.2 on purpose for backwards compatibility with older glibc and musl. No production-code behaviour changes. Co-authored-by: Cursor <cursoragent@cursor.com>
c260888 to
1dbf74e
Compare
|
Force-pushed a linearized branch that drops Branch is now exactly three focused commits on top of Reverted/dropped from this PR (no longer present anywhere in the diff):
|
kolkov
left a comment
There was a problem hiding this comment.
LGTM. All review criteria met: CI green (CGO=0/1 × 3 OS), callbacks verified from C-threads, core FFI untouched, clean 3-commit scope. Welcome aboard as CGO path maintainer.
Closes #13. Also covers the duplicate-symbol root cause referenced in #22.
Summary
Make
CGO_ENABLED=1a fully supported build mode for goffi while keeping the existingCGO_ENABLED=0/ fakecgo path byte-identical for the gogpu ecosystem. Lets goffi co-exist in binaries that already link other CGO bindings (gocv, libavcodec wrappers, etc.).This builds on the dl-layer dynamic-loading sketch in 44fb57f and completes the missing pieces called out in the review:
internal/fakecgo/with//go:build !cgo" is finished off (asm files +netbsd.gowere the gaps).runtime/cgois now reliably linked under cgo mode through bothinternal/dlandinternal/syscall, soruntime.cgocallworks regardless of which package the import chain enters from.ffi/cgo_unsupported.go(the panic-on-CGO=1 blocker) is removed, since CGO=1 is now supported.cgo: ['0', '1']dimension on Linux/Windows/macOS.What changed
internal/dl— make work in both cgo modesdl_unix.goand the fourdl_{stubs,wrappers}_{unix,arm64}.sfiles: drop the!cgobuild tag. The implementation only depends onruntime.cgocall, which works under both modes onceruntime/cgois wired up.dl_{darwin,linux,freebsd}.go: fold thecgo_import_dynamicdirectives in from the now-deleteddl_*_nocgo.gosiblings. The directives are valid under both cgo modes, so the_nocgosplit is no longer needed.internal/dl/cgo.go(cgoonly):import _ "runtime/cgo"soiscgo=trueandruntime.cgocallis initialised. Without this, CGO=1 builds hitfatal error: cgocall unavailable.internal/syscall— same wiring at the lower layerinternal/syscall/cgo.go(cgoonly):import _ "runtime/cgo". Required because theinternal/arch/*packages depend oninternal/syscallonly — theinternal/dlimport alone wouldn't be enough for binaries that exercise the syscall layer directly (e.g. arm64 unit tests).internal/fakecgo— finish the!cgogating (root cause shared with #22)asm_amd64.s,asm_arm64.s: add the!cgo && (darwin || freebsd || linux || netbsd)build tag the rest of the package already has. Without it,crosscall2could collide withruntime/cgo's owncrosscall2under cgo mode.netbsd.go: build tag was justnetbsd, missing!cgo. Aligned with every other file in the package.internal/arch/arm64— test isolationabi_capture_test.go: drop the unconditional_ "internal/fakecgo"import. Under CGO=1 fakecgo has zero Go files and the import fails withbuild constraints exclude all Go files.fakecgo_test.go(arm64 && !cgo): hosts the fakecgo blank import for the no-cgo test build only.fficgo_unsupported.goremoved. Its only role was to make CGO=1 builds panic, which is no longer the desired behaviour.dl_unix.go,dl_darwin.go: drop the!cgobuild tag soLoadLibrary/GetSymbol/FreeLibraryare available under CGO=1 too.README.mdCGO_ENABLED=0remains the recommended default.CGO_ENABLED=1as an alternative workaround (the realruntime/cgomakes both projects'fakecgopackages drop out via//go:build !cgo).CI (
.github/workflows/ci.yml)testjob: addcgo: ['0', '1']matrix dimension on every OS (Linux/Windows/macOS).coverage-<os>-cgo<0|1>.quality-gateparsing updated for the new artefact name.Compliance with the review checklist
internal/fakecgo/gated with//go:build !cgonetbsd.gowere the gaps)!cgo-gated code paths untouched in CGO=0 modeCGO_ENABLED=1jobsruntime/cgo, only undercgobuild tagdl_freebsd.goupdated symmetrically; ARM64 dual-mode tests pass; cross-compile verified for all 7 targetsinternal/arch/{amd64,arm64}core,types/, orffi/{call,cif,classification}.goTest plan
Local (darwin/arm64, Go 1.26.1):
Cross-compile (CGO_ENABLED=0):
-gcflags=...fakecgo=-std)Reviewer checklist for CI:
Test - <os> (cgo=<0|1>)jobs are greenNotes / non-goals
nofakecgobuild tag (link: duplicated definition of symbol _cgo_init #22) is unchanged; users who pull in purego in the same binary can still use it. This PR adds a second escape hatch: building withCGO_ENABLED=1sidesteps the duplicate symbols entirely.