diff --git a/libwild/src/wasm.rs b/libwild/src/wasm.rs index cbf66b2b7..913117046 100644 --- a/libwild/src/wasm.rs +++ b/libwild/src/wasm.rs @@ -205,6 +205,15 @@ const UNREACHABLE_FUNCTION_BODY: &[u8] = &[0x00, 0x00, 0x0b]; /// `i32.const` body for `LINKER_MEMORY_BASE`. const LINKER_MEMORY_BASE_INIT_EXPR: &[u8] = &[0x41, 0x80, 0x08]; +const fn memory_base_init_expr(is_pic: bool) -> &'static [u8] { + if is_pic { + LINKER_MEMORY_BASE_INIT_EXPR + } else { + // Optional non-PIC `__memory_base` is 0. + ZERO_I32_INIT_EXPR + } +} + /// `i32.const 0`. Used for immutable `__tls_base` when no TLS segment is laid out. const ZERO_I32_INIT_EXPR: &[u8] = &[0x41, 0x00]; @@ -1672,6 +1681,9 @@ fn build_name_section<'data>( if let Some(idx) = indices.tls_base_global { set_name_first_wins(&mut global_names, idx, "__tls_base"); } + for &(known, idx) in &indices.data_address_globals { + set_name_first_wins(&mut global_names, idx, <&str>::from(known)); + } if let Some(got_base) = indices.got_mem_global_base { got_mem_names.reserve(got_mem.entries.len()); for (i, entry) in got_mem.entries.iter().enumerate() { @@ -3881,6 +3893,18 @@ struct LinkerImportAbsorption { } impl LinkerImportAbsorption { + fn need(&mut self, known: WasmLinkerSymbol) { + match known { + WasmLinkerSymbol::CallCtors => self.needs_ctors = true, + WasmLinkerSymbol::MemoryBase => self.needs_memory_base = true, + WasmLinkerSymbol::TableBase => self.needs_table_base = true, + WasmLinkerSymbol::StackPointer => self.needs_stack_pointer = true, + // Single-threaded. Immutable base (no TLS segment yet). + WasmLinkerSymbol::TlsBase => self.needs_tls_base = true, + _ => {} + } + } + fn from_resolutions( resolutions: &ObjectImportResolutions, live_function_imports: &[bool], @@ -3891,8 +3915,8 @@ impl LinkerImportAbsorption { if !live_function_imports.get(i).copied().unwrap_or(false) { continue; } - if let ImportResolution::LinkerDefined(WasmLinkerSymbol::CallCtors) = *resolution { - absorption.needs_ctors = true; + if let ImportResolution::LinkerDefined(known) = *resolution { + absorption.need(known); } } for (i, resolution) in resolutions.global_resolutions.iter().enumerate() { @@ -3900,14 +3924,7 @@ impl LinkerImportAbsorption { continue; } if let ImportResolution::LinkerDefined(known) = *resolution { - match known { - WasmLinkerSymbol::MemoryBase => absorption.needs_memory_base = true, - WasmLinkerSymbol::TableBase => absorption.needs_table_base = true, - WasmLinkerSymbol::StackPointer => absorption.needs_stack_pointer = true, - // Single-threaded. Immutable base (no TLS segment yet). - WasmLinkerSymbol::TlsBase => absorption.needs_tls_base = true, - _ => {} - } + absorption.need(known); } } absorption @@ -3945,6 +3962,12 @@ struct LinkerDefinedIndices { /// First module global index for GOT.func entries. got_func_global_base: Option, got_func_count: u32, + data_address_globals: Vec<(WasmLinkerSymbol, u32)>, + // Linker symbols named by `--export` / `--export-if-defined`. + requested_exports: Vec, + // True when `__memory_base` is the PIC value (`LINKER_MEMORY_BASE`). False when it exists + // only because of `--export`. + memory_base_is_pic: bool, } /// Where a GOT.mem slot's final linear-memory address comes from. @@ -4061,8 +4084,10 @@ fn setup_got_mem_and_indices<'data>( shared_imports.function_count(), shared_imports.global_count(), weak_undef_stubs, - LinkerDefinedIndexRequest { + &LinkerDefinedIndexRequest { has_init_funcs, + export_symbols: requested_linker_export_symbols(symbol_db.args), + has_memory: any_object_needs_linker_memory(layout_inputs), wrap_entry, got_mem_count: scan.got_mem.len(), got_func_count: scan.got_func.len(), @@ -4733,9 +4758,34 @@ fn fill_got_mem_inits( .globals .get_mut(global_slot) .ok_or_else(|| crate::error!("GOT.mem global slot {global_slot} out of range"))?; - let addr_i32 = i32::try_from(addr) - .map_err(|_| crate::error!("GOT.mem data address out of i32 range"))?; - global.init_expr_body = Cow::Owned(encode_i32_const_body(addr_i32)); + global.init_expr_body = Cow::Owned(encode_i32_const_u32(addr)); + } + Ok(()) +} + +fn fill_exported_data_global_inits( + layout: &mut WasmLayout<'_>, + indices: &LinkerDefinedIndices, + data_start: u32, + data_end: u32, + stack_size: u32, + heap_end: Option, + stack_first: bool, +) -> Result { + for &(known, global_index) in &indices.data_address_globals { + let addr = known + .data_address(data_start, data_end, stack_size, heap_end, stack_first)? + .ok_or_else(|| { + crate::error!( + "linker-defined symbol `{}` has no address to export", + std::str::from_utf8(known.name()).unwrap_or("?") + ) + })?; + let defined_slot = (global_index - indices.global_import_count) as usize; + let global = layout.globals.get_mut(defined_slot).ok_or_else(|| { + crate::error!("exported data global slot {defined_slot} out of range") + })?; + global.init_expr_body = Cow::Owned(encode_i32_const_u32(addr)); } Ok(()) } @@ -4828,9 +4878,11 @@ fn entry_is_defined_function( !sym.is_undefined() && sym.kind == WasmSymbolKind::Func } -#[derive(Clone, Copy)] struct LinkerDefinedIndexRequest { has_init_funcs: bool, + // Linker symbols named by `--export` / `--export-if-defined`. + export_symbols: Vec, + has_memory: bool, wrap_entry: bool, got_mem_count: u32, got_func_count: u32, @@ -4845,13 +4897,31 @@ impl LinkerDefinedIndices { function_import_count: u32, global_import_count: u32, mut weak_undef_stubs: Vec, - request: LinkerDefinedIndexRequest, + request: &LinkerDefinedIndexRequest, ) -> Result { let mut needs_memory_base = request.needs_memory_base; let mut needs_table_base = request.needs_table_base; let mut needs_stack_pointer = false; let mut needs_tls_base = false; let mut needs_ctors = request.has_init_funcs; + let mut export_data = Vec::new(); + let mut export_needs = LinkerImportAbsorption::default(); + for &sym in &request.export_symbols { + if !sym.materialize_on_export() { + continue; + } + if sym.exported_as_data_global(request.has_memory) { + if !export_data.contains(&sym) { + export_data.push(sym); + } + } else { + export_needs.need(sym); + } + } + needs_ctors |= export_needs.needs_ctors; + needs_table_base |= export_needs.needs_table_base; + needs_stack_pointer |= export_needs.needs_stack_pointer; + let export_memory_base = export_needs.needs_memory_base; for (input, resolutions) in layout_inputs.iter().zip(import_resolutions.iter()) { let absorption = LinkerImportAbsorption::from_resolutions( @@ -4865,6 +4935,8 @@ impl LinkerDefinedIndices { needs_stack_pointer |= absorption.needs_stack_pointer; needs_tls_base |= absorption.needs_tls_base; } + let memory_base_is_pic = needs_memory_base; + needs_memory_base |= export_memory_base; let mut next_global = global_import_count; // Defined-global slot before `__stack_pointer` (used for its init expression). @@ -4890,6 +4962,13 @@ impl LinkerDefinedIndices { next_global += 1; idx }); + let mut data_address_globals = Vec::with_capacity(export_data.len()); + for known in export_data { + data_address_globals.push((known, next_global)); + next_global = next_global + .checked_add(1) + .ok_or_else(|| crate::error!("Wasm global index overflow"))?; + } let got_mem_global_base = if request.got_mem_count > 0 { let base = next_global; next_global = next_global @@ -4945,6 +5024,9 @@ impl LinkerDefinedIndices { got_mem_count: request.got_mem_count, got_func_global_base, got_func_count: request.got_func_count, + data_address_globals, + requested_exports: request.export_symbols.clone(), + memory_base_is_pic, }) } @@ -4954,7 +5036,11 @@ impl LinkerDefinedIndices { WasmLinkerSymbol::TableBase => self.table_base_global, WasmLinkerSymbol::StackPointer => self.stack_pointer_global, WasmLinkerSymbol::TlsBase => self.tls_base_global, - _ => None, + other => self + .data_address_globals + .iter() + .find(|(sym, _)| *sym == other) + .map(|(_, idx)| *idx), } } @@ -4972,6 +5058,11 @@ fn encode_i32_const_body(value: i32) -> Vec { bytes } +/// Encode a linear-memory address as Wasm `i32.const`. +fn encode_i32_const_u32(value: u32) -> Vec { + encode_i32_const_body(value as i32) +} + fn ensure_void_void_type(types: &mut Vec) -> u32 { ensure_func_type(types, &wasmparser::FuncType::new([], [])) } @@ -5160,14 +5251,16 @@ fn emit_reserved_linker_definitions( ) { let mut linker_globals = Vec::with_capacity(indices.num_defined_globals as usize); if indices.memory_base_global.is_some() { - layout.memory_base = LINKER_MEMORY_BASE; + if indices.memory_base_is_pic { + layout.memory_base = LINKER_MEMORY_BASE; + } linker_globals.push(OutputGlobal { ty: GlobalType { content_type: wasmparser::ValType::I32, mutable: false, shared: false, }, - init_expr_body: Cow::Borrowed(LINKER_MEMORY_BASE_INIT_EXPR), + init_expr_body: Cow::Borrowed(memory_base_init_expr(indices.memory_base_is_pic)), }); } if indices.table_base_global.is_some() { @@ -5200,6 +5293,16 @@ fn emit_reserved_linker_definitions( init_expr_body: Cow::Borrowed(ZERO_I32_INIT_EXPR), }); } + for _ in &indices.data_address_globals { + linker_globals.push(OutputGlobal { + ty: GlobalType { + content_type: wasmparser::ValType::I32, + mutable: false, + shared: false, + }, + init_expr_body: Cow::Borrowed(ZERO_I32_INIT_EXPR), + }); + } // GOT.mem placeholders. wasm-ld emits static GOT.data.internal.* as immutable i32 for // freestanding executables. for _ in 0..indices.got_mem_count { @@ -5359,7 +5462,7 @@ fn fill_stack_pointer_init( global.ty.mutable && global.ty.content_type == wasmparser::ValType::I32, "Wasm stack pointer global has unexpected type" ); - global.init_expr_body = Cow::Owned(encode_i32_const_body(sp as i32)); + global.init_expr_body = Cow::Owned(encode_i32_const_u32(sp)); Ok(()) } @@ -5499,6 +5602,43 @@ fn ensure_entry_export<'data>( }); } +fn requested_linker_export_symbols(args: &WasmArgs) -> Vec { + let mut symbols = Vec::new(); + for name in args.force_export_symbol_names() { + let Some(sym) = WasmLinkerSymbol::parse(name) else { + continue; + }; + if !symbols.contains(&sym) { + symbols.push(sym); + } + } + symbols +} + +fn try_export_linker_defined( + exports: &mut Vec>, + known: WasmLinkerSymbol, + indices: &LinkerDefinedIndices, +) -> bool { + let name = <&str>::from(known); + if let Some(index) = indices.function_index(known) { + push_function_export(exports, name, index); + return true; + } + if let Some(index) = indices.global_index(known) { + push_global_export(exports, name, index); + return true; + } + false +} + +fn is_requested_linker_export(indices: &LinkerDefinedIndices, name: &str) -> bool { + indices + .requested_exports + .iter() + .any(|&sym| <&str>::from(sym) == name) +} + /// Export symbols requested via `--export` and `--export-if-defined`. fn ensure_force_exports<'data>( exports: &mut Vec>, @@ -5506,9 +5646,27 @@ fn ensure_force_exports<'data>( object_index_maps: &[WasmObjectIndexMap], symbol_db: &SymbolDb<'data, Wasm>, entry: Option<&ResolvedEntry<'data>>, - entry_wrapper_func: Option, + indices: &LinkerDefinedIndices, ) -> Result<()> { + for &known in &indices.requested_exports { + if try_export_linker_defined(exports, known, indices) { + continue; + } + let name = <&str>::from(known); + if symbol_db + .args + .required_export_symbols + .iter() + .any(|required| required == name) + { + bail!("symbol exported via --export not found: {name}"); + } + } + for name in symbol_db.args.force_export_symbol_names() { + if is_requested_linker_export(indices, name) { + continue; + } let required = symbol_db.args.required_export_symbols.contains(name); let Some(symbol_id) = symbol_db.get_unversioned(&UnversionedSymbolName::prehashed(name.as_bytes())) @@ -5550,7 +5708,7 @@ fn ensure_force_exports<'data>( let mut index = remap_wasm_index(&index_map.function_indices, def_sym.index, "function")?; // If this is the entry and we wrap it, export the wrapper. - if let (Some(entry), Some(wrapper)) = (entry, entry_wrapper_func) + if let (Some(entry), Some(wrapper)) = (entry, indices.entry_wrapper_func) && export_name == entry.export_name { index = wrapper; @@ -5662,7 +5820,7 @@ where ensure_stack_size_aligned(stack_size)?; } let mut layout = WasmLayout { - memory_base: if linker_memory || indices.memory_base_global.is_some() { + memory_base: if linker_memory || indices.memory_base_is_pic { LINKER_MEMORY_BASE } else { 0 @@ -5820,6 +5978,15 @@ where heap_end, stack_first, )?; + fill_exported_data_global_inits( + &mut layout, + &indices, + data_start, + data_end, + stack_size, + heap_end, + stack_first, + )?; fill_stack_pointer_init(&mut layout, &indices, stack_size, stack_first)?; ensure_entry_export( &mut layout.exports, @@ -5832,7 +5999,7 @@ where &layout.object_index_maps, symbol_db, entry.as_ref(), - indices.entry_wrapper_func, + &indices, )?; } { @@ -6072,6 +6239,23 @@ impl WasmLinkerSymbol { name.parse().ok() } + fn materialize_on_export(self) -> bool { + // `--export` materializes every linker symbol except `__tls_base`. + !matches!(self, Self::TlsBase) + } + + fn exported_as_data_global(self, has_memory: bool) -> bool { + match self { + Self::DataEnd + | Self::GlobalBase + | Self::HeapBase + | Self::WasmFirstPageEnd + | Self::DsoHandle => true, + Self::HeapEnd => has_memory, + _ => false, + } + } + fn matches_import_kind(self, kind: WasmSymbolKind) -> bool { match self { Self::CallCtors => kind == WasmSymbolKind::Func, @@ -7624,6 +7808,12 @@ mod tests { assert_eq!(features[1].name, "atomics"); } + #[test] + fn memory_base_export_only_is_zero() { + assert_eq!(memory_base_init_expr(false), ZERO_I32_INIT_EXPR); + assert_eq!(memory_base_init_expr(true), LINKER_MEMORY_BASE_INIT_EXPR); + } + #[test] fn linker_defined_data_symbol_addresses() { let data_start = 1024u32; diff --git a/wild/tests/sources/wasm/export-linker-syms/export-linker-syms.c b/wild/tests/sources/wasm/export-linker-syms/export-linker-syms.c new file mode 100644 index 000000000..730b5e0b6 --- /dev/null +++ b/wild/tests/sources/wasm/export-linker-syms/export-linker-syms.c @@ -0,0 +1,39 @@ +// `--export` of linker-defined symbols. + +//#Config:linker-globals +//#LinkArgs: --export=__wasm_call_ctors --export=__stack_pointer --export=__memory_base --export=__table_base --export=__heap_base --export=__data_end --export=__global_base --export=__dso_handle --export=__heap_end --export=__wasm_first_page_end +//#ExpectSym: __wasm_call_ctors +//#ExpectSym: __stack_pointer +//#ExpectSym: __memory_base +//#ExpectSym: __table_base +//#ExpectSym: __heap_base +//#ExpectSym: __data_end +//#ExpectSym: __global_base +//#ExpectSym: __dso_handle +//#ExpectSym: __heap_end +//#ExpectSym: __wasm_first_page_end +//#ExpectSym: _start + +//#Config:export-if-defined +//#LinkArgs: --export-if-defined=__wasm_call_ctors --export-if-defined=__stack_pointer --export-if-defined=__heap_base +//#ExpectSym: __wasm_call_ctors +//#ExpectSym: __stack_pointer +//#ExpectSym: __heap_base +//#ExpectSym: _start + +//#Config:no-entry +//#LinkArgs: --no-entry --export=__wasm_call_ctors +//#RunEnabled: false +//#ExpectSym: __wasm_call_ctors +//#NoSym: _start + +//#Config:tls-base +//#LinkArgs: --export=__tls_base +//#ExpectError: symbol exported via --export not found: __tls_base + +//#Config:tls-base-if-defined +//#LinkArgs: --export-if-defined=__tls_base +//#NoSym: __tls_base +//#ExpectSym: _start + +void _start(void) {}