diff --git a/docs/sdk-guide.md b/docs/sdk-guide.md index d4d8e7bad9..1eda6dc11d 100644 --- a/docs/sdk-guide.md +++ b/docs/sdk-guide.md @@ -178,6 +178,12 @@ When linking an executable (not compile-only), the SDK adds: - `sysroot/lib/crt1.o` — C runtime startup - `sysroot/lib/libc.a` — musl libc +The injected glue and startup objects precede user linker inputs, and the final +musl archive follows them. The SDK preserves user source, object, archive, and +`-l` ordering between those boundaries. This also makes an explicit `-lc`, as +emitted by some Autoconf projects, equivalent to the automatically linked libc +without pulling musl's syscall definitions ahead of Kandelo's glue overrides. + ### Sysroot platform libraries `scripts/build-musl.sh` also builds Kandelo's platform graphics shims into the diff --git a/sdk/src/bin/cc.ts b/sdk/src/bin/cc.ts index 33bd8d2c23..9dd032ef79 100755 --- a/sdk/src/bin/cc.ts +++ b/sdk/src/bin/cc.ts @@ -24,6 +24,17 @@ export function buildClangArgs(userArgs: string[], toolchain: Toolchain, arch: W const parsed = parseArgs(filtered); const linking = needsLinking(parsed); const hasSourceFiles = parsed.sourceFiles.length > 0; + const forwardedArgs: string[] = []; + for (let i = 0; i < filtered.length; i++) { + const arg = filtered[i]; + if (arg === '-shared' || arg === '-ldl') continue; + if (arg === '--kandelo-thread-slots' || arg === '--wasm-posix-thread-slots') { + i++; + continue; + } + if (arg.startsWith('--kandelo-thread-slots=') || arg.startsWith('--wasm-posix-thread-slots=')) continue; + forwardedArgs.push(arg); + } const args: string[] = []; const target = `--target=${targetTriple(arch)}`; @@ -39,29 +50,14 @@ export function buildClangArgs(userArgs: string[], toolchain: Toolchain, arch: W } args.push(`--sysroot=${toolchain.sysroot}`); - if (parsed.compileOnly) args.push('-c'); - if (parsed.preprocessOnly) args.push('-E'); - if (parsed.assemblyOnly) args.push('-S'); - if (parsed.outputFile) args.push('-o', parsed.outputFile); - args.push(...parsed.otherArgs); - - args.push(...parsed.sourceFiles); - args.push(...parsed.objectFiles); - args.push(...parsed.archiveFiles); - - // -fPIC is consumed by parseArgs (so the linker can see `parsed.pic`), - // but it must also reach clang at compile time so the resulting object - // uses PIC relocations. Without this a TU later linked into a shared - // library produces non-PIC objects and `wasm-ld --shared` rejects them - // with "R_WASM_MEMORY_ADDR_LEB cannot be used; recompile with -fPIC". - if (parsed.pic) args.push('-fPIC'); - if (linking) { if (parsed.shared) { // Shared library build: no CRT, no libc, no syscall glue - args.push(...SHARED_LINK_FLAGS); + args.push(...forwardedArgs, ...SHARED_LINK_FLAGS); } else { - // Executable build: link CRT, libc, and syscall glue + // Executable build: resolve user libraries after the platform glue that + // overrides musl symbols, then make the final libc archive available for + // everything still unresolved. Preserve the user's link-input order. const threadSlots = inferThreadSlotDeclaration(parsed, userArgs, { readFile: (path) => { try { @@ -84,10 +80,13 @@ export function buildClangArgs(userArgs: string[], toolchain: Toolchain, arch: W } args.push( join(toolchain.sysroot, 'lib', 'crt1.o'), + ...forwardedArgs, join(toolchain.sysroot, 'lib', 'libc.a'), ...linkFlags(arch), ); } + } else { + args.push(...forwardedArgs); } return args; diff --git a/sdk/test/cc.test.ts b/sdk/test/cc.test.ts index 0e9716e6f2..8c29eaf184 100644 --- a/sdk/test/cc.test.ts +++ b/sdk/test/cc.test.ts @@ -44,6 +44,33 @@ describe('buildClangArgs', () => { expect(args.join(' ')).toContain('channel_syscall.c'); }); + it('orders explicit libc and user libraries after syscall glue', () => { + const args = buildClangArgs([ + 'main.o', + '-L', '/deps/lib', + '-lxml2', + 'support.a', + '-lc', + '-o', 'out.wasm', + ], toolchain); + const channelGlue = args.indexOf('/tmp/glue/channel_syscall.c'); + const crt = args.indexOf('/tmp/sysroot/lib/crt1.o'); + const main = args.indexOf('main.o'); + const libraryPath = args.indexOf('-L'); + const xml = args.indexOf('-lxml2'); + const support = args.indexOf('support.a'); + const explicitLibc = args.indexOf('-lc'); + const finalLibc = args.indexOf('/tmp/sysroot/lib/libc.a'); + + expect(channelGlue).toBeLessThan(crt); + expect(crt).toBeLessThan(main); + expect(main).toBeLessThan(libraryPath); + expect(libraryPath).toBeLessThan(xml); + expect(xml).toBeLessThan(support); + expect(support).toBeLessThan(explicitLibc); + expect(explicitLibc).toBeLessThan(finalLibc); + }); + it('preprocess-only: no link flags', () => { const args = buildClangArgs(['-E', 'foo.c'], toolchain); expect(args).not.toContain('-Wl,--entry=_start');