Skip to content

Commit 769cdd7

Browse files
authored
Implement new handling of TLS base/symbols (bytecodealliance#2590)
* Implement new handling of TLS base/symbols This commit is the wasm-tools half of the implementation of WebAssembly/wasi-libc#857. More information can be found in the writeup of that issue itself, but the general goal here is adjusting linking conventions for wasip3 modules, in particular around dynamic libraries, to ensure that TLS works everywhere. * Fix warnings on nightly
1 parent 1b4d261 commit 769cdd7

42 files changed

Lines changed: 1533 additions & 166 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ jobs:
244244
submodules: true
245245
- uses: ./.github/actions/install-rust
246246
with:
247-
toolchain: nightly
247+
toolchain: nightly-2026-08-10
248248
- run: cargo install cargo-fuzz
249249
- run: cargo fuzz build --dev -s none
250250
- run: cargo fuzz build --dev --features wasmtime -s none

Cargo.toml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,20 @@ unsafe_code = "deny"
5151
unused_extern_crates = 'warn'
5252
unstable_features = 'warn'
5353
unused_import_braces = 'warn'
54-
unused-lifetimes = 'warn'
55-
unused-macro-rules = 'warn'
54+
unused_lifetimes = 'warn'
55+
unused_macro_rules = 'warn'
5656

57-
# Lints that are part of the `rust-2024-compatibility` group. This group is a
57+
# Lints that are part of the `rust_2024_compatibility` group. This group is a
5858
# bit too noisy to enable wholesale but some selective items are ones we want to
59-
# opt-in to.
59+
# opt_in to.
6060
keyword_idents_2024 = 'warn'
61-
unsafe-attr-outside-unsafe = 'warn'
62-
deprecated-safe-2024 = 'warn'
63-
rust-2024-guarded-string-incompatible-syntax = 'warn'
64-
rust-2024-prelude-collisions = 'warn'
65-
rust-2024-incompatible-pat = 'warn'
66-
missing-unsafe-on-extern = 'warn'
67-
unsafe-op-in-unsafe-fn = 'warn'
61+
unsafe_attr_outside_unsafe = 'warn'
62+
deprecated_safe_2024 = 'warn'
63+
rust_2024_guarded_string_incompatible_syntax = 'warn'
64+
rust_2024_prelude_collisions = 'warn'
65+
rust_2024_incompatible_pat = 'warn'
66+
missing_unsafe_on_extern = 'warn'
67+
unsafe_op_in_unsafe_fn = 'warn'
6868

6969
unexpected_cfgs = { level = 'warn', check-cfg = ['cfg(fuzzing)', 'cfg(debug_check_try_op)'] }
7070

crates/fuzz-stats/src/bin/failed-instantiations.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ impl State {
4242
engine: Engine::default(),
4343
print: true,
4444
total: AtomicUsize::new(0),
45-
remaining: AtomicIsize::new(isize::max_value()),
45+
remaining: AtomicIsize::new(isize::MAX),
4646
instantiate_trap: AtomicUsize::new(0),
4747
instantiate_oom: AtomicUsize::new(0),
4848
}

crates/wasm-encoder/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ impl Encode for str {
129129

130130
impl Encode for usize {
131131
fn encode(&self, sink: &mut Vec<u8>) {
132-
assert!(*self <= u32::max_value() as usize);
132+
assert!(*self <= u32::MAX as usize);
133133
(*self as u32).encode(sink)
134134
}
135135
}

crates/wasm-smith/src/core.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1796,7 +1796,7 @@ impl Module {
17961796
ExportKind::Table => EntityType::Table(self.tables[index as usize]),
17971797
ExportKind::Func => {
17981798
let (_idx, ty) = &self.funcs[index as usize];
1799-
EntityType::Func(u32::max_value(), ty.clone())
1799+
EntityType::Func(u32::MAX, ty.clone())
18001800
}
18011801
ExportKind::Tag => EntityType::Tag(self.tags[index as usize].clone()),
18021802
}

crates/wasm-wave/tests/nan.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ fn nan() {
1919
0xffffffff,
2020
0x7fff0f0f,
2121
0x8f800000,
22-
f32::NAN.to_bits(),
22+
<f32>::NAN.to_bits(),
2323
] {
2424
let val = f32::from_bits(bits);
2525
let expected = if val.is_nan() { 0x7fc00000 } else { bits };
@@ -38,7 +38,7 @@ fn nan() {
3838
0xffffffffffffffff,
3939
0x7fff0f0f0f0f0f0f,
4040
0x8ff0000000000000,
41-
f64::NAN.to_bits(),
41+
<f64>::NAN.to_bits(),
4242
] {
4343
let val = f64::from_bits(bits);
4444
let expected = if val.is_nan() {

crates/wast/src/encode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ impl Encode for str {
3838

3939
impl Encode for usize {
4040
fn encode(&self, e: &mut Vec<u8>) {
41-
assert!(*self <= u32::max_value() as usize);
41+
assert!(*self <= u32::MAX as usize);
4242
(*self as u32).encode(e)
4343
}
4444
}

crates/wast/src/parser.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ use std::borrow::Cow;
7070
use std::cell::{Cell, RefCell};
7171
use std::collections::HashMap;
7272
use std::fmt;
73-
use std::usize;
7473

7574
/// The maximum recursive depth of parens to parse.
7675
///

crates/wit-component/src/encoding.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,8 +422,21 @@ pub struct EncodingState<'a> {
422422
/// Maps from original export name to task initialization wrapper function index.
423423
/// Used to wrap exports with __wasm_init_(async_)task calls.
424424
export_task_initialization_wrappers: HashMap<String, u32>,
425+
426+
/// The index of the instance of the synthesized module which stores the TLS
427+
/// base pointer in a `global`.
428+
///
429+
/// This is only used, and only created, when a module imports
430+
/// `__wasm_{get,set}_tls_base` but the program doesn't use cooperative
431+
/// threading. See `materialize_tls_base_import`.
432+
tls_base_instance_index: Option<(u32, ValType)>,
425433
}
426434

435+
/// Name of the export of the synthesized TLS-base module which reads the base.
436+
const TLS_BASE_GET: &str = "get";
437+
/// Name of the export of the synthesized TLS-base module which writes the base.
438+
const TLS_BASE_SET: &str = "set";
439+
427440
impl<'a> EncodingState<'a> {
428441
fn encode_core_modules(&mut self) {
429442
assert!(self.module_index.is_none());
@@ -1991,6 +2004,14 @@ impl<'a> EncodingState<'a> {
19912004
let index = self.component.context_set((*ty).try_into()?, *slot);
19922005
Ok((ExportKind::Func, index))
19932006
}
2007+
Import::TlsBaseGet { ty } => Ok((
2008+
ExportKind::Func,
2009+
self.materialize_tls_base_import(false, (*ty).try_into()?),
2010+
)),
2011+
Import::TlsBaseSet { ty } => Ok((
2012+
ExportKind::Func,
2013+
self.materialize_tls_base_import(true, (*ty).try_into()?),
2014+
)),
19942015
Import::ExportedTaskCancel => {
19952016
let index = self.component.task_cancel();
19962017
Ok((ExportKind::Func, index))
@@ -2037,6 +2058,93 @@ impl<'a> EncodingState<'a> {
20372058
}
20382059
}
20392060

2061+
/// Helper to satisfy `__wasm_{get,set}_tls_base` imports.
2062+
///
2063+
/// For more information on this see WebAssembly/wasi-libc#857
2064+
fn materialize_tls_base_import(&mut self, set: bool, ty: ValType) -> u32 {
2065+
if self.info.uses_cooperative_threading() {
2066+
return if set {
2067+
self.component.context_set(ty, 1)
2068+
} else {
2069+
self.component.context_get(ty, 1)
2070+
};
2071+
}
2072+
2073+
let instance = match self.tls_base_instance_index {
2074+
Some((index, prev_ty)) => {
2075+
assert_eq!(prev_ty, ty, "conflicting TLS base pointer types");
2076+
index
2077+
}
2078+
None => {
2079+
let index = self.encode_tls_base_module(ty);
2080+
self.tls_base_instance_index = Some((index, ty));
2081+
index
2082+
}
2083+
};
2084+
let name = if set { TLS_BASE_SET } else { TLS_BASE_GET };
2085+
self.core_alias_export(
2086+
Some(&format!("tls-base-{name}")),
2087+
instance,
2088+
name,
2089+
ExportKind::Func,
2090+
)
2091+
}
2092+
2093+
/// Synthesizes and instantiates a module which stores the TLS base pointer
2094+
/// in a mutable `global`, exporting accessors for it.
2095+
fn encode_tls_base_module(&mut self, ty: ValType) -> u32 {
2096+
let mut types = TypeSection::new();
2097+
types.ty().function([], [ty]);
2098+
types.ty().function([ty], []);
2099+
2100+
let mut globals = GlobalSection::new();
2101+
globals.global(
2102+
wasm_encoder::GlobalType {
2103+
val_type: ty,
2104+
mutable: true,
2105+
shared: false,
2106+
},
2107+
&match ty {
2108+
ValType::I64 => ConstExpr::i64_const(0),
2109+
ValType::I32 => ConstExpr::i32_const(0),
2110+
_ => unreachable!(),
2111+
},
2112+
);
2113+
2114+
let mut functions = FunctionSection::new();
2115+
let mut code = CodeSection::new();
2116+
2117+
functions.function(0);
2118+
let mut get = wasm_encoder::Function::new([]);
2119+
get.instruction(&Instruction::GlobalGet(0));
2120+
get.instruction(&Instruction::End);
2121+
code.function(&get);
2122+
2123+
functions.function(1);
2124+
let mut set = wasm_encoder::Function::new([]);
2125+
set.instruction(&Instruction::LocalGet(0));
2126+
set.instruction(&Instruction::GlobalSet(0));
2127+
set.instruction(&Instruction::End);
2128+
code.function(&set);
2129+
2130+
let mut exports = ExportSection::new();
2131+
exports.export(TLS_BASE_GET, ExportKind::Func, 0);
2132+
exports.export(TLS_BASE_SET, ExportKind::Func, 1);
2133+
2134+
let mut module = Module::new();
2135+
module.section(&types);
2136+
module.section(&functions);
2137+
module.section(&globals);
2138+
module.section(&exports);
2139+
module.section(&code);
2140+
2141+
let module_index = self
2142+
.component
2143+
.core_module(Some("wit-component:tls-base"), &module);
2144+
self.component
2145+
.core_instantiate(Some("wit-component:tls-base"), module_index, [])
2146+
}
2147+
20402148
/// Helper for `materialize_import` above for materializing functions that
20412149
/// are part of the "shim module" generated.
20422150
fn materialize_shim_import(&mut self, shims: &Shims<'_>, kind: &ShimKind) -> (ExportKind, u32) {
@@ -2686,6 +2794,8 @@ impl<'a> Shims<'a> {
26862794
| Import::WaitableJoin
26872795
| Import::ContextGet { .. }
26882796
| Import::ContextSet { .. }
2797+
| Import::TlsBaseGet { .. }
2798+
| Import::TlsBaseSet { .. }
26892799
| Import::ThreadIndex
26902800
| Import::ThreadResumeLater
26912801
| Import::ThreadSuspend { .. }
@@ -3411,6 +3521,7 @@ impl ComponentEncoder {
34113521
aliased_core_items: Default::default(),
34123522
info: &world,
34133523
export_task_initialization_wrappers: HashMap::new(),
3524+
tls_base_instance_index: None,
34143525
};
34153526
state.encode_imports(&self.import_name_map)?;
34163527
state.encode_core_modules();

crates/wit-component/src/encoding/world.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,21 @@ impl<'a> ComponentWorld<'a> {
8585
Ok(ret)
8686
}
8787

88+
/// Returns whether any module in this component may spawn a thread, and
89+
/// thus whether the program is using cooperative threading.
90+
///
91+
/// This is a heuristic which should go away once component-model-threading
92+
/// has been stable for awhile and the return value of this function should
93+
/// be const-propagated as `true`.
94+
pub fn uses_cooperative_threading(&self) -> bool {
95+
let uses = |info: &ValidatedModule| {
96+
info.imports
97+
.imports()
98+
.any(|(_, _, import)| matches!(import, Import::ThreadNewIndirect))
99+
};
100+
uses(&self.info) || self.adapters.values().any(|a| uses(&a.info))
101+
}
102+
88103
/// Process adapters which are required here. Iterate over all
89104
/// adapters and figure out what functions are required from the
90105
/// adapter itself, either because the functions are imported by the
@@ -432,6 +447,8 @@ impl<'a> ComponentWorld<'a> {
432447
| Import::Item(_)
433448
| Import::ContextGet { .. }
434449
| Import::ContextSet { .. }
450+
| Import::TlsBaseGet { .. }
451+
| Import::TlsBaseSet { .. }
435452
| Import::BackpressureInc
436453
| Import::BackpressureDec
437454
| Import::WaitableSetNew

0 commit comments

Comments
 (0)