Skip to content

Commit 6d9bf0d

Browse files
committed
support weak imports when linking
We currently generate trapping stubs for such imports if no suitable export is found. Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent 57faf5b commit 6d9bf0d

File tree

8 files changed

+452
-130
lines changed

8 files changed

+452
-130
lines changed

crates/wit-component/src/linking.rs

Lines changed: 80 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use {
2626
crate::encoding::{ComponentEncoder, Instance, Item, LibraryInfo, MainOrAdapter},
2727
anyhow::{anyhow, bail, Context, Result},
2828
indexmap::IndexSet,
29-
metadata::{Export, FunctionType, GlobalType, Metadata, Type, ValueType},
29+
metadata::{Export, ExportKey, FunctionType, GlobalType, Metadata, Type, ValueType},
3030
std::{
3131
collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
3232
iter,
@@ -37,6 +37,7 @@ use {
3737
Instruction as Ins, MemArg, MemorySection, MemoryType, Module, RawCustomSection, RefType,
3838
StartSection, TableSection, TableType, TypeSection, ValType,
3939
},
40+
wasmparser::WASM_SYM_BINDING_WEAK,
4041
};
4142

4243
mod metadata;
@@ -101,17 +102,17 @@ impl<'a> DlOpenables<'a> {
101102
.iter()
102103
.map(|export| {
103104
let name_address = memory_base + u32::try_from(buffer.len()).unwrap();
104-
write_bytes_padded(&mut buffer, export.name.as_bytes());
105+
write_bytes_padded(&mut buffer, export.key.name.as_bytes());
105106

106-
let address = if let Type::Function(_) = &export.ty {
107+
let address = if let Type::Function(_) = &export.key.ty {
107108
Address::Function(
108109
table_base + get_and_increment(&mut function_count),
109110
)
110111
} else {
111-
Address::Global(export.name)
112+
Address::Global(export.key.name)
112113
};
113114

114-
(export.name, name_address, address)
115+
(export.key.name, name_address, address)
115116
})
116117
.collect::<Vec<_>>();
117118

@@ -405,7 +406,7 @@ fn make_env_module<'a>(
405406
/// This module also contains the data segment for the `dlopen`/`dlsym` lookup table.
406407
fn make_init_module(
407408
metadata: &[Metadata],
408-
exporters: &HashMap<&Export, &str>,
409+
exporters: &HashMap<&ExportKey, (&str, &Export)>,
409410
function_exports: &[(&str, &FunctionType, usize)],
410411
dl_openables: DlOpenables,
411412
indirection_table_base: u32,
@@ -421,7 +422,7 @@ fn make_init_module(
421422
for metadata in metadata {
422423
if metadata.dl_openable {
423424
for export in &metadata.exports {
424-
if let Type::Function(ty) = &export.ty {
425+
if let Type::Function(ty) = &export.key.ty {
425426
types.function(
426427
ty.parameters.iter().copied().map(ValType::from),
427428
ty.results.iter().copied().map(ValType::from),
@@ -552,7 +553,7 @@ fn make_init_module(
552553
}
553554

554555
for import in &metadata.memory_address_imports {
555-
let exporter = find_offset_exporter(import, exporters)?;
556+
let (exporter, _) = find_offset_exporter(import, exporters)?;
556557

557558
memory_address_inits.push(Ins::GlobalGet(add_global_import(
558559
&mut imports,
@@ -580,11 +581,11 @@ fn make_init_module(
580581
for metadata in metadata {
581582
if metadata.dl_openable {
582583
for export in &metadata.exports {
583-
if let Type::Function(_) = &export.ty {
584+
if let Type::Function(_) = &export.key.ty {
584585
dl_openable_functions.push(add_function_import(
585586
&mut imports,
586587
metadata.name,
587-
export.name,
588+
export.key.name,
588589
get_and_increment(&mut type_offset),
589590
));
590591
}
@@ -665,8 +666,11 @@ fn make_init_module(
665666
}
666667

667668
/// Find the library which exports the specified function or global address.
668-
fn find_offset_exporter<'a>(name: &str, exporters: &HashMap<&Export, &'a str>) -> Result<&'a str> {
669-
let export = Export {
669+
fn find_offset_exporter<'a>(
670+
name: &str,
671+
exporters: &HashMap<&ExportKey, (&'a str, &'a Export<'a>)>,
672+
) -> Result<(&'a str, &'a Export<'a>)> {
673+
let export = ExportKey {
670674
name,
671675
ty: Type::Global(GlobalType {
672676
ty: ValueType::I32,
@@ -684,9 +688,9 @@ fn find_offset_exporter<'a>(name: &str, exporters: &HashMap<&Export, &'a str>) -
684688
fn find_function_exporter<'a>(
685689
name: &str,
686690
ty: &FunctionType,
687-
exporters: &HashMap<&Export, &'a str>,
688-
) -> Result<&'a str> {
689-
let export = Export {
691+
exporters: &HashMap<&ExportKey, (&'a str, &'a Export<'a>)>,
692+
) -> Result<(&'a str, &'a Export<'a>)> {
693+
let export = ExportKey {
690694
name,
691695
ty: Type::Function(ty.clone()),
692696
};
@@ -700,11 +704,14 @@ fn find_function_exporter<'a>(
700704
/// Analyze the specified library metadata, producing a symbol-to-library-name map of exports.
701705
fn resolve_exporters<'a>(
702706
metadata: &'a [Metadata<'a>],
703-
) -> Result<HashMap<&'a Export<'a>, Vec<&'a str>>> {
707+
) -> Result<HashMap<&'a ExportKey<'a>, Vec<(&'a str, &'a Export<'a>)>>> {
704708
let mut exporters = HashMap::<_, Vec<_>>::new();
705709
for metadata in metadata {
706710
for export in &metadata.exports {
707-
exporters.entry(export).or_default().push(metadata.name);
711+
exporters
712+
.entry(&export.key)
713+
.or_default()
714+
.push((metadata.name, export));
708715
}
709716
}
710717
Ok(exporters)
@@ -713,11 +720,11 @@ fn resolve_exporters<'a>(
713720
/// Match up all imported symbols to their corresponding exports, reporting any missing or duplicate symbols.
714721
fn resolve_symbols<'a>(
715722
metadata: &'a [Metadata<'a>],
716-
exporters: &'a HashMap<&'a Export<'a>, Vec<&'a str>>,
723+
exporters: &'a HashMap<&'a ExportKey<'a>, Vec<(&'a str, &'a Export<'a>)>>,
717724
) -> (
718-
HashMap<&'a Export<'a>, &'a str>,
725+
HashMap<&'a ExportKey<'a>, (&'a str, &'a Export<'a>)>,
719726
Vec<(&'a str, Export<'a>)>,
720-
Vec<(&'a str, &'a Export<'a>, &'a [&'a str])>,
727+
Vec<(&'a str, &'a ExportKey<'a>, &'a [(&'a str, &'a Export<'a>)])>,
721728
) {
722729
// TODO: consider weak symbols when checking for duplicates
723730

@@ -736,8 +743,8 @@ fn resolve_symbols<'a>(
736743
let mut missing = Vec::new();
737744
let mut duplicates = Vec::new();
738745

739-
let mut triage = |metadata: &'a Metadata, export| {
740-
if let Some((key, value)) = exporters.get_key_value(&export) {
746+
let mut triage = |metadata: &'a Metadata, export: Export<'a>| {
747+
if let Some((key, value)) = exporters.get_key_value(&export.key) {
741748
match value.as_slice() {
742749
[] => unreachable!(),
743750
[exporter] => {
@@ -753,12 +760,15 @@ fn resolve_symbols<'a>(
753760
};
754761

755762
for metadata in metadata {
756-
for (name, ty) in &metadata.env_imports {
763+
for (name, (ty, flags)) in &metadata.env_imports {
757764
triage(
758765
metadata,
759766
Export {
760-
name,
761-
ty: Type::Function(ty.clone()),
767+
key: ExportKey {
768+
name,
769+
ty: Type::Function(ty.clone()),
770+
},
771+
flags: *flags,
762772
},
763773
);
764774
}
@@ -767,11 +777,14 @@ fn resolve_symbols<'a>(
767777
triage(
768778
metadata,
769779
Export {
770-
name,
771-
ty: Type::Global(GlobalType {
772-
ty: ValueType::I32,
773-
mutable: false,
774-
}),
780+
key: ExportKey {
781+
name,
782+
ty: Type::Global(GlobalType {
783+
ty: ValueType::I32,
784+
mutable: false,
785+
}),
786+
},
787+
flags: 0,
775788
},
776789
);
777790
}
@@ -793,11 +806,14 @@ fn resolve_symbols<'a>(
793806
missing.push((
794807
metadata.name,
795808
Export {
796-
name,
797-
ty: Type::Function(FunctionType {
798-
parameters: Vec::new(),
799-
results: Vec::new(),
800-
}),
809+
key: ExportKey {
810+
name,
811+
ty: Type::Function(FunctionType {
812+
parameters: Vec::new(),
813+
results: Vec::new(),
814+
}),
815+
},
816+
flags: 0,
801817
},
802818
));
803819
}
@@ -850,7 +866,7 @@ fn topo_sort(count: usize, dependencies: &HashMap<usize, HashSet<usize>>) -> Res
850866
/// represented by its offset in the original metadata slice.
851867
fn find_dependencies(
852868
metadata: &[Metadata],
853-
exporters: &HashMap<&Export, &str>,
869+
exporters: &HashMap<&ExportKey, (&str, &Export)>,
854870
) -> Result<HashMap<usize, HashSet<usize>>> {
855871
let mut dependencies = HashMap::<_, HashSet<_>>::new();
856872
let mut indexes = HashMap::new();
@@ -862,11 +878,11 @@ fn find_dependencies(
862878
.or_default()
863879
.insert(needed);
864880
}
865-
for (import_name, ty) in &metadata.env_imports {
881+
for (import_name, (ty, _)) in &metadata.env_imports {
866882
dependencies
867883
.entry(metadata.name)
868884
.or_default()
869-
.insert(find_function_exporter(import_name, ty, exporters)?);
885+
.insert(find_function_exporter(import_name, ty, exporters)?.0);
870886
}
871887
}
872888

@@ -909,7 +925,7 @@ fn find_dependencies(
909925
/// the original export.
910926
fn env_function_exports<'a>(
911927
metadata: &'a [Metadata<'a>],
912-
exporters: &'a HashMap<&'a Export, &'a str>,
928+
exporters: &'a HashMap<&'a ExportKey, (&'a str, &Export)>,
913929
topo_sorted: &[usize],
914930
) -> Result<Vec<(&'a str, &'a FunctionType, usize)>> {
915931
let function_exporters = exporters
@@ -938,7 +954,7 @@ fn env_function_exports<'a>(
938954

939955
for name in &metadata.table_address_imports {
940956
if !exported.contains(name) {
941-
let (ty, exporter) = function_exporters
957+
let (ty, (exporter, _)) = function_exporters
942958
.get(name)
943959
.ok_or_else(|| anyhow!("unable to find {name:?} in any library"))?;
944960

@@ -947,9 +963,11 @@ fn env_function_exports<'a>(
947963
}
948964
}
949965

950-
for (import_name, ty) in &metadata.env_imports {
966+
for (import_name, (ty, _)) in &metadata.env_imports {
951967
if !exported.contains(import_name) {
952-
let exporter = indexes[find_function_exporter(import_name, ty, exporters).unwrap()];
968+
let exporter = indexes[find_function_exporter(import_name, ty, exporters)
969+
.unwrap()
970+
.0];
953971
if !seen.contains(&exporter) {
954972
result.push((*import_name, ty, exporter));
955973
exported.insert(*import_name);
@@ -971,7 +989,7 @@ fn make_stubs_module(missing: &[(&str, Export)]) -> Vec<u8> {
971989
for (offset, (_, export)) in missing.iter().enumerate() {
972990
let offset = u32::try_from(offset).unwrap();
973991

974-
let Export { name, ty: Type::Function(ty) } = export else {
992+
let Export { key: ExportKey { name, ty: Type::Function(ty) }, .. } = export else {
975993
unreachable!();
976994
};
977995

@@ -1136,7 +1154,7 @@ impl Linker {
11361154
let mut exporters = resolve_exporters(&metadata)?;
11371155

11381156
let cabi_realloc_exporter = exporters
1139-
.get_mut(&Export {
1157+
.get_mut(&ExportKey {
11401158
name: "cabi_realloc",
11411159
ty: Type::Function(FunctionType {
11421160
parameters: vec![ValueType::I32; 4],
@@ -1147,16 +1165,19 @@ impl Linker {
11471165
// TODO: Verify that there is at most one strong exporter
11481166
let first = *exporters.first().unwrap();
11491167
*exporters = vec![first];
1150-
first
1168+
first.0
11511169
});
11521170

11531171
let (exporters, missing, duplicates) = resolve_symbols(&metadata, &exporters);
11541172

11551173
if !missing.is_empty() {
1156-
if self.stub_missing_functions
1157-
&& missing
1158-
.iter()
1159-
.all(|(_, export)| matches!(&export.ty, Type::Function(_)))
1174+
if missing
1175+
.iter()
1176+
.all(|(_, export)| matches!(&export.key.ty, Type::Function(_)))
1177+
&& (self.stub_missing_functions
1178+
|| missing
1179+
.iter()
1180+
.all(|(_, export)| 0 != (export.flags & WASM_SYM_BINDING_WEAK)))
11601181
{
11611182
self.stub_missing_functions = false;
11621183
self.libraries.push((
@@ -1166,11 +1187,18 @@ impl Linker {
11661187
));
11671188
return self.encode();
11681189
} else {
1169-
bail!("unresolved symbol(s): {missing:#?}")
1190+
bail!(
1191+
"unresolved symbol(s): {:#?}",
1192+
missing
1193+
.iter()
1194+
.filter(|(_, export)| 0 == (export.flags & WASM_SYM_BINDING_WEAK))
1195+
.collect::<Vec<_>>()
1196+
);
11701197
}
11711198
}
11721199

11731200
if !duplicates.is_empty() {
1201+
// TODO: Check for weak symbols before giving up here
11741202
bail!("duplicate symbol(s): {duplicates:#?}");
11751203
}
11761204

@@ -1249,8 +1277,8 @@ impl Linker {
12491277
name: format!("{name}:table_base"),
12501278
},
12511279
])
1252-
.chain(metadata.env_imports.iter().map(|(name, ty)| {
1253-
let exporter = find_function_exporter(name, ty, &exporters).unwrap();
1280+
.chain(metadata.env_imports.iter().map(|(name, (ty, _))| {
1281+
let (exporter, _) = find_function_exporter(name, ty, &exporters).unwrap();
12541282

12551283
Item {
12561284
alias: (*name).into(),

0 commit comments

Comments
 (0)