From 2e555b8976727d87b4ee33230ba0c0b3889ebe44 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 20 Jul 2026 10:37:18 +0800 Subject: [PATCH 1/2] runtime: add Go 1.26 C library hooks Provide the Darwin CPU, Linux gettimeofday, and all-thread syscall hooks required by Go 1.26 standard-library code in c-shared and c-archive builds. Exercise them from actual C consumers through fmt.Sprintf, syscall.Gettimeofday, and syscall.AllThreadsSyscall. --- _demo/go/export/export.go | 6 ++++ _demo/go/export/libexport.h.want | 6 ++++ _demo/go/export/runtime_hooks_linux.go | 28 +++++++++++++++++++ _demo/go/export/runtime_hooks_other.go | 8 ++++++ _demo/go/export/use/Makefile | 3 +- _demo/go/export/use/main.c | 11 ++++++++ .../runtime/gettimeofday_linux_amd64_llgo.go | 26 +++++++++++++++++ runtime/internal/lib/runtime/os_darwin.go | 16 +++++++++++ .../lib/runtime/syscall_linux_llgo.go | 25 +++++++++++++++++ 9 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 _demo/go/export/runtime_hooks_linux.go create mode 100644 _demo/go/export/runtime_hooks_other.go create mode 100644 runtime/internal/lib/runtime/gettimeofday_linux_amd64_llgo.go create mode 100644 runtime/internal/lib/runtime/syscall_linux_llgo.go diff --git a/_demo/go/export/export.go b/_demo/go/export/export.go index 2dd5392704..2f2bee1a41 100644 --- a/_demo/go/export/export.go +++ b/_demo/go/export/export.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "runtime" "unsafe" @@ -134,6 +135,11 @@ func RunGoroutine(value int) int { return <-ch } +//export FormatValue +func FormatValue(value string, number int) string { + return fmt.Sprintf("%s:%d", value, number) +} + // Functions with small struct parameters and return values //export CreateSmallStruct diff --git a/_demo/go/export/libexport.h.want b/_demo/go/export/libexport.h.want index 67deee32be..919f305869 100644 --- a/_demo/go/export/libexport.h.want +++ b/_demo/go/export/libexport.h.want @@ -121,6 +121,9 @@ mul(float a, float b); void github_com_goplus_llgo__demo_go_export_c_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/_demo/go/export/c.init") +intptr_t +AllThreadsSyscallStatus(void); + main_ComplexData CreateComplexData(void); @@ -163,6 +166,9 @@ CreateStringMap(void); C_XType CreateXType(int32_t id, GoString name, double value, _Bool flag); +GoString +FormatValue(GoString value, intptr_t number); + main_FuncInfoResult GetFuncInfo(void); diff --git a/_demo/go/export/runtime_hooks_linux.go b/_demo/go/export/runtime_hooks_linux.go new file mode 100644 index 0000000000..5ecf8aa427 --- /dev/null +++ b/_demo/go/export/runtime_hooks_linux.go @@ -0,0 +1,28 @@ +//go:build linux + +package main + +import "syscall" + +func gettimeofdayStatus() int { + var tv syscall.Timeval + if err := syscall.Gettimeofday(&tv); err != nil { + if errno, ok := err.(syscall.Errno); ok { + return int(errno) + } + return int(syscall.EINVAL) + } + if tv.Sec <= 0 { + return int(syscall.EINVAL) + } + return 0 +} + +//export AllThreadsSyscallStatus +func AllThreadsSyscallStatus() int { + if status := gettimeofdayStatus(); status != 0 { + return status + } + _, _, err := syscall.AllThreadsSyscall(syscall.SYS_GETPID, 0, 0, 0) + return int(err) +} diff --git a/_demo/go/export/runtime_hooks_other.go b/_demo/go/export/runtime_hooks_other.go new file mode 100644 index 0000000000..2d5176f5ea --- /dev/null +++ b/_demo/go/export/runtime_hooks_other.go @@ -0,0 +1,8 @@ +//go:build !linux + +package main + +//export AllThreadsSyscallStatus +func AllThreadsSyscallStatus() int { + return 0 +} diff --git a/_demo/go/export/use/Makefile b/_demo/go/export/use/Makefile index a7c75c2722..ee7dcee847 100644 --- a/_demo/go/export/use/Makefile +++ b/_demo/go/export/use/Makefile @@ -24,6 +24,7 @@ else SHARED_EXT = so PLATFORM_LIBS = $(shell pkg-config --libs libunwind 2>/dev/null || echo -lunwind) endif +STATIC_STDLIB_LIBS = $(shell pkg-config --libs libffi 2>/dev/null || echo -lffi) -lresolv # Library and flags based on link type ifeq ($(LINK_TYPE),shared) @@ -35,7 +36,7 @@ ifeq ($(LINK_TYPE),shared) else BUILDMODE = c-archive LIBRARY = ../libexport.a - LDFLAGS = $(LIBRARY) $(RUNTIME_LIBS) $(PLATFORM_LIBS) + LDFLAGS = $(LIBRARY) $(RUNTIME_LIBS) $(PLATFORM_LIBS) $(STATIC_STDLIB_LIBS) BUILD_MSG = "Building Go static library..." LINK_MSG = "Linking with static library..." endif diff --git a/_demo/go/export/use/main.c b/_demo/go/export/use/main.c index 4e1a911b45..4c6534a7c8 100644 --- a/_demo/go/export/use/main.c +++ b/_demo/go/export/use/main.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "../libexport.h" @@ -59,6 +60,16 @@ int main() { HelloWorld(); printf("\n"); + // Verify that a C library can initialize and call standard-library paths + // that depend on the runtime hooks supplied by LLGo. + GoString formatted = FormatValue((GoString){"answer", 6}, 42); + assert(go_string_equals(formatted, "answer:42")); +#ifdef __linux__ + assert(AllThreadsSyscallStatus() == ENOTSUP); +#else + assert(AllThreadsSyscallStatus() == 0); +#endif + // Test small struct main_SmallStruct small = CreateSmallStruct(5, 1); // 1 for true assert(small.ID == 5); diff --git a/runtime/internal/lib/runtime/gettimeofday_linux_amd64_llgo.go b/runtime/internal/lib/runtime/gettimeofday_linux_amd64_llgo.go new file mode 100644 index 0000000000..41c075b0a5 --- /dev/null +++ b/runtime/internal/lib/runtime/gettimeofday_linux_amd64_llgo.go @@ -0,0 +1,26 @@ +//go:build linux && amd64 && !baremetal + +package runtime + +import ( + "unsafe" + + c "github.com/goplus/llgo/runtime/internal/clite" + cliteos "github.com/goplus/llgo/runtime/internal/clite/os" + clitesyscall "github.com/goplus/llgo/runtime/internal/clite/syscall" +) + +//go:linkname c_gettimeofday C.gettimeofday +func c_gettimeofday(tv *clitesyscall.Timeval, tz unsafe.Pointer) c.Int + +// syscall.Gettimeofday and syscall.Time call an amd64 assembly helper. LLGo's +// replacement syscall package does not include that symbol, so forward it to +// libc while preserving syscall's errno result. +// +//go:linkname syscall_gettimeofday syscall.gettimeofday +func syscall_gettimeofday(tv *clitesyscall.Timeval) clitesyscall.Errno { + if c_gettimeofday(tv, nil) != 0 { + return clitesyscall.Errno(cliteos.Errno()) + } + return 0 +} diff --git a/runtime/internal/lib/runtime/os_darwin.go b/runtime/internal/lib/runtime/os_darwin.go index e0f3fa16bb..d4a8cc0f97 100644 --- a/runtime/internal/lib/runtime/os_darwin.go +++ b/runtime/internal/lib/runtime/os_darwin.go @@ -9,11 +9,27 @@ func sysctlbynameInt32(name []byte) (int32, int32) { return ret, out } +func sysctlbynameBytes(name, out []byte) int32 { + nout := uintptr(len(out)) + return sysctlbyname(&name[0], &out[0], &nout, nil, 0) +} + //go:linkname internal_cpu_getsysctlbyname internal/cpu.getsysctlbyname func internal_cpu_getsysctlbyname(name []byte) (int32, int32) { return sysctlbynameInt32(name) } +//go:linkname internal_cpu_sysctlbynameInt32 internal/cpu.sysctlbynameInt32 +func internal_cpu_sysctlbynameInt32(name []byte) (int32, int32) { + return sysctlbynameInt32(name) +} + +//go:linkname internal_cpu_sysctlbynameBytes internal/cpu.sysctlbynameBytes +func internal_cpu_sysctlbynameBytes(name, out []byte) int32 { + return sysctlbynameBytes(name, out) +} + +//go:cgo_import_dynamic libc_sysctlbyname sysctlbyname "/usr/lib/libSystem.B.dylib" var libc_sysctlbyname_trampoline_addr uintptr // adapted from runtime/sys_darwin.go in the pattern of sysctl() above, as defined in x/sys/unix diff --git a/runtime/internal/lib/runtime/syscall_linux_llgo.go b/runtime/internal/lib/runtime/syscall_linux_llgo.go new file mode 100644 index 0000000000..44c07816d4 --- /dev/null +++ b/runtime/internal/lib/runtime/syscall_linux_llgo.go @@ -0,0 +1,25 @@ +//go:build linux + +package runtime + +import ( + "unsafe" + + clitesyscall "github.com/goplus/llgo/runtime/internal/clite/syscall" +) + +// LLGo may run alongside threads created by C libraries, which the Go runtime +// cannot enumerate. Match Go's cgo behavior and report AllThreadsSyscall as +// unsupported rather than changing process credentials on only one thread. +// +//go:linkname syscall_runtime_doAllThreadsSyscall syscall.runtime_doAllThreadsSyscall +//go:uintptrescapes +func syscall_runtime_doAllThreadsSyscall(_, _, _, _, _, _, _ uintptr) (r1, r2, err uintptr) { + return ^uintptr(0), ^uintptr(0), uintptr(clitesyscall.ENOTSUP) +} + +//go:linkname syscall_cgocaller syscall.cgocaller +//go:uintptrescapes +func syscall_cgocaller(_ unsafe.Pointer, _ ...uintptr) uintptr { + return uintptr(clitesyscall.ENOTSUP) +} From 6ffdfff96c368e20323b1ea182e7cf9516c93d84 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 20 Jul 2026 14:48:48 +0800 Subject: [PATCH 2/2] build: initialize C shared library runtime on Linux --- internal/build/main_module.go | 23 ++++++++++++++++++----- internal/build/main_module_test.go | 6 +++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/internal/build/main_module.go b/internal/build/main_module.go index 2a9cac7c18..9742444419 100644 --- a/internal/build/main_module.go +++ b/internal/build/main_module.go @@ -107,7 +107,11 @@ func genMainModule(ctx *context, rtPkgPath string, pkg *packages.Package, cfg *g mainMain := declareNoArgFunc(mainPkg, pkg.PkgPath+".main") if ctx.buildConf.BuildMode != BuildModeExe { - defineLibraryRuntimeInit(mainPkg, pyInit, rtInit, abiInit, runtimeStub, mainInit) + initArraySection := "" + if ctx.buildConf.BuildMode == BuildModeCShared && ctx.buildConf.Goos == "linux" { + initArraySection = ".init_array" + } + defineLibraryRuntimeInit(mainPkg, initArraySection, pyInit, rtInit, abiInit, runtimeStub, mainInit) return mainAPkg } @@ -129,10 +133,11 @@ func genMainModule(ctx *context, rtPkgPath string, pkg *packages.Package, cfg *g } // defineLibraryRuntimeInit arranges for the LLGo runtime to be initialized -// before a C program calls an exported Go function. llvm.global_ctors is -// lowered to the platform's native constructor mechanism for both shared -// libraries and archive members. -func defineLibraryRuntimeInit(pkg llssa.Package, inits ...llssa.Function) { +// before a C program calls an exported Go function. Linux shared libraries use +// .init_array explicitly because the raw LLVM target machine lowers +// llvm.global_ctors to legacy .ctors; other library formats use LLVM's +// platform-specific constructor lowering. +func defineLibraryRuntimeInit(pkg llssa.Package, initArraySection string, inits ...llssa.Function) { const ctorName = "__llgo_runtime_ctor" ctor := pkg.NewFunc(ctorName, llssa.NoArgsNoRet, llssa.InC) ctorValue := pkg.Module().NamedFunction(ctorName) @@ -146,6 +151,14 @@ func defineLibraryRuntimeInit(pkg llssa.Package, inits ...llssa.Function) { b.Return() mod := pkg.Module() + if initArraySection != "" { + initEntry := llvm.AddGlobal(mod, ctorValue.Type(), "__llgo_runtime_ctor_init") + initEntry.SetInitializer(ctorValue) + initEntry.SetGlobalConstant(true) + initEntry.SetSection(initArraySection) + initEntry.SetVisibility(llvm.HiddenVisibility) + return + } llvmCtx := mod.Context() priority := llvm.ConstInt(llvmCtx.Int32Type(), 65535, false) entry := llvmCtx.ConstStruct([]llvm.Value{ diff --git a/internal/build/main_module_test.go b/internal/build/main_module_test.go index a1e6fe9350..7e3ce20b97 100644 --- a/internal/build/main_module_test.go +++ b/internal/build/main_module_test.go @@ -97,10 +97,14 @@ func TestGenMainModuleLibraryInitializesRuntime(t *testing.T) { mod := genMainModule(ctx, llssa.PkgRuntime, pkg, &genConfig{rtInit: true}) ir := mod.LPkg.String() checks := []string{ - "@llvm.global_ctors = appending global", "define internal void @__llgo_runtime_ctor()", "call void @\"github.com/goplus/llgo/runtime/internal/runtime.init\"()", } + if mode == BuildModeCShared { + checks = append(checks, `@__llgo_runtime_ctor_init = hidden constant ptr @__llgo_runtime_ctor, section ".init_array"`) + } else { + checks = append(checks, "@llvm.global_ctors = appending global") + } for _, want := range checks { if !strings.Contains(ir, want) { t.Fatalf("library module IR missing %q:\n%s", want, ir)