From bf9c6abb2305929f6e316d801aef238c2900be18 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sat, 25 Oct 2025 11:29:16 +0800 Subject: [PATCH 1/6] build: refactor main module generation --- internal/build/build.go | 119 +--------------- internal/build/main_module.go | 221 +++++++++++++++++++++++++++++ internal/build/main_module_test.go | 76 ++++++++++ 3 files changed, 302 insertions(+), 114 deletions(-) create mode 100644 internal/build/main_module.go create mode 100644 internal/build/main_module_test.go diff --git a/internal/build/build.go b/internal/build/build.go index 2c7de4e4..1d3ff2e8 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -782,11 +782,14 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, outputPa } }) // Generate main module file (needed for global variables even in library modes) - entryObjFile, err := genMainModuleFile(ctx, llssa.PkgRuntime, pkg, needRuntime, needPyInit) + entryPkg, err := genMainModule(ctx, llssa.PkgRuntime, pkg, needRuntime, needPyInit) + if err != nil { + return err + } + entryObjFile, err := exportObject(ctx, entryPkg.PkgPath, entryPkg.ExportFile, []byte(entryPkg.LPkg.String())) if err != nil { return err } - // defer os.Remove(entryLLFile) objFiles = append(objFiles, entryObjFile) // Compile extra files from target configuration @@ -908,118 +911,6 @@ func needStart(ctx *context) bool { } } -func genMainModuleFile(ctx *context, rtPkgPath string, pkg *packages.Package, needRuntime, needPyInit bool) (path string, err error) { - var ( - pyInitDecl string - pyInit string - rtInitDecl string - rtInit string - ) - mainPkgPath := pkg.PkgPath - if needRuntime { - rtInit = "call void @\"" + rtPkgPath + ".init\"()" - rtInitDecl = "declare void @\"" + rtPkgPath + ".init\"()" - } - if needPyInit { - pyInit = "call void @Py_Initialize()" - pyInitDecl = "declare void @Py_Initialize()" - } - declSizeT := "%size_t = type i64" - if is32Bits(ctx.buildConf.Goarch) { - declSizeT = "%size_t = type i32" - } - stdioDecl := "" - stdioNobuf := "" - if IsStdioNobuf() { - stdioDecl = ` -@stdout = external global ptr -@stderr = external global ptr -@__stdout = external global ptr -@__stderr = external global ptr -declare i32 @setvbuf(ptr, ptr, i32, %size_t) - ` - stdioNobuf = ` -; Set stdout with no buffer -%stdout_is_null = icmp eq ptr @stdout, null -%stdout_ptr = select i1 %stdout_is_null, ptr @__stdout, ptr @stdout -call i32 @setvbuf(ptr %stdout_ptr, ptr null, i32 2, %size_t 0) -; Set stderr with no buffer -%stderr_ptr = select i1 %stdout_is_null, ptr @__stderr, ptr @stderr -call i32 @setvbuf(ptr %stderr_ptr, ptr null, i32 2, %size_t 0) - ` - } - // TODO(lijie): workaround for libc-free - // Remove main/_start when -buildmode and libc are ready - startDefine := ` -define weak void @_start() { - ; argc = 0 - %argc = add i32 0, 0 - ; argv = null - %argv = inttoptr i64 0 to i8** - call i32 @main(i32 %argc, i8** %argv) - ret void -} -` - mainDefine := "define i32 @main(i32 noundef %0, ptr nocapture noundef readnone %1) local_unnamed_addr" - if !needStart(ctx) && isWasmTarget(ctx.buildConf.Goos) { - mainDefine = "define hidden noundef i32 @__main_argc_argv(i32 noundef %0, ptr nocapture noundef readnone %1) local_unnamed_addr" - } - if !needStart(ctx) { - startDefine = "" - } - - var mainCode string - // For library modes (c-archive, c-shared), only generate global variables - if ctx.buildConf.BuildMode != BuildModeExe { - mainCode = `; ModuleID = 'main' -source_filename = "main" -@__llgo_argc = global i32 0, align 4 -@__llgo_argv = global ptr null, align 8 -` - } else { - // For executable mode, generate full main function - mainCode = fmt.Sprintf(`; ModuleID = 'main' -source_filename = "main" -%s -@__llgo_argc = global i32 0, align 4 -@__llgo_argv = global ptr null, align 8 -%s -%s -%s -declare void @"%s.init"() -declare void @"%s.main"() -define weak void @runtime.init() { - ret void -} - -; TODO(lijie): workaround for syscall patch -define weak void @"syscall.init"() { - ret void -} - -%s - -%s { -_llgo_0: - store i32 %%0, ptr @__llgo_argc, align 4 - store ptr %%1, ptr @__llgo_argv, align 8 - %s - %s - %s - call void @runtime.init() - call void @"%s.init"() - call void @"%s.main"() - ret i32 0 -} -`, declSizeT, stdioDecl, - pyInitDecl, rtInitDecl, mainPkgPath, mainPkgPath, - startDefine, mainDefine, stdioNobuf, - pyInit, rtInit, mainPkgPath, mainPkgPath) - } - - return exportObject(ctx, pkg.PkgPath+".main", pkg.ExportFile+"-main", []byte(mainCode)) -} - func is32Bits(goarch string) bool { return goarch == "386" || goarch == "arm" || goarch == "mips" || goarch == "wasm" } diff --git a/internal/build/main_module.go b/internal/build/main_module.go new file mode 100644 index 00000000..7fc2b433 --- /dev/null +++ b/internal/build/main_module.go @@ -0,0 +1,221 @@ +//go:build !llgo +// +build !llgo + +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package build + +import ( + "go/token" + "go/types" + "runtime" + + "github.com/goplus/llgo/internal/packages" + llvm "github.com/goplus/llvm" + + llssa "github.com/goplus/llgo/ssa" +) + +func genMainModule(ctx *context, rtPkgPath string, pkg *packages.Package, needRuntime, needPyInit bool) (Package, error) { + prog := ctx.prog + mainPkg := prog.NewPackage("", pkg.PkgPath+".main") + + argcVar := mainPkg.NewVarEx("__llgo_argc", prog.Pointer(prog.Int32())) + argcVar.Init(prog.Zero(prog.Int32())) + + charPtrType := prog.Pointer(prog.Byte()) + argvValueType := prog.Pointer(charPtrType) + argvVar := mainPkg.NewVarEx("__llgo_argv", prog.Pointer(argvValueType)) + argvVar.InitNil() + + exportFile := pkg.ExportFile + if exportFile == "" { + exportFile = pkg.PkgPath + } + mainAPkg := &aPackage{ + Package: &packages.Package{ + PkgPath: pkg.PkgPath + ".main", + ExportFile: exportFile + "-main", + }, + LPkg: mainPkg, + } + + if ctx.buildConf.BuildMode != BuildModeExe { + return mainAPkg, nil + } + + runtimeStub := defineWeakNoArgStub(mainPkg, "runtime.init") + defineWeakNoArgStub(mainPkg, "syscall.init") + + var pyInit llssa.Function + if needPyInit { + pyInit = declareNoArgFunc(mainPkg, "Py_Initialize") + } + + var rtInit llssa.Function + if needRuntime { + rtInit = declareNoArgFunc(mainPkg, rtPkgPath+".init") + } + + mainInit := declareNoArgFunc(mainPkg, pkg.PkgPath+".init") + mainMain := declareNoArgFunc(mainPkg, pkg.PkgPath+".main") + + entryFn := defineEntryFunction(ctx, mainPkg, argcVar, argvVar, argvValueType, runtimeStub, mainInit, mainMain, pyInit, rtInit) + + if needStart(ctx) { + defineStart(mainPkg, entryFn, argvValueType) + } + + return mainAPkg, nil +} + +func defineEntryFunction(ctx *context, pkg llssa.Package, argcVar, argvVar llssa.Global, argvType llssa.Type, runtimeStub, mainInit, mainMain llssa.Function, pyInit, rtInit llssa.Function) llssa.Function { + prog := pkg.Prog + entryName := "main" + if !needStart(ctx) && isWasmTarget(ctx.buildConf.Goos) { + entryName = "__main_argc_argv" + } + sig := newEntrySignature(argvType.RawType()) + fn := pkg.NewFunc(entryName, sig, llssa.InC) + fnVal := pkg.Module().NamedFunction(entryName) + if entryName != "main" { + fnVal.SetVisibility(llvm.HiddenVisibility) + fnVal.SetUnnamedAddr(true) + } + b := fn.MakeBody(1) + b.Store(argcVar.Expr, fn.Param(0)) + b.Store(argvVar.Expr, fn.Param(1)) + if IsStdioNobuf() { + emitStdioNobuf(b, pkg, ctx.buildConf.Goarch) + } + if pyInit != nil { + b.Call(pyInit.Expr) + } + if rtInit != nil { + b.Call(rtInit.Expr) + } + b.Call(runtimeStub.Expr) + b.Call(mainInit.Expr) + b.Call(mainMain.Expr) + b.Return(prog.IntVal(0, prog.Int32())) + return fn +} + +func defineStart(pkg llssa.Package, entry llssa.Function, argvType llssa.Type) { + fn := pkg.NewFunc("_start", llssa.NoArgsNoRet, llssa.InC) + pkg.Module().NamedFunction("_start").SetLinkage(llvm.WeakAnyLinkage) + b := fn.MakeBody(1) + prog := pkg.Prog + b.Call(entry.Expr, prog.IntVal(0, prog.Int32()), prog.Nil(argvType)) + b.Return() +} + +func declareNoArgFunc(pkg llssa.Package, name string) llssa.Function { + return pkg.NewFunc(name, llssa.NoArgsNoRet, llssa.InC) +} + +func defineWeakNoArgStub(pkg llssa.Package, name string) llssa.Function { + fn := pkg.NewFunc(name, llssa.NoArgsNoRet, llssa.InC) + pkg.Module().NamedFunction(name).SetLinkage(llvm.WeakAnyLinkage) + b := fn.MakeBody(1) + b.Return() + return fn +} + +func emitStdioNobuf(b llssa.Builder, pkg llssa.Package, goarch string) { + prog := pkg.Prog + streamType := prog.VoidPtr() + streamPtrType := prog.Pointer(streamType) + stdout := declareExternalPtrGlobal(pkg, "stdout", streamType) + stderr := declareExternalPtrGlobal(pkg, "stderr", streamType) + stdoutAlt := declareExternalPtrGlobal(pkg, "__stdout", streamType) + stderrAlt := declareExternalPtrGlobal(pkg, "__stderr", streamType) + sizeType := sizeTypeForArch(prog, goarch) + charPtrType := prog.Pointer(prog.Byte()) + setvbuf := declareSetvbuf(pkg, streamPtrType, charPtrType, prog.Int32(), sizeType) + + stdoutSlot := b.AllocaT(streamPtrType) + b.Store(stdoutSlot, stdout) + stderrSlot := b.AllocaT(streamPtrType) + b.Store(stderrSlot, stderr) + cond := b.BinOp(token.EQL, stdout, prog.Nil(streamPtrType)) + b.IfThen(cond, func() { + b.Store(stdoutSlot, stdoutAlt) + b.Store(stderrSlot, stderrAlt) + }) + stdoutPtr := b.Load(stdoutSlot) + stderrPtr := b.Load(stderrSlot) + + mode := prog.IntVal(2, prog.Int32()) + zeroSize := prog.Zero(sizeType) + nullBuf := prog.Nil(charPtrType) + + b.Call(setvbuf.Expr, stdoutPtr, nullBuf, mode, zeroSize) + b.Call(setvbuf.Expr, stderrPtr, nullBuf, mode, zeroSize) +} + +func declareExternalPtrGlobal(pkg llssa.Package, name string, valueType llssa.Type) llssa.Expr { + ptrType := pkg.Prog.Pointer(valueType) + global := pkg.NewVarEx(name, ptrType) + pkg.Module().NamedGlobal(name).SetLinkage(llvm.ExternalLinkage) + return global.Expr +} + +func declareSetvbuf(pkg llssa.Package, streamPtrType, bufPtrType, intType, sizeType llssa.Type) llssa.Function { + sig := newSignature( + []types.Type{ + streamPtrType.RawType(), + bufPtrType.RawType(), + intType.RawType(), + sizeType.RawType(), + }, + []types.Type{intType.RawType()}, + ) + return pkg.NewFunc("setvbuf", sig, llssa.InC) +} + +func tupleOf(tys ...types.Type) *types.Tuple { + if len(tys) == 0 { + return types.NewTuple() + } + vars := make([]*types.Var, len(tys)) + for i, t := range tys { + vars[i] = types.NewParam(token.NoPos, nil, "", t) + } + return types.NewTuple(vars...) +} + +func newSignature(params []types.Type, results []types.Type) *types.Signature { + return types.NewSignatureType(nil, nil, nil, tupleOf(params...), tupleOf(results...), false) +} + +func newEntrySignature(argvType types.Type) *types.Signature { + return newSignature( + []types.Type{types.Typ[types.Int32], argvType}, + []types.Type{types.Typ[types.Int32]}, + ) +} + +func sizeTypeForArch(prog llssa.Program, arch string) llssa.Type { + if arch == "" { + arch = runtime.GOARCH + } + if is32Bits(arch) { + return prog.Uint32() + } + return prog.Uint64() +} diff --git a/internal/build/main_module_test.go b/internal/build/main_module_test.go new file mode 100644 index 00000000..d7e6e242 --- /dev/null +++ b/internal/build/main_module_test.go @@ -0,0 +1,76 @@ +//go:build !llgo +// +build !llgo + +package build + +import ( + "strings" + "testing" + + "github.com/goplus/llvm" + + "github.com/goplus/llgo/internal/packages" + llssa "github.com/goplus/llgo/ssa" +) + +func init() { + llssa.Initialize(llssa.InitAll) +} + +func TestGenMainModuleExecutable(t *testing.T) { + llvm.InitializeAllTargets() + t.Setenv(llgoStdioNobuf, "") + ctx := &context{ + prog: llssa.NewProgram(nil), + buildConf: &Config{ + BuildMode: BuildModeExe, + Goos: "linux", + Goarch: "amd64", + }, + } + pkg := &packages.Package{PkgPath: "example.com/foo", ExportFile: "foo.a"} + mod, err := genMainModule(ctx, llssa.PkgRuntime, pkg, true, true) + if err != nil { + t.Fatalf("genMainModule() error = %v", err) + } + if mod.ExportFile != "foo.a-main" { + t.Fatalf("unexpected export file: %s", mod.ExportFile) + } + ir := mod.LPkg.String() + checks := []string{ + "define i32 @main(", + "call void @Py_Initialize()", + "call void @\"example.com/foo.init\"()", + "define weak void @_start()", + } + for _, want := range checks { + if !strings.Contains(ir, want) { + t.Fatalf("main module IR missing %q:\n%s", want, ir) + } + } +} + +func TestGenMainModuleLibrary(t *testing.T) { + llvm.InitializeAllTargets() + t.Setenv(llgoStdioNobuf, "") + ctx := &context{ + prog: llssa.NewProgram(nil), + buildConf: &Config{ + BuildMode: BuildModeCArchive, + Goos: "linux", + Goarch: "amd64", + }, + } + pkg := &packages.Package{PkgPath: "example.com/foo", ExportFile: "foo.a"} + mod, err := genMainModule(ctx, llssa.PkgRuntime, pkg, false, false) + if err != nil { + t.Fatalf("genMainModule() error = %v", err) + } + ir := mod.LPkg.String() + if strings.Contains(ir, "define i32 @main") { + t.Fatalf("library mode should not emit main function:\n%s", ir) + } + if !strings.Contains(ir, "@__llgo_argc = global i32 0") { + t.Fatalf("library mode missing argc global:\n%s", ir) + } +} From 7abb46859282f1c1d61dd29a33d6d880033fff38 Mon Sep 17 00:00:00 2001 From: xgopilot Date: Fri, 14 Nov 2025 15:57:00 +0000 Subject: [PATCH 2/6] fix: separate stdout and stderr null checks in emitStdioNobuf The original code incorrectly used the stdout null check condition for both stdout and stderr pointer selection. This caused incorrect behavior when stderr is null but stdout is not, or vice-versa. This fix separates the null checks for stdout and stderr into independent conditions, ensuring each stream is properly selected based on its own null status. Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: cpunion <8459+cpunion@users.noreply.github.com> --- internal/build/main_module.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/internal/build/main_module.go b/internal/build/main_module.go index 7fc2b433..404179a6 100644 --- a/internal/build/main_module.go +++ b/internal/build/main_module.go @@ -150,14 +150,18 @@ func emitStdioNobuf(b llssa.Builder, pkg llssa.Package, goarch string) { stdoutSlot := b.AllocaT(streamPtrType) b.Store(stdoutSlot, stdout) - stderrSlot := b.AllocaT(streamPtrType) - b.Store(stderrSlot, stderr) - cond := b.BinOp(token.EQL, stdout, prog.Nil(streamPtrType)) - b.IfThen(cond, func() { + condOut := b.BinOp(token.EQL, stdout, prog.Nil(streamPtrType)) + b.IfThen(condOut, func() { b.Store(stdoutSlot, stdoutAlt) - b.Store(stderrSlot, stderrAlt) }) stdoutPtr := b.Load(stdoutSlot) + + stderrSlot := b.AllocaT(streamPtrType) + b.Store(stderrSlot, stderr) + condErr := b.BinOp(token.EQL, stderr, prog.Nil(streamPtrType)) + b.IfThen(condErr, func() { + b.Store(stderrSlot, stderrAlt) + }) stderrPtr := b.Load(stderrSlot) mode := prog.IntVal(2, prog.Int32()) From 2a4b2ef023036d830a26541dc308b251b182e391 Mon Sep 17 00:00:00 2001 From: xgopilot Date: Fri, 14 Nov 2025 22:03:36 +0000 Subject: [PATCH 3/6] build: remove error return from genMainModule The genMainModule function never returns an error, so simplified its signature to return only Package. Updated: - genMainModule signature: (Package, error) -> Package - Call site in build.go to not handle error - Both test cases to remove error checking Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: cpunion <8459+cpunion@users.noreply.github.com> --- internal/build/build.go | 5 +---- internal/build/main_module.go | 6 +++--- internal/build/main_module_test.go | 10 ++-------- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/internal/build/build.go b/internal/build/build.go index 1d3ff2e8..73c7c9dd 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -782,10 +782,7 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, outputPa } }) // Generate main module file (needed for global variables even in library modes) - entryPkg, err := genMainModule(ctx, llssa.PkgRuntime, pkg, needRuntime, needPyInit) - if err != nil { - return err - } + entryPkg := genMainModule(ctx, llssa.PkgRuntime, pkg, needRuntime, needPyInit) entryObjFile, err := exportObject(ctx, entryPkg.PkgPath, entryPkg.ExportFile, []byte(entryPkg.LPkg.String())) if err != nil { return err diff --git a/internal/build/main_module.go b/internal/build/main_module.go index 404179a6..760177ce 100644 --- a/internal/build/main_module.go +++ b/internal/build/main_module.go @@ -30,7 +30,7 @@ import ( llssa "github.com/goplus/llgo/ssa" ) -func genMainModule(ctx *context, rtPkgPath string, pkg *packages.Package, needRuntime, needPyInit bool) (Package, error) { +func genMainModule(ctx *context, rtPkgPath string, pkg *packages.Package, needRuntime, needPyInit bool) Package { prog := ctx.prog mainPkg := prog.NewPackage("", pkg.PkgPath+".main") @@ -55,7 +55,7 @@ func genMainModule(ctx *context, rtPkgPath string, pkg *packages.Package, needRu } if ctx.buildConf.BuildMode != BuildModeExe { - return mainAPkg, nil + return mainAPkg } runtimeStub := defineWeakNoArgStub(mainPkg, "runtime.init") @@ -80,7 +80,7 @@ func genMainModule(ctx *context, rtPkgPath string, pkg *packages.Package, needRu defineStart(mainPkg, entryFn, argvValueType) } - return mainAPkg, nil + return mainAPkg } func defineEntryFunction(ctx *context, pkg llssa.Package, argcVar, argvVar llssa.Global, argvType llssa.Type, runtimeStub, mainInit, mainMain llssa.Function, pyInit, rtInit llssa.Function) llssa.Function { diff --git a/internal/build/main_module_test.go b/internal/build/main_module_test.go index d7e6e242..96f87ac6 100644 --- a/internal/build/main_module_test.go +++ b/internal/build/main_module_test.go @@ -29,10 +29,7 @@ func TestGenMainModuleExecutable(t *testing.T) { }, } pkg := &packages.Package{PkgPath: "example.com/foo", ExportFile: "foo.a"} - mod, err := genMainModule(ctx, llssa.PkgRuntime, pkg, true, true) - if err != nil { - t.Fatalf("genMainModule() error = %v", err) - } + mod := genMainModule(ctx, llssa.PkgRuntime, pkg, true, true) if mod.ExportFile != "foo.a-main" { t.Fatalf("unexpected export file: %s", mod.ExportFile) } @@ -62,10 +59,7 @@ func TestGenMainModuleLibrary(t *testing.T) { }, } pkg := &packages.Package{PkgPath: "example.com/foo", ExportFile: "foo.a"} - mod, err := genMainModule(ctx, llssa.PkgRuntime, pkg, false, false) - if err != nil { - t.Fatalf("genMainModule() error = %v", err) - } + mod := genMainModule(ctx, llssa.PkgRuntime, pkg, false, false) ir := mod.LPkg.String() if strings.Contains(ir, "define i32 @main") { t.Fatalf("library mode should not emit main function:\n%s", ir) From 8c7e8b6290db84150d161c0c43e81037d28488a3 Mon Sep 17 00:00:00 2001 From: xgopilot Date: Sun, 16 Nov 2025 04:35:41 +0000 Subject: [PATCH 4/6] build: apply review feedback on main module generation --- internal/build/main_module.go | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/internal/build/main_module.go b/internal/build/main_module.go index 760177ce..8076bf61 100644 --- a/internal/build/main_module.go +++ b/internal/build/main_module.go @@ -22,7 +22,6 @@ package build import ( "go/token" "go/types" - "runtime" "github.com/goplus/llgo/internal/packages" llvm "github.com/goplus/llvm" @@ -32,13 +31,12 @@ import ( func genMainModule(ctx *context, rtPkgPath string, pkg *packages.Package, needRuntime, needPyInit bool) Package { prog := ctx.prog - mainPkg := prog.NewPackage("", pkg.PkgPath+".main") + mainPkg := prog.NewPackage("", pkg.ID+".main") argcVar := mainPkg.NewVarEx("__llgo_argc", prog.Pointer(prog.Int32())) argcVar.Init(prog.Zero(prog.Int32())) - charPtrType := prog.Pointer(prog.Byte()) - argvValueType := prog.Pointer(charPtrType) + argvValueType := prog.Pointer(prog.CStr()) argvVar := mainPkg.NewVarEx("__llgo_argv", prog.Pointer(argvValueType)) argvVar.InitNil() @@ -59,6 +57,7 @@ func genMainModule(ctx *context, rtPkgPath string, pkg *packages.Package, needRu } runtimeStub := defineWeakNoArgStub(mainPkg, "runtime.init") + // TODO(lijie): workaround for syscall patch defineWeakNoArgStub(mainPkg, "syscall.init") var pyInit llssa.Function @@ -144,9 +143,8 @@ func emitStdioNobuf(b llssa.Builder, pkg llssa.Package, goarch string) { stderr := declareExternalPtrGlobal(pkg, "stderr", streamType) stdoutAlt := declareExternalPtrGlobal(pkg, "__stdout", streamType) stderrAlt := declareExternalPtrGlobal(pkg, "__stderr", streamType) - sizeType := sizeTypeForArch(prog, goarch) - charPtrType := prog.Pointer(prog.Byte()) - setvbuf := declareSetvbuf(pkg, streamPtrType, charPtrType, prog.Int32(), sizeType) + sizeType := prog.Uintptr() + setvbuf := declareSetvbuf(pkg, streamPtrType, prog.CStr(), prog.Int32(), sizeType) stdoutSlot := b.AllocaT(streamPtrType) b.Store(stdoutSlot, stdout) @@ -166,7 +164,7 @@ func emitStdioNobuf(b llssa.Builder, pkg llssa.Package, goarch string) { mode := prog.IntVal(2, prog.Int32()) zeroSize := prog.Zero(sizeType) - nullBuf := prog.Nil(charPtrType) + nullBuf := prog.Nil(prog.CStr()) b.Call(setvbuf.Expr, stdoutPtr, nullBuf, mode, zeroSize) b.Call(setvbuf.Expr, stderrPtr, nullBuf, mode, zeroSize) @@ -213,13 +211,3 @@ func newEntrySignature(argvType types.Type) *types.Signature { []types.Type{types.Typ[types.Int32]}, ) } - -func sizeTypeForArch(prog llssa.Program, arch string) llssa.Type { - if arch == "" { - arch = runtime.GOARCH - } - if is32Bits(arch) { - return prog.Uint32() - } - return prog.Uint64() -} From 1473ee98f79ca3094d014856a9b550c41b032662 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sun, 16 Nov 2025 19:05:13 +0800 Subject: [PATCH 5/6] build: add package and function docs --- internal/build/main_module.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/internal/build/main_module.go b/internal/build/main_module.go index 8076bf61..09601bc0 100644 --- a/internal/build/main_module.go +++ b/internal/build/main_module.go @@ -17,6 +17,13 @@ * limitations under the License. */ +// Package build contains the llgo compiler build orchestration logic. +// +// The main_module.go file generates the entry point module for llgo programs, +// which contains the main() function, initialization sequence, and global +// variables like argc/argv. This module is generated differently depending on +// BuildMode (exe, c-archive, c-shared). + package build import ( @@ -29,6 +36,11 @@ import ( llssa "github.com/goplus/llgo/ssa" ) +// genMainModule generates the main entry module for an llgo program. +// +// The module contains argc/argv globals and, for executable build modes, +// the entry function that wires initialization and main. For C archive or +// shared library modes, only the globals are emitted. func genMainModule(ctx *context, rtPkgPath string, pkg *packages.Package, needRuntime, needPyInit bool) Package { prog := ctx.prog mainPkg := prog.NewPackage("", pkg.ID+".main") @@ -82,6 +94,13 @@ func genMainModule(ctx *context, rtPkgPath string, pkg *packages.Package, needRu return mainAPkg } +// defineEntryFunction creates the program's entry function. The name is +// "main" for standard targets, or "__main_argc_argv" with hidden visibility +// for WASM targets that don't require _start. +// +// The entry stores argc/argv, optionally disables stdio buffering, runs +// initialization hooks (Python, runtime, package init), and finally calls +// main.main before returning 0. func defineEntryFunction(ctx *context, pkg llssa.Package, argcVar, argvVar llssa.Global, argvType llssa.Type, runtimeStub, mainInit, mainMain llssa.Function, pyInit, rtInit llssa.Function) llssa.Function { prog := pkg.Prog entryName := "main" @@ -136,6 +155,9 @@ func defineWeakNoArgStub(pkg llssa.Package, name string) llssa.Function { } func emitStdioNobuf(b llssa.Builder, pkg llssa.Package, goarch string) { +// emitStdioNobuf generates code to disable buffering on stdout and stderr +// when the LLGO_STDIO_NOBUF environment variable is set. Each stream is +// checked independently so missing stdio symbols are handled gracefully. prog := pkg.Prog streamType := prog.VoidPtr() streamPtrType := prog.Pointer(streamType) From 7e4e53eb1c103935122d8d3a2cd02d0cd85a7a85 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sun, 16 Nov 2025 19:07:34 +0800 Subject: [PATCH 6/6] build: refactor emitStdioNobuf for performance and readability --- internal/build/build_test.go | 5 ++++ internal/build/main_module.go | 54 ++++++++++++++++------------------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/internal/build/build_test.go b/internal/build/build_test.go index c71f0d74..65aba541 100644 --- a/internal/build/build_test.go +++ b/internal/build/build_test.go @@ -159,3 +159,8 @@ func runBinary(t *testing.T, path string, args ...string) string { } return string(output) } + +func TestRunPrintfWithStdioNobuf(t *testing.T) { + t.Setenv(llgoStdioNobuf, "1") + mockRun([]string{"../../cl/_testdata/printf"}, &Config{Mode: ModeRun}) +} diff --git a/internal/build/main_module.go b/internal/build/main_module.go index 09601bc0..959debe4 100644 --- a/internal/build/main_module.go +++ b/internal/build/main_module.go @@ -118,7 +118,7 @@ func defineEntryFunction(ctx *context, pkg llssa.Package, argcVar, argvVar llssa b.Store(argcVar.Expr, fn.Param(0)) b.Store(argvVar.Expr, fn.Param(1)) if IsStdioNobuf() { - emitStdioNobuf(b, pkg, ctx.buildConf.Goarch) + emitStdioNobuf(b, pkg, ctx.buildConf.Goos) } if pyInit != nil { b.Call(pyInit.Expr) @@ -154,47 +154,43 @@ func defineWeakNoArgStub(pkg llssa.Package, name string) llssa.Function { return fn } -func emitStdioNobuf(b llssa.Builder, pkg llssa.Package, goarch string) { +const ( + // ioNoBuf represents the _IONBF flag for setvbuf (no buffering) + ioNoBuf = 2 +) + // emitStdioNobuf generates code to disable buffering on stdout and stderr -// when the LLGO_STDIO_NOBUF environment variable is set. Each stream is -// checked independently so missing stdio symbols are handled gracefully. +// when the LLGO_STDIO_NOBUF environment variable is set. Only Darwin uses +// the alternate `__stdoutp`/`__stderrp` symbols; other targets rely on the +// standard `stdout`/`stderr` globals. +func emitStdioNobuf(b llssa.Builder, pkg llssa.Package, goos string) { prog := pkg.Prog streamType := prog.VoidPtr() streamPtrType := prog.Pointer(streamType) - stdout := declareExternalPtrGlobal(pkg, "stdout", streamType) - stderr := declareExternalPtrGlobal(pkg, "stderr", streamType) - stdoutAlt := declareExternalPtrGlobal(pkg, "__stdout", streamType) - stderrAlt := declareExternalPtrGlobal(pkg, "__stderr", streamType) + + stdoutName := "stdout" + stderrName := "stderr" + if goos == "darwin" { + stdoutName = "__stdoutp" + stderrName = "__stderrp" + } + stdout := declareExternalPtrGlobal(pkg, stdoutName, streamPtrType) + stderr := declareExternalPtrGlobal(pkg, stderrName, streamPtrType) + stdoutPtr := b.Load(stdout) + stderrPtr := b.Load(stderr) sizeType := prog.Uintptr() setvbuf := declareSetvbuf(pkg, streamPtrType, prog.CStr(), prog.Int32(), sizeType) - stdoutSlot := b.AllocaT(streamPtrType) - b.Store(stdoutSlot, stdout) - condOut := b.BinOp(token.EQL, stdout, prog.Nil(streamPtrType)) - b.IfThen(condOut, func() { - b.Store(stdoutSlot, stdoutAlt) - }) - stdoutPtr := b.Load(stdoutSlot) - - stderrSlot := b.AllocaT(streamPtrType) - b.Store(stderrSlot, stderr) - condErr := b.BinOp(token.EQL, stderr, prog.Nil(streamPtrType)) - b.IfThen(condErr, func() { - b.Store(stderrSlot, stderrAlt) - }) - stderrPtr := b.Load(stderrSlot) - - mode := prog.IntVal(2, prog.Int32()) + noBufMode := prog.IntVal(ioNoBuf, prog.Int32()) zeroSize := prog.Zero(sizeType) nullBuf := prog.Nil(prog.CStr()) - b.Call(setvbuf.Expr, stdoutPtr, nullBuf, mode, zeroSize) - b.Call(setvbuf.Expr, stderrPtr, nullBuf, mode, zeroSize) + b.Call(setvbuf.Expr, stdoutPtr, nullBuf, noBufMode, zeroSize) + b.Call(setvbuf.Expr, stderrPtr, nullBuf, noBufMode, zeroSize) } func declareExternalPtrGlobal(pkg llssa.Package, name string, valueType llssa.Type) llssa.Expr { - ptrType := pkg.Prog.Pointer(valueType) - global := pkg.NewVarEx(name, ptrType) + global := pkg.NewVarEx(name, valueType) pkg.Module().NamedGlobal(name).SetLinkage(llvm.ExternalLinkage) return global.Expr }