Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/sdk-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 18 additions & 19 deletions sdk/src/bin/cc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)}`;
Expand All @@ -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 {
Expand All @@ -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;
Expand Down
27 changes: 27 additions & 0 deletions sdk/test/cc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
Loading