Skip to content

Commit ea93037

Browse files
committed
build: resolve late Emscripten LTO stdio dependencies
1 parent bbe9b2a commit ea93037

3 files changed

Lines changed: 70 additions & 0 deletions

File tree

‎internal/build/build.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3018,6 +3018,7 @@ func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose
30183018
defer cleanupHiddenGlobals()
30193019
buildArgs = append(buildArgs, hiddenGlobalArgs...)
30203020
buildArgs = append(buildArgs, debugInfoLinkerArgs(ctx.buildConf, &ctx.crossCompile)...)
3021+
buildArgs = append(buildArgs, emscriptenLTOLibcallArgs(ctx)...)
30213022
ltoPluginFlags, err := ctx.buildConf.LTOPlugin.LinkerFlags(ctx.buildConf.Goos)
30223023
if err != nil {
30233024
return err

‎internal/build/wasm_lto.go‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package build
2+
3+
import "github.com/xgo-dev/llgo/internal/crosscompile"
4+
5+
func emscriptenLTOLibcallArgs(ctx *context) []string {
6+
profile := ctx.crossCompile.WasmProfile
7+
if !ctx.buildConf.ltoEnabled() || (profile != crosscompile.WasmProfileJ32 && profile != crosscompile.WasmProfileJ64) {
8+
return nil
9+
}
10+
// Emscripten 6.0.8 keeps fputc in a native archive member, since LTO can
11+
// introduce it when simplifying fwrite. Its thread helpers remain bitcode,
12+
// which wasm-ld cannot extract after LTO has introduced that native member.
13+
// Resolve them before LTO, using the SDK's stubs or real pthread versions
14+
// according to the selected target. Both Wasm pointer widths are affected.
15+
return []string{"-Wl,--undefined=__get_tp", "-Wl,--undefined=emscripten_futex_wake"}
16+
}

‎internal/build/wasm_lto_test.go‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package build
2+
3+
import (
4+
"os"
5+
"os/exec"
6+
"path/filepath"
7+
"strings"
8+
"testing"
9+
10+
"github.com/xgo-dev/llgo/internal/crosscompile"
11+
"github.com/xgo-dev/llgo/internal/lto"
12+
)
13+
14+
func TestEmscriptenLTOLateStdioLibcall(t *testing.T) {
15+
if testing.Short() {
16+
t.Skip("links and executes Emscripten LTO binaries")
17+
}
18+
for _, tool := range []string{"emcc", "node"} {
19+
if _, err := exec.LookPath(tool); err != nil {
20+
t.Skipf("%s is not installed", tool)
21+
}
22+
}
23+
dir := t.TempDir()
24+
source := filepath.Join(dir, "stdio.c")
25+
if err := os.WriteFile(source, []byte(`#include <stdio.h>
26+
__attribute__((noinline)) void byte(FILE *f) { fwrite("0", 1, 1, f); }
27+
int main(void) { byte(stderr); return 0; }
28+
`), 0600); err != nil {
29+
t.Fatal(err)
30+
}
31+
for _, target := range []struct {
32+
profile crosscompile.WasmProfile
33+
width string
34+
}{{crosscompile.WasmProfileJ32, "-m32"}, {crosscompile.WasmProfileJ64, "-m64"}} {
35+
for _, mode := range []lto.Mode{lto.Thin, lto.Full} {
36+
t.Run(string(target.profile)+"/"+mode.String(), func(t *testing.T) {
37+
ctx := &context{buildConf: &Config{LTO: mode}, crossCompile: crosscompile.Export{WasmProfile: target.profile}}
38+
out := filepath.Join(dir, string(target.profile)+"-"+mode.String()+".mjs")
39+
// Leave fwrite intact until link-time optimization, as happens
40+
// when LLVM specializes LLGo's runtime print buffers during LTO.
41+
args := []string{"-O2", "-flto=" + mode.String(), target.width, "-sEXIT_RUNTIME=1", "-sNODE_HOST_ENV=1", "-Xclang", "-disable-llvm-passes", source, "-o", out}
42+
args = append(args, emscriptenLTOLibcallArgs(ctx)...)
43+
if output, err := exec.CommandContext(t.Context(), "emcc", args...).CombinedOutput(); err != nil {
44+
t.Fatalf("link late stdio libcall: %v\n%s", err, output)
45+
}
46+
runner := `import {pathToFileURL} from 'node:url'; const m = await import(pathToFileURL(process.argv[1]).href); await m.default();`
47+
if output, err := exec.CommandContext(t.Context(), "node", "--input-type=module", "-e", runner, out).CombinedOutput(); err != nil || strings.TrimSpace(string(output)) != "0" {
48+
t.Fatalf("execute late stdio libcall: %v\n%s", err, output)
49+
}
50+
})
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)