diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b308c0..954202a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -395,10 +395,68 @@ jobs: echo "✅ All coverage thresholds met" echo "🎯 Production platforms (AMD64): 85%+ coverage" + # ELF linking contract: default dynamic FFI vs -tags goffi_static + # (gogpu#474 / goffi#74). Runs on Linux so file(1)/readelf are available. + elf-linking: + name: ELF Linking (linux/${{ matrix.arch }}) + runs-on: ubuntu-latest + needs: [lint, formatting] + strategy: + fail-fast: false + matrix: + arch: [amd64, arm64] + env: + CGO_ENABLED: "0" + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + cache: true + + - name: Build probe binaries + run: | + mkdir -p /tmp/goffi-elf + cat > /tmp/goffi-elf/main.go <<'EOF' + package main + + import ( + "fmt" + + "github.com/go-webgpu/goffi/ffi" + ) + + func main() { + _, err := ffi.LoadLibrary("libc.so.6") + fmt.Println(err) + } + EOF + # Strip leading indentation from the heredoc body for valid Go. + sed -i 's/^ //' /tmp/goffi-elf/main.go + cd /tmp/goffi-elf + go mod init goffielfprobe + go mod edit -replace=github.com/go-webgpu/goffi="${{ github.workspace }}" + go get github.com/go-webgpu/goffi@v0.0.0 + GOARCH=${{ matrix.arch }} go build -o default . + GOARCH=${{ matrix.arch }} go build -tags goffi_static -o static . + + - name: Assert dynamic vs static ELF + run: | + sudo apt-get update -qq && sudo apt-get install -y -qq binutils + "${{ github.workspace }}/scripts/check-elf-linking.sh" --dynamic /tmp/goffi-elf/default + "${{ github.workspace }}/scripts/check-elf-linking.sh" --static /tmp/goffi-elf/static + + - name: Static-profile unit tests + if: matrix.arch == 'amd64' + run: go test -tags goffi_static ./ffi -count=1 -run 'StaticBuild' + # Final status - All checks passed ci-success: name: CI Success - needs: [lint, formatting, cross-compile, android-cross, test, benchmarks, quality-gate] + needs: [lint, formatting, cross-compile, android-cross, test, benchmarks, quality-gate, elf-linking] runs-on: ubuntu-latest if: success() steps: @@ -413,6 +471,7 @@ jobs: echo " - Linux AMD64 (ubuntu-latest)" echo " - Windows AMD64 (windows-latest)" echo " - macOS ARM64 (macos-latest)" + echo "✅ ELF Linking: PASSED (default dynamic + goffi_static)" echo "✅ Benchmarks: PASSED" echo "✅ Quality Gate: PASSED" echo "" diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bbd595..710ea04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- **`-tags goffi_static`** — fully static Linux amd64/arm64 binaries under `CGO_ENABLED=0` by excluding all `//go:cgo_import_dynamic` directives (`libdl`/`libc`/`libpthread`). `ffi.LoadLibrary` / `GetSymbol` return `ffi.ErrStaticBuild`. ([#74](https://github.com/go-webgpu/goffi/issues/74), [gogpu#474](https://github.com/gogpu/gogpu/issues/474)) +- **Linking modes** documented in README (dynamic FFI, musl dynamic, static no-FFI) +- **`scripts/check-elf-linking.sh`** — CI/helper asserts `PT_INTERP` / `DT_NEEDED` for default vs static profiles +- **`docs/ADR-001-userspace-elf-loader.md`** — research track for optional pure-Go ELF `.so` loader (preview, not default) + ## [0.6.3] - 2026-08-01 ### Fixed diff --git a/README.md b/README.md index e11f5de..399d4bc 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,28 @@ CGO_ENABLED=1 go build ./... > **How?** goffi uses Go's `cgo_import_dynamic` for dynamic library loading. Under `CGO_ENABLED=0` the cgo runtime is supplied by `internal/fakecgo`; under `CGO_ENABLED=1` the standard `runtime/cgo` is linked in. Both modes share the same FFI fast path and ABIs. +### Linking modes (Linux) + +`CGO_ENABLED=0` does **not** imply a fully static ELF when goffi is imported. `//go:cgo_import_dynamic` for `dlopen` / libc still records `PT_INTERP` and `DT_NEEDED` (`libdl.so.2`, `libc.so.6`, `libpthread.so.0`). This matches purego and is required for host `dlopen` — the kernel only maps `ld.so` when `PT_INTERP` is present. See [goffi#74](https://github.com/go-webgpu/goffi/issues/74) and [gogpu#474](https://github.com/gogpu/gogpu/issues/474). + +| Mode | How | ELF shape | `LoadLibrary` | Typical use | +|------|-----|-----------|---------------|-------------| +| **Dynamic FFI** (default) | `CGO_ENABLED=0 go build` | dynamic + `libdl`/`libc` | yes | desktop GPU/GUI | +| **Musl dynamic** | build on Alpine / `CC=musl-gcc` | dynamic vs musl | yes | Alpine containers with GPU/GUI | +| **Static no-FFI** | `CGO_ENABLED=0 go build -tags goffi_static` | fully static (no `PT_INTERP`, no `NEEDED`) | no (`errors.Is(err, ffi.ErrStaticBuild)`) | `FROM scratch`, air-gapped CLI | + +```bash +# Fully static Linux amd64/arm64 binary (FFI unavailable) +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags goffi_static -o app . +file app # statically linked +# Verify: no INTERP / NEEDED +scripts/check-elf-linking.sh --static ./app +``` + +Under `-tags goffi_static`, errno capture is unavailable (always returns 0): `ErrnoFnAddr()` is a no-op so the assembly trampoline skips `__errno_location` / `__error`, which need dynamic libc. + +`FROM scratch` + Vulkan/Wayland/libX11 via host `dlopen` is not possible without either `ld.so` or a userspace ELF loader (see [docs/ADR-001-userspace-elf-loader.md](docs/ADR-001-userspace-elf-loader.md)). Windows is unaffected (`LoadLibraryW` via ntdll). + ### Example: Calling strlen ```go @@ -380,6 +402,9 @@ if err != nil { ## Known Limitations +**Linux: default builds are dynamically linked** ([#74](https://github.com/go-webgpu/goffi/issues/74)) +- Importing goffi records `libdl`/`libc` via `cgo_import_dynamic` even with `CGO_ENABLED=0`. Use `-tags goffi_static` for a fully static ELF (no runtime `.so` loading), or build against musl for Alpine. See [Linking modes](#linking-modes-linux). + **Windows: C++ exceptions may crash the program** ([#12516](https://github.com/golang/go/issues/12516)) - Go runtime limitation, not goffi-specific. Go 1.22+ added partial SEH support ([#58542](https://github.com/golang/go/issues/58542)), but edge cases remain. - Workaround: build native libraries with `panic=abort`. diff --git a/ROADMAP.md b/ROADMAP.md index 33f9b85..286c088 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -3,7 +3,7 @@ > **Strategic Approach**: Build production-ready Zero-CGO FFI with benchmarked performance > **Philosophy**: Performance first, usability second, platform coverage third -**Last Updated**: 2026-08-01 | **Current Version**: v0.6.3 | **Strategy**: Benchmarks → Callbacks → ARM64 → Runtime → ABI → v1.0 LTS | **Milestone**: v0.6.3 (HFA checkptr fix) → v0.7.0 RegisterFunc/Builder → v1.0.0 LTS +**Last Updated**: 2026-09-08 | **Current Version**: v0.6.3 | **Strategy**: Benchmarks → Callbacks → ARM64 → Runtime → ABI → v1.0 LTS | **Milestone**: v0.6.3 (HFA checkptr fix) → v0.7.0 `goffi_static` + RegisterFunc/Builder → v1.0.0 LTS --- @@ -175,7 +175,10 @@ v1.0.0 LTS → Long-term support release (2027 Q1) - ARM64 9-16B struct return proactive fix (copy pattern) - Struct pass/return examples and README section (#58) -**v0.7.0** = RegisterFunc + Builder API (2026 Q3-Q4) +**v0.7.0** = Static linking + RegisterFunc + Builder API (2026 Q3-Q4) +- `-tags goffi_static` fully static Linux ELFs (#74, gogpu#474) — shipped in PR #78 +- Linking-mode docs + ELF CI gates +- ADR-001 userspace ELF loader (research) - RegisterFunc convenience API (ADR-008) - Library struct + OpenLibraryBytes (ADR-009) - NewFunc/Call/CallCtx ergonomic wrappers (ADR-009) @@ -190,7 +193,7 @@ v1.0.0 LTS → Long-term support release (2027 Q1) ## 📊 Current Status (v0.6.3) -**Phase**: HFA checkptr fix, struct examples. 9 platforms. Planning v0.7.0 (RegisterFunc) +**Phase**: HFA checkptr fix, struct examples. 9 platforms. `goffi_static` in PR #78; planning RegisterFunc for v0.7.0 **What Works**: - ✅ Dynamic library loading (`LoadLibrary`, `GetSymbol`, `FreeLibrary`) diff --git a/docs/ADR-001-userspace-elf-loader.md b/docs/ADR-001-userspace-elf-loader.md new file mode 100644 index 0000000..dce8c42 --- /dev/null +++ b/docs/ADR-001-userspace-elf-loader.md @@ -0,0 +1,90 @@ +# ADR-001: Userspace ELF loader for static binaries + +**Status:** Proposed (research / preview) +**Date:** 2026-09-08 +**Tracking:** [goffi#74](https://github.com/go-webgpu/goffi/issues/74), [gogpu#474](https://github.com/gogpu/gogpu/issues/474) +**Go baseline:** 1.25+ (`structs.HostLayout`, `CGO_ENABLED=0`) + +## Context + +Default goffi Linux builds use `//go:cgo_import_dynamic` to resolve `dlopen` / +`dlsym` from `libdl.so.2`. That forces `PT_INTERP` + `DT_NEEDED`, so binaries +are dynamically linked even with `CGO_ENABLED=0`. + +`-tags goffi_static` removes those imports and yields a fully static ELF, but +`ffi.LoadLibrary` returns `ErrStaticBuild`. Host `dlopen` cannot work inside a +static ELF: the kernel only loads `ld.so` when `PT_INTERP` is present (SunOS +userspace-loader design). Windows differs because `ntdll` is always mapped. + +Consumer apps (e.g. f4) want **static distribution** and, ideally, still load +system `.so` files (Wayland, Vulkan ICD, libX11) at runtime. + +## Decision + +Explore a **pure-Go userspace ELF `.so` loader** gated behind +`-tags goffi_elfloader` (experimental, not default). It is the only credible +path to “static binary + runtime LoadLibrary-like behavior” on Linux without +shipping `ld.so`. + +Ship Track 1–2 (`goffi_static` + docs/CI) first. This ADR does **not** block +closing the documentation / static-profile side of #74 / gogpu#474. + +## Goals + +1. Map `ET_DYN` shared objects via `unix.Mmap` / `PROT_EXEC` without calling + host `dlopen`. +2. Apply relative / symbolic relocations needed for a small allowlisted set of + UI/GPU libraries. +3. Hand resolved symbol addresses to existing goffi `CallFunction` (no change + to the assembly call path once the function pointer is known). +4. Fail closed on TLS, IFUNC, GNU symbol versioning, and glibc-private ABI + that cannot be reproduced safely. + +## Non-goals + +- Full glibc / musl loader compatibility. +- Loading arbitrary untrusted `.so` from the network. +- Default-on behavior for release builds. +- Mach-O / PE loaders in v0 (Linux amd64 first, then arm64). + +## Proposed design (v0) + +``` +LoadLibrary(path) + → open + read ELF headers + → mmap PT_LOAD segments (honor p_align) + → apply R_*_RELATIVE / R_*_GLOB_DAT against local + allowlisted modules + → return handle; GetSymbol walks .dynsym / hash +CallFunction(cif, sym, ...) // unchanged +``` + +Build tag: `goffi_elfloader` (implies or composes with static profile). +Public API stays `ffi.LoadLibrary` / `GetSymbol`; implementation switches +under the tag. + +Threat model: load **system libraries by absolute path** only +(`/usr/lib/**`, `/lib/**`). Document dual-use concerns of in-memory loaders; +reject relative paths and `LD_LIBRARY_PATH` search in v0. + +## Alternatives + +| Alternative | Why not primary | +|-------------|-----------------| +| Document-only | Does not meet static + FFI consumer need | +| musl-dynamic Alpine | Valid container story; not `FROM scratch` | +| Embed `ld.so` | Not Pure Go; redistributes glibc/musl loader | +| External linker `-static` | Fights CGO_ENABLED=0; still no useful `dlopen` | + +## Consequences + +- Large engineering surface (relocations, TLS, IFUNC). +- Security review required before preview announcement. +- Success metric for spike: load a trivial self-built `.so` and call one + exported `int add(int,int)` via `CallFunction` from a static binary. + +## Spike checklist + +- [ ] Linux amd64: map toy `.so`, resolve one symbol, call via goffi +- [ ] Reject TLS / IFUNC with typed errors +- [ ] Absolute-path allowlist +- [ ] Document Go/No-Go for arm64 preview diff --git a/ffi/dl_android.go b/ffi/dl_android.go index c957e24..850642c 100644 --- a/ffi/dl_android.go +++ b/ffi/dl_android.go @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2026 The Goffi Authors -//go:build android && arm64 +//go:build android && arm64 && !goffi_static package ffi diff --git a/ffi/dl_darwin.go b/ffi/dl_darwin.go index 2212730..db2ed07 100644 --- a/ffi/dl_darwin.go +++ b/ffi/dl_darwin.go @@ -1,4 +1,4 @@ -//go:build darwin && (amd64 || arm64) +//go:build darwin && (amd64 || arm64) && !goffi_static // macOS library loading - OUR OWN implementation (NO dependencies!) // diff --git a/ffi/dl_static.go b/ffi/dl_static.go new file mode 100644 index 0000000..bbaa9bc --- /dev/null +++ b/ffi/dl_static.go @@ -0,0 +1,42 @@ +//go:build goffi_static && ((linux && !android) || darwin || freebsd || (android && arm64)) && (amd64 || arm64) + +package ffi + +import "unsafe" + +// RTLD constants match the dynamic profile so call sites compile unchanged. +// They are unused under goffi_static because LoadLibrary always fails. +const ( + RTLD_NOW = 0x00002 + RTLD_GLOBAL = 0x00100 +) + +// LoadLibrary always fails under -tags goffi_static. +func LoadLibrary(name string) (unsafe.Pointer, error) { + return nil, &LibraryError{ + Operation: "load", + Name: name, + Err: ErrStaticBuild, + } +} + +// GetSymbol always fails under -tags goffi_static. +func GetSymbol(handle unsafe.Pointer, name string) (unsafe.Pointer, error) { + return nil, &LibraryError{ + Operation: "symbol", + Name: name, + Err: ErrStaticBuild, + } +} + +// FreeLibrary is a no-op success for nil and returns ErrStaticBuild otherwise. +func FreeLibrary(handle unsafe.Pointer) error { + if handle == nil { + return nil + } + return &LibraryError{ + Operation: "free", + Name: "", + Err: ErrStaticBuild, + } +} diff --git a/ffi/dl_unix.go b/ffi/dl_unix.go index f30ceee..3117fa6 100644 --- a/ffi/dl_unix.go +++ b/ffi/dl_unix.go @@ -1,4 +1,4 @@ -//go:build ((linux && !android) || freebsd) && (amd64 || arm64) +//go:build ((linux && !android) || freebsd) && (amd64 || arm64) && !goffi_static // Unix library loading via dlopen - OUR OWN implementation (NO dependencies!) // diff --git a/ffi/dl_unix_stubs.s b/ffi/dl_unix_stubs.s index d0c15fd..b1dcca9 100644 --- a/ffi/dl_unix_stubs.s +++ b/ffi/dl_unix_stubs.s @@ -1,4 +1,4 @@ -//go:build linux && amd64 +//go:build linux && amd64 && !goffi_static #include "textflag.h" diff --git a/ffi/errors_static.go b/ffi/errors_static.go new file mode 100644 index 0000000..1a98e52 --- /dev/null +++ b/ffi/errors_static.go @@ -0,0 +1,11 @@ +package ffi + +import "errors" + +// ErrStaticBuild is returned by LoadLibrary / GetSymbol / FreeLibrary when the +// binary was built with -tags goffi_static. That profile removes all +// //go:cgo_import_dynamic directives so the Go internal linker can emit a +// fully static ELF; dynamic library loading is intentionally unavailable. +// +// Use errors.Is(err, ErrStaticBuild) or unwrap through *LibraryError. +var ErrStaticBuild = errors.New("goffi: dynamic loading unavailable in static build (-tags goffi_static)") diff --git a/ffi/fakecgo_static.go b/ffi/fakecgo_static.go new file mode 100644 index 0000000..022bb03 --- /dev/null +++ b/ffi/fakecgo_static.go @@ -0,0 +1,8 @@ +//go:build goffi_static && (linux || darwin || freebsd) && !cgo + +package ffi + +// Pull in the static-profile fakecgo stubs (crosscall2 abort trampoline) so the +// ffi package links when callback assembly is present. Dynamic loading remains +// unavailable; see ErrStaticBuild. +import _ "github.com/go-webgpu/goffi/internal/fakecgo" diff --git a/ffi/fakecgo_unix.go b/ffi/fakecgo_unix.go index 34c7e51..1f0387a 100644 --- a/ffi/fakecgo_unix.go +++ b/ffi/fakecgo_unix.go @@ -1,4 +1,4 @@ -//go:build (linux || darwin || freebsd) && !cgo && !nofakecgo +//go:build (linux || darwin || freebsd) && !cgo && !nofakecgo && !goffi_static package ffi diff --git a/ffi/static_test.go b/ffi/static_test.go new file mode 100644 index 0000000..1def3c6 --- /dev/null +++ b/ffi/static_test.go @@ -0,0 +1,42 @@ +//go:build goffi_static + +package ffi_test + +import ( + "errors" + "testing" + "unsafe" + + "github.com/go-webgpu/goffi/ffi" +) + +func TestStaticBuild_LoadLibraryReturnsErrStaticBuild(t *testing.T) { + handle, err := ffi.LoadLibrary("libc.so.6") + if handle != nil { + t.Fatalf("LoadLibrary handle = %v, want nil", handle) + } + if !errors.Is(err, ffi.ErrStaticBuild) { + t.Fatalf("LoadLibrary error = %v, want errors.Is(..., ErrStaticBuild)", err) + } + + var libErr *ffi.LibraryError + if !errors.As(err, &libErr) { + t.Fatalf("LoadLibrary error type = %T, want *LibraryError", err) + } + if libErr.Operation != "load" { + t.Fatalf("Operation = %q, want load", libErr.Operation) + } +} + +func TestStaticBuild_GetSymbolReturnsErrStaticBuild(t *testing.T) { + _, err := ffi.GetSymbol(unsafe.Pointer(uintptr(1)), "strlen") + if !errors.Is(err, ffi.ErrStaticBuild) { + t.Fatalf("GetSymbol error = %v, want errors.Is(..., ErrStaticBuild)", err) + } +} + +func TestStaticBuild_FreeLibraryNilOK(t *testing.T) { + if err := ffi.FreeLibrary(nil); err != nil { + t.Fatalf("FreeLibrary(nil) = %v, want nil", err) + } +} diff --git a/internal/dl/cgo.go b/internal/dl/cgo.go index 5245f96..3c5a717 100644 --- a/internal/dl/cgo.go +++ b/internal/dl/cgo.go @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2026 Andrey Kolkov and GoGPU Contributors -//go:build cgo && (darwin || freebsd || linux || netbsd) +//go:build cgo && (darwin || freebsd || linux || netbsd) && !goffi_static package dl diff --git a/internal/dl/dl_android.go b/internal/dl/dl_android.go index dbcdc57..07c14df 100644 --- a/internal/dl/dl_android.go +++ b/internal/dl/dl_android.go @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2026 The Goffi Authors -//go:build android && arm64 +//go:build android && arm64 && !goffi_static // Android/Bionic dynamic-loader declarations. package dl diff --git a/internal/dl/dl_android_cgo.go b/internal/dl/dl_android_cgo.go index 8adfa8c..7e865b5 100644 --- a/internal/dl/dl_android_cgo.go +++ b/internal/dl/dl_android_cgo.go @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2026 The Goffi Authors -//go:build android && cgo && arm64 +//go:build android && cgo && arm64 && !goffi_static package dl diff --git a/internal/dl/dl_android_nocgo.go b/internal/dl/dl_android_nocgo.go index 758182d..1c7c0e7 100644 --- a/internal/dl/dl_android_nocgo.go +++ b/internal/dl/dl_android_nocgo.go @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2026 The Goffi Authors -//go:build android && !cgo && arm64 +//go:build android && !cgo && arm64 && !goffi_static package dl diff --git a/internal/dl/dl_android_nocgo_arm64.s b/internal/dl/dl_android_nocgo_arm64.s index 868e5f1..9849ae5 100644 --- a/internal/dl/dl_android_nocgo_arm64.s +++ b/internal/dl/dl_android_nocgo_arm64.s @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2026 The Goffi Authors -//go:build android && !cgo && arm64 +//go:build android && !cgo && arm64 && !goffi_static #include "textflag.h" diff --git a/internal/dl/dl_darwin.go b/internal/dl/dl_darwin.go index 1095464..9ee2455 100644 --- a/internal/dl/dl_darwin.go +++ b/internal/dl/dl_darwin.go @@ -1,4 +1,4 @@ -//go:build darwin +//go:build darwin && !goffi_static // macOS-specific constants for dynamic library loading. // diff --git a/internal/dl/dl_freebsd.go b/internal/dl/dl_freebsd.go index 8fd0fdc..91f1728 100644 --- a/internal/dl/dl_freebsd.go +++ b/internal/dl/dl_freebsd.go @@ -1,4 +1,4 @@ -//go:build freebsd +//go:build freebsd && !goffi_static // FreeBSD-specific constants for dynamic library loading. // diff --git a/internal/dl/dl_linux.go b/internal/dl/dl_linux.go index bec6a9b..b507c45 100644 --- a/internal/dl/dl_linux.go +++ b/internal/dl/dl_linux.go @@ -1,4 +1,4 @@ -//go:build linux && !android +//go:build linux && !android && !goffi_static // Linux-specific constants for dynamic library loading. // diff --git a/internal/dl/dl_static.go b/internal/dl/dl_static.go new file mode 100644 index 0000000..1fc7b96 --- /dev/null +++ b/internal/dl/dl_static.go @@ -0,0 +1,40 @@ +//go:build goffi_static + +// Static-profile stubs for dynamic library loading. +// +// Under -tags goffi_static every //go:cgo_import_dynamic directive is excluded +// so Linux binaries can link fully statically. Dlopen/Dlsym/Dlclose therefore +// return ErrStaticBuild instead of calling libdl. + +package dl + +import "errors" + +// ErrStaticBuild indicates dynamic loading is unavailable in a static build. +var ErrStaticBuild = errors.New("goffi/internal/dl: dynamic loading unavailable in static build (-tags goffi_static)") + +// RTLD constants (POSIX values; unused when loading is stubbed). +const ( + RTLD_LAZY = 0x00001 + RTLD_NOW = 0x00002 + RTLD_GLOBAL = 0x00100 + RTLD_LOCAL = 0x00000 +) + +// RTLD_DEFAULT is a pseudo-handle for dlsym. +const RTLD_DEFAULT = 0x00000 + +// Dlopen always fails under -tags goffi_static. +func Dlopen(path string, mode int) (uintptr, error) { + return 0, ErrStaticBuild +} + +// Dlsym always fails under -tags goffi_static. +func Dlsym(handle uintptr, name string) (uintptr, error) { + return 0, ErrStaticBuild +} + +// Dlclose always fails under -tags goffi_static. +func Dlclose(handle uintptr) error { + return ErrStaticBuild +} diff --git a/internal/dl/dl_stubs_arm64.s b/internal/dl/dl_stubs_arm64.s index cec205d..29c1924 100644 --- a/internal/dl/dl_stubs_arm64.s +++ b/internal/dl/dl_stubs_arm64.s @@ -1,4 +1,4 @@ -//go:build ((linux && !android) || darwin || freebsd) && arm64 +//go:build ((linux && !android) || darwin || freebsd) && arm64 && !goffi_static #include "textflag.h" diff --git a/internal/dl/dl_stubs_unix.s b/internal/dl/dl_stubs_unix.s index 03cbf65..71c9cbf 100644 --- a/internal/dl/dl_stubs_unix.s +++ b/internal/dl/dl_stubs_unix.s @@ -1,4 +1,4 @@ -//go:build (linux || darwin || freebsd) && amd64 +//go:build (linux || darwin || freebsd) && amd64 && !goffi_static #include "textflag.h" diff --git a/internal/dl/dl_unix.go b/internal/dl/dl_unix.go index 7299aed..eae0df0 100644 --- a/internal/dl/dl_unix.go +++ b/internal/dl/dl_unix.go @@ -1,4 +1,4 @@ -//go:build (linux && !android) || darwin || freebsd +//go:build ((linux && !android) || darwin || freebsd) && !goffi_static // OUR OWN Dlopen/Dlsym implementation - NO dependencies! // Uses runtime.cgocall approach similar to syscall6. diff --git a/internal/dl/dl_wrappers_arm64.s b/internal/dl/dl_wrappers_arm64.s index d90a72f..8acbd70 100644 --- a/internal/dl/dl_wrappers_arm64.s +++ b/internal/dl/dl_wrappers_arm64.s @@ -1,4 +1,4 @@ -//go:build ((linux && !android) || darwin || freebsd) && arm64 +//go:build ((linux && !android) || darwin || freebsd) && arm64 && !goffi_static #include "textflag.h" diff --git a/internal/dl/dl_wrappers_unix.s b/internal/dl/dl_wrappers_unix.s index fe06718..b51c6ca 100644 --- a/internal/dl/dl_wrappers_unix.s +++ b/internal/dl/dl_wrappers_unix.s @@ -1,4 +1,4 @@ -//go:build (linux || darwin || freebsd) && amd64 +//go:build (linux || darwin || freebsd) && amd64 && !goffi_static #include "textflag.h" diff --git a/internal/fakecgo/android_abi_test.go b/internal/fakecgo/android_abi_test.go index 08b4613..606f1ae 100644 --- a/internal/fakecgo/android_abi_test.go +++ b/internal/fakecgo/android_abi_test.go @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2026 The Goffi Authors -//go:build android && arm64 && !cgo +//go:build android && arm64 && !cgo && !goffi_static package fakecgo diff --git a/internal/fakecgo/android_dl.go b/internal/fakecgo/android_dl.go index 40fe8e1..7a8476b 100644 --- a/internal/fakecgo/android_dl.go +++ b/internal/fakecgo/android_dl.go @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2026 The Goffi Authors -//go:build !cgo && android && arm64 +//go:build !cgo && android && arm64 && !goffi_static package fakecgo diff --git a/internal/fakecgo/android_dl_stubs_arm64.s b/internal/fakecgo/android_dl_stubs_arm64.s index 6e3f292..a7d34c4 100644 --- a/internal/fakecgo/android_dl_stubs_arm64.s +++ b/internal/fakecgo/android_dl_stubs_arm64.s @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2026 The Goffi Authors -//go:build !cgo && android && arm64 +//go:build !cgo && android && arm64 && !goffi_static #include "textflag.h" diff --git a/internal/fakecgo/asm_amd64.s b/internal/fakecgo/asm_amd64.s index 3cf3481..0794390 100644 --- a/internal/fakecgo/asm_amd64.s +++ b/internal/fakecgo/asm_amd64.s @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !cgo && (darwin || freebsd || linux || netbsd) +//go:build !cgo && (darwin || freebsd || linux || netbsd) && !goffi_static #include "textflag.h" #include "abi_amd64.h" diff --git a/internal/fakecgo/asm_arm64.s b/internal/fakecgo/asm_arm64.s index 92598e9..3073bb7 100644 --- a/internal/fakecgo/asm_arm64.s +++ b/internal/fakecgo/asm_arm64.s @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !cgo && (darwin || freebsd || linux || netbsd) +//go:build !cgo && (darwin || freebsd || linux || netbsd) && !goffi_static #include "textflag.h" #include "abi_arm64.h" diff --git a/internal/fakecgo/asm_static_amd64.s b/internal/fakecgo/asm_static_amd64.s new file mode 100644 index 0000000..e4af625 --- /dev/null +++ b/internal/fakecgo/asm_static_amd64.s @@ -0,0 +1,11 @@ +//go:build goffi_static && (linux || darwin || freebsd || netbsd) && amd64 + +#include "textflag.h" + +// crosscall2 stub for -tags goffi_static. +// Callbacks require fakecgo + dynamic libc; static builds intentionally omit +// that stack. This symbol exists only so the ffi package links; calling it +// aborts. +TEXT crosscall2(SB), NOSPLIT, $0-0 + CALL runtime·abort(SB) + RET diff --git a/internal/fakecgo/asm_static_arm64.s b/internal/fakecgo/asm_static_arm64.s new file mode 100644 index 0000000..c1aa126 --- /dev/null +++ b/internal/fakecgo/asm_static_arm64.s @@ -0,0 +1,11 @@ +//go:build goffi_static && (linux || darwin || freebsd || netbsd) && arm64 + +#include "textflag.h" + +// crosscall2 stub for -tags goffi_static. +// Callbacks require fakecgo + dynamic libc; static builds intentionally omit +// that stack. This symbol exists only so the ffi package links; calling it +// aborts. +TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 + BL runtime·abort(SB) + RET diff --git a/internal/fakecgo/callbacks.go b/internal/fakecgo/callbacks.go index 27d4c98..67cc9bd 100644 --- a/internal/fakecgo/callbacks.go +++ b/internal/fakecgo/callbacks.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !cgo && (darwin || freebsd || linux || netbsd) +//go:build !cgo && (darwin || freebsd || linux || netbsd) && !goffi_static package fakecgo diff --git a/internal/fakecgo/doc.go b/internal/fakecgo/doc.go index b7e3e14..5cdd0c3 100644 --- a/internal/fakecgo/doc.go +++ b/internal/fakecgo/doc.go @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && (darwin || freebsd || linux || netbsd) +//go:build !cgo && (darwin || freebsd || linux || netbsd) && !goffi_static // Package fakecgo implements the Cgo runtime (runtime/cgo) entirely in Go. // This allows code that calls into C to function properly when CGO_ENABLED=0. diff --git a/internal/fakecgo/freebsd.go b/internal/fakecgo/freebsd.go index ee6b266..d708fb1 100644 --- a/internal/fakecgo/freebsd.go +++ b/internal/fakecgo/freebsd.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build freebsd && !cgo +//go:build freebsd && !cgo && !goffi_static package fakecgo diff --git a/internal/fakecgo/gen.go b/internal/fakecgo/gen.go index 1a80e7d..c0e655a 100644 --- a/internal/fakecgo/gen.go +++ b/internal/fakecgo/gen.go @@ -22,7 +22,7 @@ const templateSymbols = `// Code generated by 'go generate' with gen.go. DO NOT // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && (darwin || freebsd || linux || netbsd) && !android +//go:build !cgo && (darwin || freebsd || linux || netbsd) && !android && !goffi_static package fakecgo @@ -90,7 +90,7 @@ const templateTrampolinesStubs = `// Code generated by 'go generate' with gen.go // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && (darwin || freebsd || linux || netbsd) && !android +//go:build !cgo && (darwin || freebsd || linux || netbsd) && !android && !goffi_static #include "textflag.h" @@ -108,7 +108,7 @@ const templateSymbolsGoos = `// Code generated by 'go generate' with gen.go. DO // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo +//go:build !cgo && !goffi_static package fakecgo @@ -263,11 +263,11 @@ func run() error { case "linux": // The go command also satisfies the linux build tag on Android, // including for _linux.go files. Keep glibc imports out of Bionic. - goosTemplate = template.Must(template.New("symbols_linux.go").Parse(strings.Replace(templateSymbolsGoos, "//go:build !cgo", "//go:build !cgo && !android", 1))) + goosTemplate = template.Must(template.New("symbols_linux.go").Parse(strings.Replace(templateSymbolsGoos, "//go:build !cgo && !goffi_static", "//go:build !cgo && !android && !goffi_static", 1))) case "android": // Android uses a distinct build selector and symbol set. Keep the // generated generic Linux imports out of the Android ELF. - goosTemplate = template.Must(template.New("symbols_android.go").Parse(strings.Replace(templateSymbolsGoos, "//go:build !cgo", "//go:build !cgo && android && arm64", 1))) + goosTemplate = template.Must(template.New("symbols_android.go").Parse(strings.Replace(templateSymbolsGoos, "//go:build !cgo && !goffi_static", "//go:build !cgo && android && arm64 && !goffi_static", 1))) } if err = goosTemplate.Execute(b, located); err != nil { return err @@ -289,7 +289,7 @@ func run() error { // The Android wrappers and assembly stubs are generated from the same // restricted symbol set as the imports above. androidSymbols := append(append([]Symbol{}, androidLibcSymbols...), androidPthreadSymbols...) - androidTemplate := strings.Replace(templateSymbols, "//go:build !cgo && (darwin || freebsd || linux || netbsd) && !android", "//go:build !cgo && android && arm64", 1) + androidTemplate := strings.Replace(templateSymbols, "//go:build !cgo && (darwin || freebsd || linux || netbsd) && !android && !goffi_static", "//go:build !cgo && android && arm64 && !goffi_static", 1) t, err = template.New("symbols_android.go").Funcs(funcs).Parse(androidTemplate) if err != nil { return err @@ -301,7 +301,7 @@ func run() error { if err := writeFormatted("symbols_android.go", buf.Bytes()); err != nil { return err } - androidStubTemplate := strings.Replace(templateTrampolinesStubs, "//go:build !cgo && (darwin || freebsd || linux || netbsd) && !android", "//go:build !cgo && android && arm64", 1) + androidStubTemplate := strings.Replace(templateTrampolinesStubs, "//go:build !cgo && (darwin || freebsd || linux || netbsd) && !android && !goffi_static", "//go:build !cgo && android && arm64 && !goffi_static", 1) t, err = template.New("trampolines_stubs_android.s").Funcs(funcs).Parse(androidStubTemplate) if err != nil { return err diff --git a/internal/fakecgo/go_android_arm64.go b/internal/fakecgo/go_android_arm64.go index 7c92736..b4b63eb 100644 --- a/internal/fakecgo/go_android_arm64.go +++ b/internal/fakecgo/go_android_arm64.go @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2011 The Go Authors // SPDX-FileCopyrightText: 2026 The Goffi Authors -//go:build !cgo && android && arm64 +//go:build !cgo && android && arm64 && !goffi_static package fakecgo diff --git a/internal/fakecgo/go_darwin_amd64.go b/internal/fakecgo/go_darwin_amd64.go index 39f5ff1..8f3edbe 100644 --- a/internal/fakecgo/go_darwin_amd64.go +++ b/internal/fakecgo/go_darwin_amd64.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !cgo +//go:build !cgo && !goffi_static package fakecgo diff --git a/internal/fakecgo/go_darwin_arm64.go b/internal/fakecgo/go_darwin_arm64.go index d0868f0..3e254c0 100644 --- a/internal/fakecgo/go_darwin_arm64.go +++ b/internal/fakecgo/go_darwin_arm64.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !cgo +//go:build !cgo && !goffi_static package fakecgo diff --git a/internal/fakecgo/go_freebsd_amd64.go b/internal/fakecgo/go_freebsd_amd64.go index c9ff715..e2f5af6 100644 --- a/internal/fakecgo/go_freebsd_amd64.go +++ b/internal/fakecgo/go_freebsd_amd64.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !cgo +//go:build !cgo && !goffi_static package fakecgo diff --git a/internal/fakecgo/go_freebsd_arm64.go b/internal/fakecgo/go_freebsd_arm64.go index e3a060b..d69a0fc 100644 --- a/internal/fakecgo/go_freebsd_arm64.go +++ b/internal/fakecgo/go_freebsd_arm64.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !cgo +//go:build !cgo && !goffi_static package fakecgo diff --git a/internal/fakecgo/go_libinit.go b/internal/fakecgo/go_libinit.go index b2f7325..5f87e5b 100644 --- a/internal/fakecgo/go_libinit.go +++ b/internal/fakecgo/go_libinit.go @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && (darwin || freebsd || linux || netbsd) +//go:build !cgo && (darwin || freebsd || linux || netbsd) && !goffi_static package fakecgo diff --git a/internal/fakecgo/go_linux_amd64.go b/internal/fakecgo/go_linux_amd64.go index 198f2dc..498e7d6 100644 --- a/internal/fakecgo/go_linux_amd64.go +++ b/internal/fakecgo/go_linux_amd64.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !cgo && linux && !android +//go:build !cgo && linux && !android && !goffi_static package fakecgo diff --git a/internal/fakecgo/go_linux_arm64.go b/internal/fakecgo/go_linux_arm64.go index fb45f64..2d79493 100644 --- a/internal/fakecgo/go_linux_arm64.go +++ b/internal/fakecgo/go_linux_arm64.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !cgo && linux && !android +//go:build !cgo && linux && !android && !goffi_static package fakecgo diff --git a/internal/fakecgo/go_netbsd.go b/internal/fakecgo/go_netbsd.go index 935a334..e81916f 100644 --- a/internal/fakecgo/go_netbsd.go +++ b/internal/fakecgo/go_netbsd.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !cgo && (amd64 || arm64) +//go:build !cgo && (amd64 || arm64) && !goffi_static package fakecgo diff --git a/internal/fakecgo/go_setenv.go b/internal/fakecgo/go_setenv.go index 0c4bf83..9ba7d2b 100644 --- a/internal/fakecgo/go_setenv.go +++ b/internal/fakecgo/go_setenv.go @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && (darwin || freebsd || linux || netbsd) +//go:build !cgo && (darwin || freebsd || linux || netbsd) && !goffi_static package fakecgo diff --git a/internal/fakecgo/go_util.go b/internal/fakecgo/go_util.go index 4d52cb5..867bccf 100644 --- a/internal/fakecgo/go_util.go +++ b/internal/fakecgo/go_util.go @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && (darwin || freebsd || linux || netbsd) +//go:build !cgo && (darwin || freebsd || linux || netbsd) && !goffi_static package fakecgo diff --git a/internal/fakecgo/iscgo.go b/internal/fakecgo/iscgo.go index 32e4372..bb842b2 100644 --- a/internal/fakecgo/iscgo.go +++ b/internal/fakecgo/iscgo.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !cgo && (darwin || freebsd || linux || netbsd) +//go:build !cgo && (darwin || freebsd || linux || netbsd) && !goffi_static // The runtime package contains an uninitialized definition // for runtime·iscgo. Override it to tell the runtime we're here. diff --git a/internal/fakecgo/libcgo.go b/internal/fakecgo/libcgo.go index d78d6d0..5179375 100644 --- a/internal/fakecgo/libcgo.go +++ b/internal/fakecgo/libcgo.go @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && (darwin || freebsd || linux || netbsd) && !android +//go:build !cgo && (darwin || freebsd || linux || netbsd) && !android && !goffi_static package fakecgo diff --git a/internal/fakecgo/libcgo_android.go b/internal/fakecgo/libcgo_android.go index f3d9def..7d906c5 100644 --- a/internal/fakecgo/libcgo_android.go +++ b/internal/fakecgo/libcgo_android.go @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2026 The Goffi Authors -//go:build !cgo && android && arm64 +//go:build !cgo && android && arm64 && !goffi_static package fakecgo diff --git a/internal/fakecgo/libcgo_darwin.go b/internal/fakecgo/libcgo_darwin.go index 35785fd..2a40b4e 100644 --- a/internal/fakecgo/libcgo_darwin.go +++ b/internal/fakecgo/libcgo_darwin.go @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo +//go:build !cgo && !goffi_static package fakecgo diff --git a/internal/fakecgo/libcgo_freebsd.go b/internal/fakecgo/libcgo_freebsd.go index 990f712..e98cc2f 100644 --- a/internal/fakecgo/libcgo_freebsd.go +++ b/internal/fakecgo/libcgo_freebsd.go @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo +//go:build !cgo && !goffi_static package fakecgo diff --git a/internal/fakecgo/libcgo_linux.go b/internal/fakecgo/libcgo_linux.go index 503a15b..ac15e1e 100644 --- a/internal/fakecgo/libcgo_linux.go +++ b/internal/fakecgo/libcgo_linux.go @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && linux && !android +//go:build !cgo && linux && !android && !goffi_static package fakecgo diff --git a/internal/fakecgo/libcgo_netbsd.go b/internal/fakecgo/libcgo_netbsd.go index bc25eb0..a5e69b1 100644 --- a/internal/fakecgo/libcgo_netbsd.go +++ b/internal/fakecgo/libcgo_netbsd.go @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2025 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo +//go:build !cgo && !goffi_static package fakecgo diff --git a/internal/fakecgo/netbsd.go b/internal/fakecgo/netbsd.go index b9ab6bf..96f7a0c 100644 --- a/internal/fakecgo/netbsd.go +++ b/internal/fakecgo/netbsd.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build netbsd && !cgo +//go:build netbsd && !cgo && !goffi_static package fakecgo diff --git a/internal/fakecgo/setenv.go b/internal/fakecgo/setenv.go index 82308b8..c827d55 100644 --- a/internal/fakecgo/setenv.go +++ b/internal/fakecgo/setenv.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !cgo && (darwin || freebsd || linux || netbsd) +//go:build !cgo && (darwin || freebsd || linux || netbsd) && !goffi_static package fakecgo diff --git a/internal/fakecgo/static.go b/internal/fakecgo/static.go new file mode 100644 index 0000000..bc7dfee --- /dev/null +++ b/internal/fakecgo/static.go @@ -0,0 +1,9 @@ +//go:build goffi_static + +// Package fakecgo is a no-op under -tags goffi_static. +// +// The dynamic-profile fakecgo implementation pulls libc/libpthread via +// //go:cgo_import_dynamic, which forces PT_INTERP + DT_NEEDED even when +// CGO_ENABLED=0. Static builds exclude that code entirely: FFI LoadLibrary is +// stubbed and runtime.cgocall is not required for a pure-Go static binary. +package fakecgo diff --git a/internal/fakecgo/symbols.go b/internal/fakecgo/symbols.go index 1a6a353..26f612c 100644 --- a/internal/fakecgo/symbols.go +++ b/internal/fakecgo/symbols.go @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && (darwin || freebsd || linux || netbsd) && !android +//go:build !cgo && (darwin || freebsd || linux || netbsd) && !android && !goffi_static package fakecgo diff --git a/internal/fakecgo/symbols_android.go b/internal/fakecgo/symbols_android.go index 0899800..e7eb5a6 100644 --- a/internal/fakecgo/symbols_android.go +++ b/internal/fakecgo/symbols_android.go @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && android && arm64 +//go:build !cgo && android && arm64 && !goffi_static package fakecgo diff --git a/internal/fakecgo/symbols_android_imports.go b/internal/fakecgo/symbols_android_imports.go index 8e298dc..5fdd9d3 100644 --- a/internal/fakecgo/symbols_android_imports.go +++ b/internal/fakecgo/symbols_android_imports.go @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && android && arm64 +//go:build !cgo && android && arm64 && !goffi_static package fakecgo diff --git a/internal/fakecgo/symbols_darwin.go b/internal/fakecgo/symbols_darwin.go index 812feda..e5456d2 100644 --- a/internal/fakecgo/symbols_darwin.go +++ b/internal/fakecgo/symbols_darwin.go @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo +//go:build !cgo && !goffi_static package fakecgo diff --git a/internal/fakecgo/symbols_freebsd.go b/internal/fakecgo/symbols_freebsd.go index c6495b5..3d1bb22 100644 --- a/internal/fakecgo/symbols_freebsd.go +++ b/internal/fakecgo/symbols_freebsd.go @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo +//go:build !cgo && !goffi_static package fakecgo diff --git a/internal/fakecgo/symbols_linux.go b/internal/fakecgo/symbols_linux.go index b5c7dfa..9c8c3c3 100644 --- a/internal/fakecgo/symbols_linux.go +++ b/internal/fakecgo/symbols_linux.go @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && !android +//go:build !cgo && !android && !goffi_static package fakecgo diff --git a/internal/fakecgo/symbols_netbsd.go b/internal/fakecgo/symbols_netbsd.go index 4d42854..1b6ac6f 100644 --- a/internal/fakecgo/symbols_netbsd.go +++ b/internal/fakecgo/symbols_netbsd.go @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo +//go:build !cgo && !goffi_static package fakecgo diff --git a/internal/fakecgo/trampolines_amd64.s b/internal/fakecgo/trampolines_amd64.s index 13aa365..e216e18 100644 --- a/internal/fakecgo/trampolines_amd64.s +++ b/internal/fakecgo/trampolines_amd64.s @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && (darwin || linux || freebsd) +//go:build !cgo && (darwin || linux || freebsd) && !goffi_static /* trampoline for emulating required C functions for cgo in go (see cgo.go) diff --git a/internal/fakecgo/trampolines_arm64.s b/internal/fakecgo/trampolines_arm64.s index b938006..ecedef3 100644 --- a/internal/fakecgo/trampolines_arm64.s +++ b/internal/fakecgo/trampolines_arm64.s @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && (darwin || freebsd || linux) +//go:build !cgo && (darwin || freebsd || linux) && !goffi_static #include "textflag.h" #include "go_asm.h" diff --git a/internal/fakecgo/trampolines_stubs.s b/internal/fakecgo/trampolines_stubs.s index 9bfcccb..2c3d4ce 100644 --- a/internal/fakecgo/trampolines_stubs.s +++ b/internal/fakecgo/trampolines_stubs.s @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && (darwin || freebsd || linux || netbsd) && !android +//go:build !cgo && (darwin || freebsd || linux || netbsd) && !android && !goffi_static #include "textflag.h" diff --git a/internal/fakecgo/trampolines_stubs_android.s b/internal/fakecgo/trampolines_stubs_android.s index 7a50243..f56f3c9 100644 --- a/internal/fakecgo/trampolines_stubs_android.s +++ b/internal/fakecgo/trampolines_stubs_android.s @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2025-2026 Andrey Kolkov and GoGPU Contributors -//go:build !cgo && android && arm64 +//go:build !cgo && android && arm64 && !goffi_static #include "textflag.h" diff --git a/internal/syscall/errno_android.go b/internal/syscall/errno_android.go index ff59c46..09df079 100644 --- a/internal/syscall/errno_android.go +++ b/internal/syscall/errno_android.go @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2026 The Goffi Authors -//go:build android && !cgo && arm64 +//go:build android && !cgo && arm64 && !goffi_static package syscall diff --git a/internal/syscall/errno_darwin.go b/internal/syscall/errno_darwin.go index a4e1cf9..41d31a8 100644 --- a/internal/syscall/errno_darwin.go +++ b/internal/syscall/errno_darwin.go @@ -1,4 +1,4 @@ -//go:build darwin && (amd64 || arm64) +//go:build darwin && (amd64 || arm64) && !goffi_static package syscall diff --git a/internal/syscall/errno_freebsd.go b/internal/syscall/errno_freebsd.go index 194b210..f73db68 100644 --- a/internal/syscall/errno_freebsd.go +++ b/internal/syscall/errno_freebsd.go @@ -1,4 +1,4 @@ -//go:build freebsd && (amd64 || arm64) +//go:build freebsd && (amd64 || arm64) && !goffi_static package syscall diff --git a/internal/syscall/errno_linux.go b/internal/syscall/errno_linux.go index 5836294..e2e38e9 100644 --- a/internal/syscall/errno_linux.go +++ b/internal/syscall/errno_linux.go @@ -1,4 +1,4 @@ -//go:build linux && !android && (amd64 || arm64) +//go:build linux && !android && (amd64 || arm64) && !goffi_static package syscall diff --git a/internal/syscall/errno_static.go b/internal/syscall/errno_static.go new file mode 100644 index 0000000..1996659 --- /dev/null +++ b/internal/syscall/errno_static.go @@ -0,0 +1,12 @@ +//go:build goffi_static && ((linux && !android) || darwin || freebsd || android) && (amd64 || arm64) + +package syscall + +// ErrnoFnAddr returns 0 under -tags goffi_static. +// +// The assembly trampoline skips errno capture when errnoFn is 0 (same as the +// Windows path). Dynamic imports for __errno_location / __error are excluded +// from static builds so the binary stays free of DT_NEEDED entries. +func ErrnoFnAddr() uintptr { + return 0 +} diff --git a/internal/syscall/errno_stubs_amd64.s b/internal/syscall/errno_stubs_amd64.s index bf45d73..f46bd45 100644 --- a/internal/syscall/errno_stubs_amd64.s +++ b/internal/syscall/errno_stubs_amd64.s @@ -1,4 +1,4 @@ -//go:build ((linux && !android) || darwin || freebsd) && amd64 +//go:build ((linux && !android) || darwin || freebsd) && amd64 && !goffi_static #include "textflag.h" diff --git a/internal/syscall/errno_stubs_arm64.s b/internal/syscall/errno_stubs_arm64.s index 24f9ba8..fa05a1d 100644 --- a/internal/syscall/errno_stubs_arm64.s +++ b/internal/syscall/errno_stubs_arm64.s @@ -1,4 +1,4 @@ -//go:build ((linux && !android) || (android && !cgo) || darwin || freebsd) && arm64 +//go:build ((linux && !android) || (android && !cgo) || darwin || freebsd) && arm64 && !goffi_static #include "textflag.h" diff --git a/internal/syscall/errno_unix.go b/internal/syscall/errno_unix.go index f815301..95964ad 100644 --- a/internal/syscall/errno_unix.go +++ b/internal/syscall/errno_unix.go @@ -1,4 +1,4 @@ -//go:build ((linux && !android) || (android && !cgo) || darwin || freebsd) && (amd64 || arm64) +//go:build ((linux && !android) || (android && !cgo) || darwin || freebsd) && (amd64 || arm64) && !goffi_static package syscall diff --git a/scripts/check-elf-linking.sh b/scripts/check-elf-linking.sh new file mode 100755 index 0000000..a83fe62 --- /dev/null +++ b/scripts/check-elf-linking.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +# check-elf-linking.sh — assert ELF linking shape for goffi builds. +# +# Usage: +# scripts/check-elf-linking.sh --dynamic +# scripts/check-elf-linking.sh --static +# +# Exit 0 on success. Requires readelf or llvm-readelf (or Python 3 fallback). + +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: scripts/check-elf-linking.sh --dynamic|--static + + --dynamic Expect PT_INTERP and at least one DT_NEEDED (default FFI profile) + --static Expect no PT_INTERP and no DT_NEEDED (-tags goffi_static) +EOF + exit 2 +} + +mode="" +binary="" +while [[ $# -gt 0 ]]; do + case "$1" in + --dynamic|--static) mode=${1#--}; shift ;; + -h|--help) usage ;; + *) binary=$1; shift ;; + esac +done +[[ -n "$mode" && -n "$binary" && -f "$binary" ]] || usage + +has_interp=0 +needed_count=0 + +if command -v llvm-readelf >/dev/null 2>&1; then + READELF=(llvm-readelf) +elif command -v readelf >/dev/null 2>&1; then + READELF=(readelf) +else + READELF=() +fi + +if [[ ${#READELF[@]} -gt 0 ]]; then + if "${READELF[@]}" -l "$binary" 2>/dev/null | grep -q 'INTERP'; then + has_interp=1 + fi + needed_count=$("${READELF[@]}" -d "$binary" 2>/dev/null | grep -c 'NEEDED' || true) +else + # Portable ELF64 little-endian probe (CI macOS runners without binutils). + eval "$(python3 - "$binary" <<'PY' +import struct, sys +path = sys.argv[1] +data = open(path, "rb").read() +if data[:4] != b"\x7fELF": + print("echo 'not ELF'; exit 1") + raise SystemExit +e_phoff = struct.unpack_from("&2 + exit 1 + fi + echo "OK: statically linked" +else + if [[ "$has_interp" -eq 0 ]]; then + echo "ERROR: expected PT_INTERP for dynamic FFI profile" >&2 + exit 1 + fi + if [[ "$needed_count" -eq 0 ]]; then + echo "ERROR: expected DT_NEEDED entries for dynamic FFI profile" >&2 + exit 1 + fi + echo "OK: dynamically linked with NEEDED libs" +fi