Skip to content

Commit cf95a4d

Browse files
committedMar 5, 2025
Target definition for wasm32-wali-linux-musl to support the Wasm Linux
Interface This commit does not patch libc, stdarch, or cc
1 parent fd17dea commit cf95a4d

File tree

14 files changed

+351
-19
lines changed

14 files changed

+351
-19
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
//! This target is a confluence of Linux and Wasm models, inheriting most
2+
//! aspects from their respective base targets
3+
4+
use crate::spec::{
5+
Cc, LinkSelfContainedDefault, LinkerFlavor, PanicStrategy, RelocModel, TargetOptions, TlsModel,
6+
add_link_args, crt_objects, cvs,
7+
};
8+
9+
pub(crate) fn opts() -> TargetOptions {
10+
macro_rules! args {
11+
($prefix:literal) => {
12+
&[
13+
// By default LLD only gives us one page of stack (64k) which is a
14+
// little small. Default to a larger stack closer to other PC platforms
15+
// (1MB) and users can always inject their own link-args to override this.
16+
concat!($prefix, "-z"),
17+
concat!($prefix, "stack-size=1048576"),
18+
// By default LLD's memory layout is:
19+
//
20+
// 1. First, a blank page
21+
// 2. Next, all static data
22+
// 3. Finally, the main stack (which grows down)
23+
//
24+
// This has the unfortunate consequence that on stack overflows you
25+
// corrupt static data and can cause some exceedingly weird bugs. To
26+
// help detect this a little sooner we instead request that the stack is
27+
// placed before static data.
28+
//
29+
// This means that we'll generate slightly larger binaries as references
30+
// to static data will take more bytes in the ULEB128 encoding, but
31+
// stack overflow will be guaranteed to trap as it underflows instead of
32+
// corrupting static data.
33+
concat!($prefix, "--stack-first"),
34+
// FIXME we probably shouldn't pass this but instead pass an explicit list
35+
// of symbols we'll allow to be undefined. We don't currently have a
36+
// mechanism of knowing, however, which symbols are intended to be imported
37+
// from the environment and which are intended to be imported from other
38+
// objects linked elsewhere. This is a coarse approximation but is sure to
39+
// hide some bugs and frustrate someone at some point, so we should ideally
40+
// work towards a world where we can explicitly list symbols that are
41+
// supposed to be imported and have all other symbols generate errors if
42+
// they remain undefined.
43+
concat!($prefix, "--allow-undefined"),
44+
// LLD only implements C++-like demangling, which doesn't match our own
45+
// mangling scheme. Tell LLD to not demangle anything and leave it up to
46+
// us to demangle these symbols later. Currently rustc does not perform
47+
// further demangling, but tools like twiggy and wasm-bindgen are intended
48+
// to do so.
49+
concat!($prefix, "--no-demangle"),
50+
]
51+
};
52+
}
53+
54+
let mut pre_link_args = TargetOptions::link_args(LinkerFlavor::WasmLld(Cc::No), args!(""));
55+
add_link_args(&mut pre_link_args, LinkerFlavor::WasmLld(Cc::Yes), args!("-Wl,"));
56+
57+
TargetOptions {
58+
is_like_wasm: true,
59+
families: cvs!["wasm", "unix"],
60+
os: "linux".into(),
61+
env: "musl".into(),
62+
63+
// we allow dynamic linking, but only cdylibs. Basically we allow a
64+
// final library artifact that exports some symbols (a wasm module) but
65+
// we don't allow intermediate `dylib` crate types
66+
dynamic_linking: true,
67+
only_cdylib: true,
68+
69+
// relatively self-explanatory!
70+
exe_suffix: ".wasm".into(),
71+
dll_prefix: "".into(),
72+
dll_suffix: ".wasm".into(),
73+
eh_frame_header: false,
74+
75+
max_atomic_width: Some(64),
76+
77+
// Unwinding doesn't work right now, so the whole target unconditionally
78+
// defaults to panic=abort. Note that this is guaranteed to change in
79+
// the future once unwinding is implemented. Don't rely on this as we're
80+
// basically guaranteed to change it once WebAssembly supports
81+
// exceptions.
82+
panic_strategy: PanicStrategy::Abort,
83+
84+
// Symbol visibility takes care of this for the WebAssembly.
85+
// Additionally the only known linker, LLD, doesn't support the script
86+
// arguments just yet
87+
limit_rdylib_exports: false,
88+
89+
// we use the LLD shipped with the Rust toolchain by default
90+
linker: Some("rust-lld".into()),
91+
linker_flavor: LinkerFlavor::WasmLld(Cc::No),
92+
93+
pre_link_args,
94+
95+
// FIXME: Figure out cases in which WASM needs to link with a native toolchain.
96+
//
97+
// rust-lang/rust#104137: cannot blindly remove this without putting in
98+
// some other way to compensate for lack of `-nostartfiles` in linker
99+
// invocation.
100+
link_self_contained: LinkSelfContainedDefault::True,
101+
pre_link_objects_self_contained: crt_objects::pre_wasi_self_contained(),
102+
post_link_objects_self_contained: crt_objects::post_wasi_self_contained(),
103+
104+
// This has no effect in LLVM 8 or prior, but in LLVM 9 and later when
105+
// PIC code is implemented this has quite a drastic effect if it stays
106+
// at the default, `pic`. In an effort to keep wasm binaries as minimal
107+
// as possible we're defaulting to `static` for now, but the hope is
108+
// that eventually we can ship a `pic`-compatible standard library which
109+
// works with `static` as well (or works with some method of generating
110+
// non-relative calls and such later on).
111+
relocation_model: RelocModel::Static,
112+
113+
// When the atomics feature is activated then these two keys matter,
114+
// otherwise they're basically ignored by the standard library. In this
115+
// mode, however, the `#[thread_local]` attribute works (i.e.
116+
// `has_thread_local`) and we need to get it to work by specifying
117+
// `local-exec` as that's all that's implemented in LLVM today for wasm.
118+
has_thread_local: true,
119+
tls_model: TlsModel::LocalExec,
120+
121+
// Supporting Linux requires multithreading supported by Wasm's thread
122+
// proposal
123+
singlethread: false,
124+
125+
// gdb scripts don't work on wasm blobs
126+
emit_debug_gdb_scripts: false,
127+
128+
// There's more discussion of this at
129+
// https://bugs.llvm.org/show_bug.cgi?id=52442 but the general result is
130+
// that this isn't useful for wasm and has tricky issues with
131+
// representation, so this is disabled.
132+
generate_arange_section: false,
133+
134+
// Right now this is a bit of a workaround but we're currently saying that
135+
// the target by default has a static crt which we're taking as a signal
136+
// for "use the bundled crt". If that's turned off then the system's crt
137+
// will be used, but this means that default usage of this target doesn't
138+
// need an external compiler but it's still interoperable with an external
139+
// compiler if configured correctly.
140+
crt_static_default: true,
141+
crt_static_respected: true,
142+
143+
// Allow `+crt-static` to create a "cdylib" output which is just a wasm file
144+
// without a main function.
145+
crt_static_allows_dylibs: true,
146+
147+
// Wasm start ignores arguments -- relies on API call from interface.
148+
main_needs_argc_argv: false,
149+
150+
// Wasm toolchains mangle the name of "main" to distinguish between different
151+
// signatures.
152+
entry_name: "__main_void".into(),
153+
154+
// Wasm Feature flags for supporting Linux
155+
features: "+atomics,+bulk-memory,+mutable-globals,+sign-ext".into(),
156+
157+
..Default::default()
158+
}
159+
}

‎compiler/rustc_target/src/spec/base/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub(crate) mod linux_gnu;
1818
pub(crate) mod linux_musl;
1919
pub(crate) mod linux_ohos;
2020
pub(crate) mod linux_uclibc;
21+
pub(crate) mod linux_wasm;
2122
pub(crate) mod msvc;
2223
pub(crate) mod netbsd;
2324
pub(crate) mod nto_qnx;

‎compiler/rustc_target/src/spec/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1925,6 +1925,7 @@ supported_targets! {
19251925
("wasm32-wasip1", wasm32_wasip1),
19261926
("wasm32-wasip2", wasm32_wasip2),
19271927
("wasm32-wasip1-threads", wasm32_wasip1_threads),
1928+
("wasm32-wali-linux-musl", wasm32_wali_linux_musl),
19281929
("wasm64-unknown-unknown", wasm64_unknown_unknown),
19291930

19301931
("thumbv6m-none-eabi", thumbv6m_none_eabi),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//! The `wasm32-wali-linux-musl` target is a wasm32 target compliant with the
2+
//! [WebAssembly Linux Interface](https://github.com/arjunr2/WALI).
3+
4+
use crate::spec::{Cc, LinkerFlavor, Target, TargetMetadata, base};
5+
6+
pub(crate) fn target() -> Target {
7+
let mut options = base::linux_wasm::opts();
8+
9+
options
10+
.add_pre_link_args(LinkerFlavor::WasmLld(Cc::No), &["--export-memory", "--shared-memory"]);
11+
options.add_pre_link_args(
12+
LinkerFlavor::WasmLld(Cc::Yes),
13+
&["--target=wasm32-wasi-threads", "-Wl,--export-memory,", "-Wl,--shared-memory"],
14+
);
15+
16+
Target {
17+
llvm_target: "wasm32-wasi".into(),
18+
metadata: TargetMetadata {
19+
description: Some("WebAssembly Linux Interface with musl-libc".into()),
20+
tier: Some(3),
21+
host_tools: Some(false),
22+
std: None,
23+
},
24+
pointer_width: 32,
25+
data_layout: "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20".into(),
26+
arch: "wasm32".into(),
27+
options,
28+
}
29+
}

‎library/core/src/ffi/primitives.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,10 @@ mod c_char_definition {
129129

130130
mod c_long_definition {
131131
cfg_if! {
132-
if #[cfg(all(target_pointer_width = "64", not(windows)))] {
132+
if #[cfg(any(
133+
all(target_pointer_width = "64", not(windows)),
134+
// wasm32 Linux ABI uses 64-bit long
135+
all(target_arch = "wasm32", target_os = "linux")))] {
133136
pub(super) type c_long = i64;
134137
pub(super) type c_ulong = u64;
135138
} else {

‎library/unwind/src/libunwind.rs

+3
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ pub const unwinder_private_data_size: usize = 35;
8181
#[cfg(target_arch = "loongarch64")]
8282
pub const unwinder_private_data_size: usize = 2;
8383

84+
#[cfg(target_arch = "wasm32")]
85+
pub const unwinder_private_data_size: usize = 2;
86+
8487
#[repr(C)]
8588
pub struct _Unwind_Exception {
8689
pub exception_class: _Unwind_Exception_Class,

‎src/bootstrap/configure.py

+5
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,11 @@ def v(*args):
269269
"target.loongarch64-unknown-linux-musl.musl-root",
270270
"loongarch64-unknown-linux-musl install directory",
271271
)
272+
v(
273+
"musl-root-wali-wasm32",
274+
"target.wasm32-wali-linux-musl.musl-root",
275+
"wasm32-wali-linux-musl install directory",
276+
)
272277
v(
273278
"qemu-armhf-rootfs",
274279
"target.arm-unknown-linux-gnueabihf.qemu-rootfs",

‎src/bootstrap/src/core/build_steps/compile.rs

+31-17
Original file line numberDiff line numberDiff line change
@@ -390,24 +390,38 @@ fn copy_self_contained_objects(
390390
let srcdir = builder.musl_libdir(target).unwrap_or_else(|| {
391391
panic!("Target {:?} does not have a \"musl-libdir\" key", target.triple)
392392
});
393-
for &obj in &["libc.a", "crt1.o", "Scrt1.o", "rcrt1.o", "crti.o", "crtn.o"] {
394-
copy_and_stamp(
395-
builder,
396-
&libdir_self_contained,
397-
&srcdir,
398-
obj,
399-
&mut target_deps,
400-
DependencyType::TargetSelfContained,
401-
);
402-
}
403-
let crt_path = builder.ensure(llvm::CrtBeginEnd { target });
404-
for &obj in &["crtbegin.o", "crtbeginS.o", "crtend.o", "crtendS.o"] {
405-
let src = crt_path.join(obj);
406-
let target = libdir_self_contained.join(obj);
407-
builder.copy_link(&src, &target);
408-
target_deps.push((target, DependencyType::TargetSelfContained));
393+
if !target.starts_with("wasm32") {
394+
for &obj in &["libc.a", "crt1.o", "Scrt1.o", "rcrt1.o", "crti.o", "crtn.o"] {
395+
copy_and_stamp(
396+
builder,
397+
&libdir_self_contained,
398+
&srcdir,
399+
obj,
400+
&mut target_deps,
401+
DependencyType::TargetSelfContained,
402+
);
403+
}
404+
let crt_path = builder.ensure(llvm::CrtBeginEnd { target });
405+
for &obj in &["crtbegin.o", "crtbeginS.o", "crtend.o", "crtendS.o"] {
406+
let src = crt_path.join(obj);
407+
let target = libdir_self_contained.join(obj);
408+
builder.copy_link(&src, &target);
409+
target_deps.push((target, DependencyType::TargetSelfContained));
410+
}
411+
} else {
412+
// For wasm32 targets, we need to copy the libc.a and crt1-command.o files from the
413+
// musl-libdir, but we don't need the other files.
414+
for &obj in &["libc.a", "crt1-command.o"] {
415+
copy_and_stamp(
416+
builder,
417+
&libdir_self_contained,
418+
&srcdir,
419+
obj,
420+
&mut target_deps,
421+
DependencyType::TargetSelfContained,
422+
);
423+
}
409424
}
410-
411425
if !target.starts_with("s390x") {
412426
let libunwind_path = copy_llvm_libunwind(builder, target, &libdir_self_contained);
413427
target_deps.push((libunwind_path, DependencyType::TargetSelfContained));

‎src/bootstrap/src/core/sanity.rs

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub struct Finder {
3434
// Targets can be removed from this list once they are present in the stage0 compiler (usually by updating the beta compiler of the bootstrap).
3535
const STAGE0_MISSING_TARGETS: &[&str] = &[
3636
// just a dummy comment so the list doesn't get onelined
37+
"wasm32-wali-linux-musl",
3738
];
3839

3940
/// Minimum version threshold for libstdc++ required when using prebuilt LLVM

‎src/doc/rustc/src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
- [wasm32-wasip1](platform-support/wasm32-wasip1.md)
9696
- [wasm32-wasip1-threads](platform-support/wasm32-wasip1-threads.md)
9797
- [wasm32-wasip2](platform-support/wasm32-wasip2.md)
98+
- [wasm32-wali-linux-musl](platform-support/wasm32-wali-linux.md)
9899
- [wasm32-unknown-emscripten](platform-support/wasm32-unknown-emscripten.md)
99100
- [wasm32-unknown-unknown](platform-support/wasm32-unknown-unknown.md)
100101
- [wasm32v1-none](platform-support/wasm32v1-none.md)

‎src/doc/rustc/src/platform-support.md

+1
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ target | std | host | notes
405405
[`thumbv8m.main-nuttx-eabi`](platform-support/nuttx.md) | ✓ | | ARMv8M Mainline with NuttX
406406
[`thumbv8m.main-nuttx-eabihf`](platform-support/nuttx.md) | ✓ | | ARMv8M Mainline with NuttX, hardfloat
407407
[`wasm64-unknown-unknown`](platform-support/wasm64-unknown-unknown.md) | ? | | WebAssembly
408+
[`wasm32-wali-linux-musl`](platform-support/wasm32-wali-linux.md) | ? | | WebAssembly with [WALI](https://github.com/arjunr2/WALI)
408409
[`x86_64-apple-tvos`](platform-support/apple-tvos.md) | ✓ | | x86 64-bit tvOS
409410
[`x86_64-apple-watchos-sim`](platform-support/apple-watchos.md) | ✓ | | x86 64-bit Apple WatchOS simulator
410411
[`x86_64-pc-cygwin`](platform-support/x86_64-pc-cygwin.md) | ? | | 64-bit x86 Cygwin |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# `wasm32-wali-linux-*`
2+
3+
**Tier: 3**
4+
5+
WebAssembly targets that use the [WebAssembly Linux Interface (WALI)](https://github.com/arjunr2/WALI) with 32-bit memory. The latest status of the WALI specification and support are documented within the repo.
6+
7+
WALI offers seamless targetability of traditional Linux applications to Wasm by exposing Linux syscalls strategically into the sandbox. Numerous applications and build system work unmodified over WALI, including complex low-level system libraries -- a list of applications are included in the research paper linked in the main repo.
8+
9+
From the wider Wasm ecosystem perspective, implementing WALI within engines allows layering of high-level security policies (e.g. WASI) above it, arming the latter's implementations with sandboxing and portability.
10+
11+
## Target maintainers
12+
13+
- Arjun Ramesh [@arjunr2](https://github.com/arjunr2)
14+
15+
## Requirements
16+
17+
### Compilation
18+
This target is cross-compiled and requires an installation of the [WALI compiler/sysroot](https://github.com/arjunr2/WALI). This produces standard `wasm32` binaries with the WALI interface methods as module imports that need to be implemented by a supported engine (see the "Execution" section below).
19+
20+
`wali` targets *minimally require* the following LLVM feature flags:
21+
22+
* [Bulk memory] - `+bulk-memory`
23+
* Mutable imported globals - `+mutable-globals`
24+
* [Sign-extending operations] - `+sign-ext`
25+
* [Threading/Atomics] - `+atomics`
26+
27+
[Bulk memory]: https://github.com/WebAssembly/spec/blob/main/proposals/bulk-memory-operations/Overview.md
28+
[Sign-extending operations]: https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
29+
[Threading/Atomics]: https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md
30+
31+
> **Note**: Users can expect that new enabled-by-default Wasm features for LLVM are transitively incorporatable into this target -- see [wasm32-unknown-unknown](wasm32-unknown-unknown.md) for detailed information on WebAssembly features.
32+
33+
34+
> **Note**: The WALI ABI is similar to default Clang wasm32 ABIs but *not identical*. The primary difference is 64-bit `long` types as opposed to 32-bit for wasm32. This is required to mantain minimum source code changes for 64-bit host platforms currently supported. This may change in the future as the spec evolves.
35+
36+
### Execution
37+
Running generated WALI binaries also requires a supported compliant engine implementation -- a working implementation in the [WebAssembly Micro-Runtime (WAMR)](https://github.com/arjunr2/WALI) is included in the repo.
38+
39+
> **Note**: WALI is still somewhat experimental and bugs may exist in the Rust support, WALI toolchain, or the LLVM compiler. The former can be filed in Rust repos while the latter two in the WALI repo.
40+
41+
## Building the target
42+
43+
You can build Rust with support for the target by adding it to the `target`
44+
list in `config.toml`, and pointing to the toolchain artifacts from the previous section ("Requirements->Compilation"). A sample `config.toml` for the `musl` environment will look like this, where `<WALI-root>` is the absolute path to the root directory of the [WALI repo](https://github.com/arjunr2/WALI):
45+
46+
```toml
47+
[build]
48+
target = ["wasm32-wali-linux-musl"]
49+
50+
[target.wasm32-wali-linux-musl]
51+
musl-root = "<WALI>/wali-musl/sysroot"
52+
llvm-config = "<WALI>/llvm-project/build/bin/llvm-config"
53+
cc = "<WALI>/llvm-project/build/bin/clang-18"
54+
cxx = "<WALI>/llvm-project/build/bin/clang-18"
55+
ar = "<WALI>/llvm-project/build/bin/llvm-ar"
56+
ranlib = "<WALI>/llvm-project/build/bin/llvm-ranlib"
57+
llvm-libunwind = "system"
58+
crt-static = true
59+
```
60+
61+
> The `llvm-config` settings are only temporary, and the changes will eventually be upstreamed into LLVM
62+
63+
## Building Rust programs
64+
65+
Rust does not yet ship pre-compiled artifacts for this target. To compile for
66+
this target, you will either need to build Rust with the target enabled (see
67+
"Building the target" above), or build your own copy of `core` by using
68+
`build-std` or similar.
69+
70+
Rust program builds can use this target normally. Currently, linking WALI programs may require pointing the `linker` to the llvm build in the [Cargo config](https://doc.rust-lang.org/cargo/reference/config.html) (until LLVM is upstreamed). A `config.toml` for Cargo will look like the following:
71+
72+
```toml
73+
[target.wasm32-wali-linux-musl]
74+
linker = "<WALI>/llvm-project/build/bin/lld"
75+
```
76+
77+
Note that the following `cfg` directives are set for `wasm32-wali-linux-*`:
78+
79+
* `cfg(target_arch = "wasm32")`
80+
* `cfg(target_family = {"wasm", "unix"})`
81+
* `cfg(target_r = "wasm")`
82+
* `cfg(target_os = "linux")`
83+
* `cfg(target_env = *)`
84+
85+
### Restrictions
86+
87+
Hardware or platform-specific support, besides `syscall` is mostly unsupported in WALI for ISA portability (these tend to be uncommon).
88+
89+
## Testing
90+
91+
Currently testing is not supported for `wali` targets and the Rust project doesn't run any tests for this target.
92+
93+
However, standard ISA-agnostic tests for Linux should be thereotically reusable for WALI targets and minor changes. Testing integration will be continually incorporated as support evolves.
94+
95+
96+
## Cross-compilation toolchains and C code
97+
98+
Most fully featured C code is compilable with the WALI toolchain -- examples can be seen in the repo.

‎tests/assembly/targets/targets-elf.rs

+3
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,9 @@
556556
//@ revisions: wasm32_wasip1_threads
557557
//@ [wasm32_wasip1_threads] compile-flags: --target wasm32-wasip1-threads
558558
//@ [wasm32_wasip1_threads] needs-llvm-components: webassembly
559+
//@ revisions: wasm32_wali_linux_musl
560+
//@ [wasm32_wali_linux_musl] compile-flags: --target wasm32-wali-linux-musl
561+
//@ [wasm32_wali_linux_musl] needs-llvm-components: webassembly
559562
//@ revisions: wasm32_wasip2
560563
//@ [wasm32_wasip2] compile-flags: --target wasm32-wasip2
561564
//@ [wasm32_wasip2] needs-llvm-components: webassembly

‎tests/run-make/musl-default-linking/rmake.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,21 @@ fn main() {
4545

4646
let target_spec: serde_json::Value =
4747
serde_json::from_str(&target_spec_json).expect("failed to parse target-spec-json");
48-
let default = &target_spec["crt-static-default"];
4948

49+
let target_families = &target_spec["target-family"];
50+
// WebAssembly doesn't support dynamic linking yet; all musl targets
51+
// need to be statically linked.
52+
if target_families
53+
.as_array()
54+
.expect("target-family wasn't an array")
55+
.iter()
56+
.filter_map(|x| x.as_str())
57+
.any(|family| family == "wasm")
58+
{
59+
continue;
60+
}
61+
62+
let default = &target_spec["crt-static-default"];
5063
// If the value is `null`, then the default to dynamically link from
5164
// musl_base was not overridden.
5265
if default.is_null() {

0 commit comments

Comments
 (0)
Please sign in to comment.