Skip to content
Closed
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: 3 additions & 3 deletions docs/porting-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -720,9 +720,9 @@ Formulae should use normal Homebrew DSL and call the normal Kandelo build path:

- build through the worktree-local SDK, usually by invoking the package's
existing `packages/registry/<name>/build-*.sh` script;
- keep cross-compile truth in the package build script with explicit
`ac_cv_*` cache variables when upstream `configure` would otherwise detect
host features;
- keep shared sysroot availability facts in `sdk/config.site`; use explicit
package `ac_cv_*` values only for package-specific runtime or semantic probes
that cross-compilation cannot execute;
- install the produced Wasm files into the Homebrew keg, not into Kandelo's
resolver cache;
- put `test do` coverage through Kandelo, for example by running the produced
Expand Down
20 changes: 20 additions & 0 deletions docs/sdk-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ wasm32posix-ar rcs libfoo.a lib_a.o lib_b.o
wasm32posix-cc main.c -L. -lfoo -o program.wasm
```

The compiler wrapper preserves the caller's linker-input order. Objects,
explicit archives, `-l` libraries, and linker group controls reach Clang in
the same relative sequence supplied by the build system, as required by
normal static archive resolution.

### With dynamic loading (dlopen)

```bash
Expand Down Expand Up @@ -251,6 +256,21 @@ This sets:
- `--host=wasm32-unknown-none`
- `--build` (auto-detected from host system)

The wrapper also loads `sdk/config.site`. That file is authoritative for
shared target facts that cross-compilation cannot discover reliably, including
functions present in the Kandelo musl sysroot and extension functions that are
absent from it. Keep package-specific runtime or semantic probe results in the
package recipe, but add reusable sysroot availability facts to `config.site`
instead of duplicating them across packages. An explicitly exported
`CONFIG_SITE` overrides the SDK default.

Dynamic loading is an important exception to link-only detection. Musl carries
weak `dlopen`/`dlsym` stubs that link but only report that dynamic loading is
unsupported. `config.site` therefore directs `AC_SEARCH_LIBS` checks for the
dlfcn API to `-ldl`; the SDK compiler wrapper interprets that library request
by linking Kandelo's functional Wasm dynamic-loading glue. A configure result
of `none required` for those searches would select the nonfunctional stubs.

### Example: building dash

```bash
Expand Down
11 changes: 0 additions & 11 deletions packages/registry/wget/build-wget.sh
Original file line number Diff line number Diff line change
Expand Up @@ -208,17 +208,6 @@ if [ ! -f Makefile ]; then
# RAND_egd was removed in OpenSSL 3.x
export ac_cv_func_RAND_egd=no

# group_member is a glibc extension not in musl.
# wget uses it unconditionally in utils.c without #ifdef guard.
# Patch the source to add a declaration before use.
if ! grep -q 'group_member stub' "$SRC_DIR/src/utils.c"; then
sed -i.bak '/#include "wget.h"/a\
/* musl lacks group_member(); stub always returns 0. group_member stub */\
static inline int group_member(int gid) { (void)gid; return 0; }
' "$SRC_DIR/src/utils.c"
rm -f "$SRC_DIR/src/utils.c.bak"
fi

wasm32posix-configure \
--disable-nls \
--disable-iri \
Expand Down
2 changes: 1 addition & 1 deletion packages/registry/wget/build.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
script_path = "packages/registry/wget/build-wget.sh"
repo_url = "https://github.com/brandonpayton/kandelo.git"
commit = "8c53383229fab78f97b098c3207a655159c03041"
revision = 2
revision = 3

[binary]
index_url = "https://github.com/Automattic/kandelo/releases/download/binaries-abi-v{abi}/index.toml"
60 changes: 56 additions & 4 deletions sdk/config.site
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ ac_cv_func_uname=${ac_cv_func_uname=yes}

# Signals
ac_cv_func_alarm=${ac_cv_func_alarm=yes}
ac_cv_func_bsd_signal=${ac_cv_func_bsd_signal=yes}
ac_cv_func_sigaction=${ac_cv_func_sigaction=yes}
ac_cv_func_sigaltstack=${ac_cv_func_sigaltstack=yes}
ac_cv_func_sigfillset=${ac_cv_func_sigfillset=yes}
Expand Down Expand Up @@ -380,6 +381,7 @@ ac_cv_func_bsearch=${ac_cv_func_bsearch=yes}
ac_cv_func_fopencookie=${ac_cv_func_fopencookie=yes}
ac_cv_func_imaxdiv=${ac_cv_func_imaxdiv=yes}
ac_cv_func_ioctl=${ac_cv_func_ioctl=yes}
ac_cv_func_isalpha=${ac_cv_func_isalpha=yes}
ac_cv_func_isdigit=${ac_cv_func_isdigit=yes}
ac_cv_func_lutimes=${ac_cv_func_lutimes=yes}
ac_cv_func_memcmp=${ac_cv_func_memcmp=yes}
Expand All @@ -389,6 +391,7 @@ ac_cv_func_sethostname=${ac_cv_func_sethostname=yes}
ac_cv_func_strerror=${ac_cv_func_strerror=yes}
ac_cv_func_strerror_r=${ac_cv_func_strerror_r=yes}
ac_cv_func_strchrnul=${ac_cv_func_strchrnul=yes}
ac_cv_func_stpcpy=${ac_cv_func_stpcpy=yes}
ac_cv_func_strlcat=${ac_cv_func_strlcat=yes}
ac_cv_func_strlcpy=${ac_cv_func_strlcpy=yes}
ac_cv_func_strncasecmp=${ac_cv_func_strncasecmp=yes}
Expand All @@ -414,43 +417,77 @@ ac_cv_func_closedir_void=${ac_cv_func_closedir_void=no}

# BSD/macOS-specific (never in musl)
ac_cv_func_arc4random=${ac_cv_func_arc4random=no}
ac_cv_func_cap_rights_limit=${ac_cv_func_cap_rights_limit=no}
ac_cv_func_chflags=${ac_cv_func_chflags=no}
ac_cv_func_copyfile=${ac_cv_func_copyfile=no}
ac_cv_func_fclonefileat=${ac_cv_func_fclonefileat=no}
ac_cv_func_fcopyfile=${ac_cv_func_fcopyfile=no}
ac_cv_func_fgetattrlist=${ac_cv_func_fgetattrlist=no}
ac_cv_func_fmtcheck=${ac_cv_func_fmtcheck=no}
ac_cv_func_getattrlist=${ac_cv_func_getattrlist=no}
ac_cv_func_fcopyfile=${ac_cv_func_fcopyfile=no}
ac_cv_func_getprogname=${ac_cv_func_getprogname=no}
ac_cv_func_lchflags=${ac_cv_func_lchflags=no}
ac_cv_func_localtime_rz=${ac_cv_func_localtime_rz=no}
ac_cv_func_mach_absolute_time=${ac_cv_func_mach_absolute_time=no}
ac_cv_func_microuptime=${ac_cv_func_microuptime=no}
ac_cv_func_nanouptime=${ac_cv_func_nanouptime=no}
ac_cv_func_strcasecoll=${ac_cv_func_strcasecoll=no}
ac_cv_func_strftime_z=${ac_cv_func_strftime_z=no}
ac_cv_func_stricoll=${ac_cv_func_stricoll=no}
ac_cv_func_tzalloc=${ac_cv_func_tzalloc=no}

# glibc-specific extensions not in musl
ac_cv_func___argz_count=${ac_cv_func___argz_count=no}
ac_cv_func___argz_next=${ac_cv_func___argz_next=no}
ac_cv_func___argz_stringify=${ac_cv_func___argz_stringify=no}
ac_cv_func_argz_count=${ac_cv_func_argz_count=no}
ac_cv_func_argz_next=${ac_cv_func_argz_next=no}
ac_cv_func_argz_stringify=${ac_cv_func_argz_stringify=no}
ac_cv_func_canonicalize_file_name=${ac_cv_func_canonicalize_file_name=no}
ac_cv_func_error=${ac_cv_func_error=no}
ac_cv_func_error_at_line=${ac_cv_func_error_at_line=no}
ac_cv_func_feenableexcept=${ac_cv_func_feenableexcept=no}
ac_cv_func_group_member=${ac_cv_func_group_member=no}
ac_cv_func_malloc_trim=${ac_cv_func_malloc_trim=no}
ac_cv_func_mallopt=${ac_cv_func_mallopt=no}
ac_cv_func_obstack_printf=${ac_cv_func_obstack_printf=no}
ac_cv_func_rawmemchr=${ac_cv_func_rawmemchr=no}
ac_cv_func_wmempcpy=${ac_cv_func_wmempcpy=no}
ac_cv_func_memset_explicit=${ac_cv_func_memset_explicit=no}
ac_cv_func_explicit_memset=${ac_cv_func_explicit_memset=no}
ac_cv_func_memset_s=${ac_cv_func_memset_s=no}

# Functions truly absent from musl on wasm32
ac_cv_func___setostype=${ac_cv_func___setostype=no}
ac_cv_func__doprnt=${ac_cv_func__doprnt=no}
ac_cv_func_GetSystemTimeAsFileTime=${ac_cv_func_GetSystemTimeAsFileTime=no}
ac_cv_func_clock_nanosleep=${ac_cv_func_clock_nanosleep=no}
ac_cv_func_close_range=${ac_cv_func_close_range=no}
ac_cv_func_closefrom=${ac_cv_func_closefrom=no}
ac_cv_func_closesocket=${ac_cv_func_closesocket=no}
ac_cv_func_dlopen=${ac_cv_func_dlopen=no}
ac_cv_func_directio=${ac_cv_func_directio=no}
ac_cv_func_fdwalk=${ac_cv_func_fdwalk=no}
ac_cv_func_getcontext=${ac_cv_func_getcontext=no}
ac_cv_func_getattrat=${ac_cv_func_getattrat=no}
ac_cv_func_getexecname=${ac_cv_func_getexecname=no}
ac_cv_func_getgrent_nomembers=${ac_cv_func_getgrent_nomembers=no}
ac_cv_func_getgrgid_nomembers=${ac_cv_func_getgrgid_nomembers=no}
ac_cv_func_getgrnam_nomembers=${ac_cv_func_getgrnam_nomembers=no}
ac_cv_func_getpass_r=${ac_cv_func_getpass_r=no}
ac_cv_func_getwd=${ac_cv_func_getwd=no}
ac_cv_func_glob64=${ac_cv_func_glob64=no}
ac_cv_func_kqueue=${ac_cv_func_kqueue=no}
ac_cv_func_kevent=${ac_cv_func_kevent=no}
ac_cv_func_lutimens=${ac_cv_func_lutimens=no}
ac_cv_func_madvise=${ac_cv_func_madvise=no}
ac_cv_func_mbscasecmp=${ac_cv_func_mbscasecmp=no}
ac_cv_func_mbschr=${ac_cv_func_mbschr=no}
ac_cv_func_mbscmp=${ac_cv_func_mbscmp=no}
ac_cv_func_mbslen=${ac_cv_func_mbslen=no}
ac_cv_func_mprotect=${ac_cv_func_mprotect=no}
ac_cv_func_mquery=${ac_cv_func_mquery=no}
ac_cv_func_mremap=${ac_cv_func_mremap=no}
ac_cv_func_open64=${ac_cv_func_open64=no}
ac_cv_func_plock=${ac_cv_func_plock=no}
ac_cv_func_process_vm_readv=${ac_cv_func_process_vm_readv=no}
ac_cv_func_pstat_getdynamic=${ac_cv_func_pstat_getdynamic=no}
Expand All @@ -460,11 +497,15 @@ ac_cv_func_pthread_cond_timedwait_relative_np=${ac_cv_func_pthread_cond_timedwai
ac_cv_func_pthread_create=${ac_cv_func_pthread_create=no}
ac_cv_func_pthread_init=${ac_cv_func_pthread_init=no}
ac_cv_func_ptsname_r=${ac_cv_func_ptsname_r=no}
ac_cv_func_renameat2=${ac_cv_func_renameat2=no}
ac_cv_func_rtpspawn=${ac_cv_func_rtpspawn=no}
ac_cv_func_setdtablesize=${ac_cv_func_setdtablesize=no}
ac_cv_func_setcontext=${ac_cv_func_setcontext=no}
ac_cv_func_setproctitle=${ac_cv_func_setproctitle=no}
ac_cv_func_sigsetmask=${ac_cv_func_sigsetmask=no}
ac_cv_func_stat64=${ac_cv_func_stat64=no}
ac_cv_func_utimens=${ac_cv_func_utimens=no}
ac_cv_func_vasnprintf=${ac_cv_func_vasnprintf=no}
ac_cv_func_RAND_egd=${ac_cv_func_RAND_egd=no}
ac_cv_func__set_invalid_parameter_handler=${ac_cv_func__set_invalid_parameter_handler=no}
ac_cv_func_posix_spawn_file_actions_addclosefrom_np=${ac_cv_func_posix_spawn_file_actions_addclosefrom_np=no}
Expand All @@ -483,7 +524,17 @@ ac_cv_func_lsetxattr=${ac_cv_func_lsetxattr=no}
ac_cv_func_removexattr=${ac_cv_func_removexattr=no}
ac_cv_func_setxattr=${ac_cv_func_setxattr=no}

ac_cv_lib_dl_dlopen=${ac_cv_lib_dl_dlopen=no}
# Dynamic loading is provided by Kandelo's SDK glue when a link requests
# -ldl. The musl archive's weak dlfcn stubs link successfully but only report
# "Dynamic loading not supported", so an unadorned AC_CHECK_FUNC result is not
# a functional capability. Force AC_SEARCH_LIBS callers through the SDK's
# explicit library-selection path instead of accepting the weak stub.
ac_cv_func_dlopen=${ac_cv_func_dlopen=no}
ac_cv_lib_dl_dlopen=${ac_cv_lib_dl_dlopen=yes}
ac_cv_search_dlclose=${ac_cv_search_dlclose=-ldl}
ac_cv_search_dlerror=${ac_cv_search_dlerror=-ldl}
ac_cv_search_dlopen=${ac_cv_search_dlopen=-ldl}
ac_cv_search_dlsym=${ac_cv_search_dlsym=-ldl}

# ─── Headers ───────────────────────────────────────────────────────────────────
# Verified via: find sysroot/include -name '*.h'
Expand Down Expand Up @@ -615,6 +666,8 @@ gl_cv_func_snprintf_truncation_c99=${gl_cv_func_snprintf_truncation_c99=yes}
gl_cv_func_stat_dir_slash=${gl_cv_func_stat_dir_slash=yes}
gl_cv_func_stat_file_slash=${gl_cv_func_stat_file_slash=yes}
gl_cv_func_stpncpy=${gl_cv_func_stpncpy=yes}
gl_cv_func_strcasecmp_works=${gl_cv_func_strcasecmp_works=yes}
gl_cv_func_strerror_0_works=${gl_cv_func_strerror_0_works=yes}
gl_cv_func_strndup_works=${gl_cv_func_strndup_works=yes}
gl_cv_func_strnlen_working=${gl_cv_func_strnlen_working=yes}
gl_cv_func_unsetenv_works=${gl_cv_func_unsetenv_works=yes}
Expand Down Expand Up @@ -685,4 +738,3 @@ ac_cv_lib_crypto_HMAC_Update=${ac_cv_lib_crypto_HMAC_Update=yes}
ac_cv_lib_crypto_HMAC_Init_ex=${ac_cv_lib_crypto_HMAC_Init_ex=yes}
ac_cv_lib_ssl_SSL_connect=${ac_cv_lib_ssl_SSL_connect=yes}
ac_cv_lib_crypto_EVP_DigestInit_ex=${ac_cv_lib_crypto_EVP_DigestInit_ex=yes}
ac_cv_lib_dl_dlopen=${ac_cv_lib_dl_dlopen=yes}
10 changes: 5 additions & 5 deletions sdk/src/bin/cc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ export function buildClangArgs(userArgs: string[], toolchain: Toolchain, arch: W
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);
// Static link semantics depend on the caller's exact ordering of objects,
// archives, -l flags, and linker group controls. Parsed classifications are
// for SDK decisions only; forwarding must never rebuild the command in
// type-based buckets.
args.push(...parsed.forwardedArgs);

// -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
Expand Down
11 changes: 10 additions & 1 deletion sdk/src/lib/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ export interface ParsedArgs {
objectFiles: string[];
archiveFiles: string[];
otherArgs: string[];
/** Arguments forwarded to clang, in the exact order supplied by the caller. */
forwardedArgs: string[];
}

const SOURCE_EXTS = new Set(['.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.i', '.ii']);
Expand Down Expand Up @@ -162,6 +164,7 @@ export function parseArgs(args: string[]): ParsedArgs {
objectFiles: [],
archiveFiles: [],
otherArgs: [],
forwardedArgs: [],
};

for (let i = 0; i < args.length; i++) {
Expand Down Expand Up @@ -199,8 +202,12 @@ export function parseArgs(args: string[]): ParsedArgs {
} else if (FLAGS_WITH_VALUE.has(arg)) {
// Flag that takes the next arg as its value — keep both as otherArgs
result.otherArgs.push(arg);
result.forwardedArgs.push(arg);
i++;
if (i < args.length) result.otherArgs.push(args[i]);
if (i < args.length) {
result.otherArgs.push(args[i]);
result.forwardedArgs.push(args[i]);
}
} else if (!arg.startsWith('-')) {
const ext = arg.substring(arg.lastIndexOf('.'));
if (SOURCE_EXTS.has(ext)) {
Expand All @@ -212,8 +219,10 @@ export function parseArgs(args: string[]): ParsedArgs {
} else {
result.otherArgs.push(arg);
}
result.forwardedArgs.push(arg);
} else {
result.otherArgs.push(arg);
result.forwardedArgs.push(arg);
}
}

Expand Down
19 changes: 19 additions & 0 deletions sdk/test/cc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ describe('buildClangArgs', () => {
expect(args.join(' ')).toContain('libc.a');
});

it('-ldl selects the functional dynamic-loading glue', () => {
const args = buildClangArgs(['foo.c', '-ldl', '-o', 'foo.wasm'], toolchain);
expect(args).not.toContain('-ldl');
expect(args).toContain('/tmp/glue/dlopen.c');
});

it('link-only: object files without -c get link flags plus compile flags for glue', () => {
const args = buildClangArgs(['foo.o', 'bar.o', '-o', 'out.wasm'], toolchain);
expect(args).toContain('-Wl,--entry=_start');
Expand All @@ -47,6 +53,19 @@ describe('buildClangArgs', () => {
expect(args.join(' ')).toContain('channel_syscall.c');
});

it('preserves user linker input order across argument categories', () => {
const userLinkArgs = [
'main.o',
'-Wl,--start-group',
'-lfoo',
'libbar.a',
'-Wl,--end-group',
];
const args = buildClangArgs([...userLinkArgs, '-o', 'out.wasm'], toolchain);
const forwarded = args.slice(args.indexOf('main.o'), args.indexOf('-Wl,--end-group') + 1);
expect(forwarded).toEqual(userLinkArgs);
});

it('preprocess-only: no link flags', () => {
const args = buildClangArgs(['-E', 'foo.c'], toolchain);
expect(args).not.toContain('-Wl,--entry=_start');
Expand Down
Loading
Loading