diff --git a/internal/build/build.go b/internal/build/build.go index cf5cc2e8..e97c135d 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -42,8 +42,8 @@ import ( "github.com/goplus/llgo/internal/mockable" "github.com/goplus/llgo/internal/packages" "github.com/goplus/llgo/internal/typepatch" - llvmTarget "github.com/goplus/llgo/internal/xtool/llvm" "github.com/goplus/llgo/ssa/abi" + "github.com/goplus/llgo/xtool/clang" xenv "github.com/goplus/llgo/xtool/env" "github.com/goplus/llgo/xtool/env/llvm" @@ -247,7 +247,7 @@ func Do(args []string, conf *Config) ([]Package, error) { os.Setenv("PATH", env.BinDir()+":"+os.Getenv("PATH")) // TODO(xsw): check windows output := conf.OutFile != "" - export, err := crosscompile.UseCrossCompileSDK(conf.Goos, conf.Goarch, IsWasiThreadsEnabled()) + export, err := crosscompile.Use(conf.Goos, conf.Goarch, IsWasiThreadsEnabled(), IsRpathChangeEnabled()) check(err) ctx := &context{env, cfg, progSSA, prog, dedup, patches, make(map[string]none), initial, mode, 0, output, make(map[*packages.Package]bool), make(map[*packages.Package]bool), conf, export} pkgs, err := buildAllPkgs(ctx, initial, verbose) @@ -334,6 +334,15 @@ type context struct { crossCompile crosscompile.Export } +func (c *context) compiler() *clang.Cmd { + cmd := c.env.Clang() + if c.crossCompile.CC != "" { + cmd = clang.New(c.crossCompile.CC) + } + cmd.Verbose = c.buildConf.Verbose + return cmd +} + func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs []*aPackage, err error) { pkgs, errPkgs := allPkgs(ctx, initial, verbose) for _, errPkg := range errPkgs { @@ -404,7 +413,7 @@ func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs ctx.nLibdir++ } } - if err := ctx.env.Clang().CheckLinkArgs(pkgLinkArgs); err != nil { + if err := ctx.compiler().CheckLinkArgs(pkgLinkArgs, isWasmTarget(ctx.buildConf.Goos)); err != nil { panic(fmt.Sprintf("test link args '%s' failed\n\texpanded to: %v\n\tresolved to: %v\n\terror: %v", param, expdArgs, pkgLinkArgs, err)) } aPkg.LinkArgs = append(aPkg.LinkArgs, pkgLinkArgs...) @@ -434,7 +443,7 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, conf *Co } else { app = filepath.Join(conf.BinPath, name+conf.AppExt) } - } else if !strings.HasSuffix(app, conf.AppExt) { + } else if filepath.Ext(app) == "" { app += conf.AppExt } @@ -550,123 +559,23 @@ func compileAndLinkLLFiles(ctx *context, app string, llFiles, linkArgs []string, buildArgs = append(buildArgs, linkArgs...) // Add common linker arguments based on target OS and architecture - targetTriple := llvmTarget.GetTargetTriple(ctx.buildConf.Goos, ctx.buildConf.Goarch) - buildArgs = append(buildArgs, buildLdflags(ctx.buildConf.Goos, ctx.buildConf.Goarch, targetTriple)...) - if IsDbgSymsEnabled() { buildArgs = append(buildArgs, "-gdwarf-4") } buildArgs = append(buildArgs, ctx.crossCompile.CCFLAGS...) buildArgs = append(buildArgs, ctx.crossCompile.LDFLAGS...) + buildArgs = append(buildArgs, ctx.crossCompile.EXTRAFLAGS...) buildArgs = append(buildArgs, llFiles...) if verbose { buildArgs = append(buildArgs, "-v") } - cmd := ctx.env.Clang() + cmd := ctx.compiler() cmd.Verbose = verbose return cmd.Link(buildArgs...) } -func buildCflags(goos, goarch, targetTriple string) []string { - args := []string{} - if goarch == "wasm" { - args = append(args, "-target", targetTriple) - } - return args -} - -// buildLdflags builds the common linker arguments based on target OS and architecture -func buildLdflags(goos, goarch, targetTriple string) []string { - args := []string{ - "-target", targetTriple, - "-Wno-override-module", - "-Wl,--error-limit=0", - } - if goos == runtime.GOOS { - // Non-cross-compile - args = append(args, - "-fuse-ld=lld", - "-Wno-override-module", - ) - } - - switch goos { - case "darwin": // ld64.lld (macOS) - if IsRpathChangeEnabled() { - args = append( - args, - "-rpath", "@loader_path", - "-rpath", "@loader_path/../lib", - ) - } - args = append( - args, - "-Xlinker", "-dead_strip", - ) - case "windows": // lld-link (Windows) - // TODO(xsw): Add options for Windows. - case "wasi", "wasip1", "js": // wasm-ld (WebAssembly) - args = append( - args, - // "-fdata-sections", - // "-ffunction-sections", - // "-nostdlib", - // "-Wl,--no-entry", - "-Wl,--export-all", - "-Wl,--allow-undefined", - "-Wl,--import-memory,", // unknown import: `env::memory` has not been defined - "-Wl,--export-memory", - "-Wl,--initial-memory=67108864", // 64MB - "-mbulk-memory", - "-mmultimemory", - "-z", "stack-size=10485760", // 10MB - "-Wl,--export=malloc", "-Wl,--export=free", - "-lc", - "-lcrypt", - "-lm", - "-lrt", - "-lutil", - // "-lxnet", - // "-lresolv", - "-lsetjmp", - "-lwasi-emulated-mman", - "-lwasi-emulated-getpid", - "-lwasi-emulated-process-clocks", - "-lwasi-emulated-signal", - "-fwasm-exceptions", - "-mllvm", "-wasm-enable-sjlj", - // "-mllvm", "-wasm-enable-eh", // unreachable error if enabled - // "-mllvm", "-wasm-disable-explicit-locals", // WASM module load failed: type mismatch: expect data but stack was empty if enabled - ) - if IsWasiThreadsEnabled() { - args = append( - args, - "-lwasi-emulated-pthread", - "-lpthread", - "-pthread", // global is immutable if -pthread is not specified - // "-matomics", // undefined symbol: __atomic_load - ) - } - default: // ld.lld (Unix) - args = append( - args, - // "-rpath", "$ORIGIN", - // "-rpath", "$ORIGIN/../lib", - "-fdata-sections", - "-ffunction-sections", - "-Xlinker", - "--gc-sections", - "-lm", - "-latomic", - "-lpthread", // libpthread is built-in since glibc 2.34 (2021-08-01); we need to support earlier versions. - ) - } - - return args -} - func isWasmTarget(goos string) bool { return slices.Contains([]string{"wasi", "js", "wasip1"}, goos) } @@ -1042,16 +951,13 @@ func clFiles(ctx *context, files string, pkg *packages.Package, procFile func(li func clFile(ctx *context, args []string, cFile, expFile string, procFile func(linkFile string), verbose bool) { llFile := expFile + filepath.Base(cFile) + ".ll" - targetTriple := llvmTarget.GetTargetTriple(ctx.buildConf.Goos, ctx.buildConf.Goarch) - cflags := buildCflags(ctx.buildConf.Goos, ctx.buildConf.Goarch, targetTriple) - args = append(cflags, args...) args = append(args, "-emit-llvm", "-S", "-o", llFile, "-c", cFile) args = append(args, ctx.crossCompile.CCFLAGS...) args = append(args, ctx.crossCompile.CFLAGS...) if verbose { fmt.Fprintln(os.Stderr, "clang", args) } - cmd := ctx.env.Clang() + cmd := ctx.compiler() err := cmd.Compile(args...) check(err) procFile(llFile) diff --git a/internal/crosscompile/cosscompile.go b/internal/crosscompile/cosscompile.go index 27cfe43f..8865aba6 100644 --- a/internal/crosscompile/cosscompile.go +++ b/internal/crosscompile/cosscompile.go @@ -12,9 +12,11 @@ import ( ) type Export struct { - CCFLAGS []string - CFLAGS []string - LDFLAGS []string + CC string // Compiler to use + CCFLAGS []string + CFLAGS []string + LDFLAGS []string + EXTRAFLAGS []string } const wasiSdkUrl = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-macos.tar.gz" @@ -23,44 +25,168 @@ func cacheDir() string { return filepath.Join(env.LLGoCacheDir(), "crosscompile") } -func UseCrossCompileSDK(goos, goarch string, wasiThreads bool) (export Export, err error) { +func Use(goos, goarch string, wasiThreads, changeRpath bool) (export Export, err error) { + targetTriple := llvm.GetTargetTriple(goos, goarch) + if runtime.GOOS == goos && runtime.GOARCH == goarch { // not cross compile + // Set up basic flags for non-cross-compile + export.LDFLAGS = []string{ + "-target", targetTriple, + "-Wno-override-module", + "-Wl,--error-limit=0", + "-fuse-ld=lld", + "-Wno-override-module", + } + + // Add OS-specific flags + switch goos { + case "darwin": // ld64.lld (macOS) + if changeRpath { + export.LDFLAGS = append( + export.LDFLAGS, + "-rpath", "@loader_path", + "-rpath", "@loader_path/../lib", + ) + } + export.LDFLAGS = append( + export.LDFLAGS, + "-Xlinker", "-dead_strip", + ) + case "windows": // lld-link (Windows) + // TODO(lijie): Add options for Windows. + default: // ld.lld (Unix) + export.LDFLAGS = append( + export.LDFLAGS, + "-fdata-sections", + "-ffunction-sections", + "-Xlinker", + "--gc-sections", + "-lm", + "-latomic", + "-lpthread", // libpthread is built-in since glibc 2.34 (2021-08-01); we need to support earlier versions. + ) + } return } - if goarch == "wasm" { + if goarch != "wasm" { + return + } + + // Configure based on GOOS + switch goos { + case "wasip1": sdkDir := filepath.Join(cacheDir(), llvm.GetTargetTriple(goos, goarch)) if _, err = os.Stat(sdkDir); err != nil { if !errors.Is(err, fs.ErrNotExist) { return } + if err = downloadAndExtract(wasiSdkUrl, sdkDir); err != nil { return } } + // WASI-SDK configuration triple := "wasm32-wasip1" if wasiThreads { triple = "wasm32-wasip1-threads" } - // Set up flags for the SDK + + // Set up flags for the WASI-SDK wasiSdkRoot := filepath.Join(sdkDir, "wasi-sdk-25.0-x86_64-macos") sysrootDir := filepath.Join(wasiSdkRoot, "share", "wasi-sysroot") libclangDir := filepath.Join(wasiSdkRoot, "lib", "clang", "19") includeDir := filepath.Join(sysrootDir, "include", triple) libDir := filepath.Join(sysrootDir, "lib", triple) + // Use system clang and sysroot of wasi-sdk + // Add compiler flags export.CCFLAGS = []string{ + "-target", targetTriple, "--sysroot=" + sysrootDir, "-resource-dir=" + libclangDir, } export.CFLAGS = []string{ "-I" + includeDir, } + // Add WebAssembly linker flags export.LDFLAGS = []string{ + "-target", targetTriple, + "-Wno-override-module", + "-Wl,--error-limit=0", "-L" + libDir, + "-Wl,--allow-undefined", + "-Wl,--import-memory,", // unknown import: `env::memory` has not been defined + "-Wl,--export-memory", + "-Wl,--initial-memory=67108864", // 64MB + "-mbulk-memory", + "-mmultimemory", + "-z", "stack-size=10485760", // 10MB + "-Wl,--export=malloc", "-Wl,--export=free", + "-lc", + "-lcrypt", + "-lm", + "-lrt", + "-lutil", + "-lsetjmp", + "-lwasi-emulated-mman", + "-lwasi-emulated-getpid", + "-lwasi-emulated-process-clocks", + "-lwasi-emulated-signal", + "-fwasm-exceptions", + "-mllvm", "-wasm-enable-sjlj", } + // Add thread support if enabled + if wasiThreads { + export.LDFLAGS = append( + export.LDFLAGS, + "-lwasi-emulated-pthread", + "-lpthread", + "-pthread", // global is immutable if -pthread is not specified + ) + } + + case "js": + targetTriple := "wasm32-unknown-emscripten" + // Emscripten configuration using system installation + // Specify emcc as the compiler + export.CC = "emcc" + // Add compiler flags + export.CCFLAGS = []string{ + "-target", targetTriple, + } + export.CFLAGS = []string{} + // Add WebAssembly linker flags for Emscripten + export.LDFLAGS = []string{ + "-target", targetTriple, + "-Wno-override-module", + "-Wl,--error-limit=0", + "-s", "ALLOW_MEMORY_GROWTH=1", + "-Wl,--allow-undefined", + // "-Wl,--import-memory,", + // "-Wl,--export-memory", + // "-Wl,--initial-memory=67108864", // 64MB + // "-mbulk-memory", + // "-mmultimemory", + // "-z", "stack-size=10485760", // 10MB + // "-Wl,--export=malloc", "-Wl,--export=free", + } + export.EXTRAFLAGS = []string{ + "-sENVIRONMENT=web", + "-DPLATFORM_WEB", + "-sEXPORT_KEEPALIVE=1", + "-sEXPORT_ES6=1", + "-sALLOW_MEMORY_GROWTH=1", + "-sEXPORTED_RUNTIME_METHODS=cwrap,allocateUTF8,stringToUTF8,UTF8ToString,FS,setValue,getValue", + "-sWASM=1", + "-sEXPORT_ALL=1", + "-sASYNCIFY=1", + "-sSTACK_SIZE=5242880", // 50MB + } + + default: + err = errors.New("unsupported GOOS for WebAssembly: " + goos) return } - // TODO(lijie): supports other platforms return } diff --git a/internal/crosscompile/crosscompile_test.go b/internal/crosscompile/crosscompile_test.go index 9a32eff3..d988171a 100644 --- a/internal/crosscompile/crosscompile_test.go +++ b/internal/crosscompile/crosscompile_test.go @@ -75,7 +75,7 @@ func TestUseCrossCompileSDK(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - export, err := UseCrossCompileSDK(tc.goos, tc.goarch, false) + export, err := Use(tc.goos, tc.goarch, false, false) if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -146,7 +146,7 @@ func TestUseCrossCompileSDK(t *testing.T) { } } } else { - if len(export.CCFLAGS) != 0 || len(export.CFLAGS) != 0 || len(export.LDFLAGS) != 0 { + if len(export.CCFLAGS) != 0 || len(export.CFLAGS) != 0 { t.Errorf("Expected empty export, got CCFLAGS=%v, CFLAGS=%v, LDFLAGS=%v", export.CCFLAGS, export.CFLAGS, export.LDFLAGS) }