Skip to content

Commit ae1d105

Browse files
Make CI deterministic try 2 (rustwasm#4190)
1 parent a69e9cc commit ae1d105

File tree

10 files changed

+233
-142
lines changed

10 files changed

+233
-142
lines changed

crates/cli-support/src/externref.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub fn process(module: &mut Module) -> Result<()> {
2626

2727
// Transform all exported functions in the module, using the bindings listed
2828
// for each exported function.
29-
for (id, adapter) in crate::sorted_iter_mut(&mut section.adapters) {
29+
for (id, adapter) in &mut section.adapters {
3030
let instructions = match &mut adapter.kind {
3131
AdapterKind::Local { instructions } => instructions,
3232
AdapterKind::Import { .. } => continue,
@@ -78,7 +78,7 @@ pub fn process(module: &mut Module) -> Result<()> {
7878
// Additionally we may need to update some adapter instructions other than
7979
// those found for the externref pass. These are some general "fringe support"
8080
// things necessary to get absolutely everything working.
81-
for (_, adapter) in crate::sorted_iter_mut(&mut section.adapters) {
81+
for adapter in &mut section.adapters.values_mut() {
8282
let instrs = match &mut adapter.kind {
8383
AdapterKind::Local { instructions } => instructions,
8484
AdapterKind::Import { .. } => continue,

crates/cli-support/src/js/mod.rs

Lines changed: 121 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ impl<'a> Context<'a> {
325325

326326
wasm_import_object.push_str(&format!(" {}: {{\n", crate::PLACEHOLDER_MODULE));
327327

328-
for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
328+
for (id, js) in iter_by_import(&self.wasm_import_definitions, self.module) {
329329
let import = self.module.imports.get(*id);
330330
wasm_import_object.push_str(&format!("{}: {},\n", &import.name, js.trim()));
331331
}
@@ -424,8 +424,8 @@ impl<'a> Context<'a> {
424424

425425
js.push_str("let wasm;\n");
426426

427-
for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
428-
let import = self.module.imports.get_mut(*id);
427+
for (id, js) in iter_by_import(&self.wasm_import_definitions, self.module) {
428+
let import = self.module.imports.get(*id);
429429
footer.push_str("\nmodule.exports.");
430430
footer.push_str(&import.name);
431431
footer.push_str(" = ");
@@ -463,7 +463,7 @@ impl<'a> Context<'a> {
463463
// and let the bundler/runtime take care of it.
464464
// With Node we manually read the Wasm file from the filesystem and instantiate it.
465465
OutputMode::Bundler { .. } | OutputMode::Node { module: true } => {
466-
for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
466+
for (id, js) in iter_by_import(&self.wasm_import_definitions, self.module) {
467467
let import = self.module.imports.get_mut(*id);
468468
import.module = format!("./{}_bg.js", module_name);
469469
if let Some(body) = js.strip_prefix("function") {
@@ -791,7 +791,7 @@ __wbg_set_wasm(wasm);"
791791
imports_init.push_str(module_name);
792792
imports_init.push_str(" = {};\n");
793793

794-
for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
794+
for (id, js) in iter_by_import(&self.wasm_import_definitions, self.module) {
795795
let import = self.module.imports.get_mut(*id);
796796
import.module = module_name.to_string();
797797
imports_init.push_str("imports.");
@@ -2531,12 +2531,13 @@ __wbg_set_wasm(wasm);"
25312531
if self.config.symbol_dispose && !self.aux.structs.is_empty() {
25322532
self.expose_symbol_dispose()?;
25332533
}
2534-
for (id, adapter) in crate::sorted_iter(&self.wit.adapters) {
2534+
2535+
for (id, adapter, kind) in iter_adapeter(self.aux, self.wit, self.module) {
25352536
let instrs = match &adapter.kind {
25362537
AdapterKind::Import { .. } => continue,
25372538
AdapterKind::Local { instructions } => instructions,
25382539
};
2539-
self.generate_adapter(*id, adapter, instrs)?;
2540+
self.generate_adapter(id, adapter, instrs, kind)?;
25402541
}
25412542

25422543
let mut pairs = self.aux.export_map.iter().collect::<Vec<_>>();
@@ -2614,26 +2615,10 @@ __wbg_set_wasm(wasm);"
26142615
id: AdapterId,
26152616
adapter: &Adapter,
26162617
instrs: &[InstructionData],
2618+
kind: ContextAdapterKind,
26172619
) -> Result<(), Error> {
2618-
enum Kind<'a> {
2619-
Export(&'a AuxExport),
2620-
Import(walrus::ImportId),
2621-
Adapter,
2622-
}
2623-
2624-
let kind = match self.aux.export_map.get(&id) {
2625-
Some(export) => Kind::Export(export),
2626-
None => {
2627-
let core = self.wit.implements.iter().find(|pair| pair.2 == id);
2628-
match core {
2629-
Some((core, _, _)) => Kind::Import(*core),
2630-
None => Kind::Adapter,
2631-
}
2632-
}
2633-
};
2634-
26352620
let catch = self.aux.imports_with_catch.contains(&id);
2636-
if let Kind::Import(core) = kind {
2621+
if let ContextAdapterKind::Import(core) = kind {
26372622
if !catch && self.attempt_direct_import(core, instrs)? {
26382623
return Ok(());
26392624
}
@@ -2643,16 +2628,16 @@ __wbg_set_wasm(wasm);"
26432628
// export that we're generating.
26442629
let mut builder = binding::Builder::new(self);
26452630
builder.log_error(match kind {
2646-
Kind::Export(_) | Kind::Adapter => false,
2647-
Kind::Import(_) => builder.cx.config.debug,
2631+
ContextAdapterKind::Export(_) | ContextAdapterKind::Adapter => false,
2632+
ContextAdapterKind::Import(_) => builder.cx.config.debug,
26482633
});
26492634
builder.catch(catch);
26502635
let mut arg_names = &None;
26512636
let mut asyncness = false;
26522637
let mut variadic = false;
26532638
let mut generate_jsdoc = false;
26542639
match kind {
2655-
Kind::Export(export) => {
2640+
ContextAdapterKind::Export(export) => {
26562641
arg_names = &export.arg_names;
26572642
asyncness = export.asyncness;
26582643
variadic = export.variadic;
@@ -2667,8 +2652,8 @@ __wbg_set_wasm(wasm);"
26672652
},
26682653
}
26692654
}
2670-
Kind::Import(_) => {}
2671-
Kind::Adapter => {}
2655+
ContextAdapterKind::Import(_) => {}
2656+
ContextAdapterKind::Adapter => {}
26722657
}
26732658

26742659
// Process the `binding` and generate a bunch of JS/TypeScript/etc.
@@ -2692,23 +2677,27 @@ __wbg_set_wasm(wasm);"
26922677
generate_jsdoc,
26932678
)
26942679
.with_context(|| match kind {
2695-
Kind::Export(e) => format!("failed to generate bindings for `{}`", e.debug_name),
2696-
Kind::Import(i) => {
2680+
ContextAdapterKind::Export(e) => {
2681+
format!("failed to generate bindings for `{}`", e.debug_name)
2682+
}
2683+
ContextAdapterKind::Import(i) => {
26972684
let i = builder.cx.module.imports.get(i);
26982685
format!(
26992686
"failed to generate bindings for import of `{}::{}`",
27002687
i.module, i.name
27012688
)
27022689
}
2703-
Kind::Adapter => "failed to generates bindings for adapter".to_string(),
2690+
ContextAdapterKind::Adapter => {
2691+
"failed to generates bindings for adapter".to_string()
2692+
}
27042693
})?;
27052694

27062695
self.typescript_refs.extend(ts_refs);
27072696

27082697
// Once we've got all the JS then put it in the right location depending
27092698
// on what's being exported.
27102699
match kind {
2711-
Kind::Export(export) => {
2700+
ContextAdapterKind::Export(export) => {
27122701
assert!(!catch);
27132702
assert!(!log_error);
27142703

@@ -2795,7 +2784,7 @@ __wbg_set_wasm(wasm);"
27952784
}
27962785
}
27972786
}
2798-
Kind::Import(core) => {
2787+
ContextAdapterKind::Import(core) => {
27992788
let code = if catch {
28002789
format!(
28012790
"function() {{ return handleError(function {}, arguments) }}",
@@ -2812,7 +2801,7 @@ __wbg_set_wasm(wasm);"
28122801

28132802
self.wasm_import_definitions.insert(core, code);
28142803
}
2815-
Kind::Adapter => {
2804+
ContextAdapterKind::Adapter => {
28162805
assert!(!catch);
28172806
assert!(!log_error);
28182807

@@ -4137,6 +4126,102 @@ __wbg_set_wasm(wasm);"
41374126
}
41384127
}
41394128

4129+
/// A categorization of adapters for the purpose of code generation.
4130+
///
4131+
/// This is different from [`AdapterKind`] and is only used internally in the
4132+
/// code generation process.
4133+
enum ContextAdapterKind<'a> {
4134+
/// An exported function, method, constrctor, or getter/setter.
4135+
Export(&'a AuxExport),
4136+
/// An imported function or intrinsic.
4137+
Import(walrus::ImportId),
4138+
Adapter,
4139+
}
4140+
impl<'a> ContextAdapterKind<'a> {
4141+
fn get(id: AdapterId, aux: &'a WasmBindgenAux, wit: &'a NonstandardWitSection) -> Self {
4142+
match aux.export_map.get(&id) {
4143+
Some(export) => ContextAdapterKind::Export(export),
4144+
None => {
4145+
let core = wit.implements.iter().find(|pair| pair.2 == id);
4146+
match core {
4147+
Some((core, _, _)) => ContextAdapterKind::Import(*core),
4148+
None => ContextAdapterKind::Adapter,
4149+
}
4150+
}
4151+
}
4152+
}
4153+
}
4154+
4155+
/// Iterate over the adapters in a deterministic order.
4156+
fn iter_adapeter<'a>(
4157+
aux: &'a WasmBindgenAux,
4158+
wit: &'a NonstandardWitSection,
4159+
module: &Module,
4160+
) -> Vec<(AdapterId, &'a Adapter, ContextAdapterKind<'a>)> {
4161+
let mut adapters: Vec<_> = wit
4162+
.adapters
4163+
.iter()
4164+
.map(|(id, adapter)| {
4165+
// we need the kind of the adapter to properly sort them
4166+
let kind = ContextAdapterKind::get(*id, aux, wit);
4167+
(*id, adapter, kind)
4168+
})
4169+
.collect();
4170+
4171+
// Since `wit.adapters` is a BTreeMap, the adapters are already sorted by
4172+
// their ID. This is good enough for exports and adapters, but imports need
4173+
// to be sorted by their name.
4174+
//
4175+
// Note: we do *NOT* want to sort exports by name. By default, exports are
4176+
// the order in which they were defined in the Rust code. Sorting them by
4177+
// name would break that order and take away control from the user.
4178+
4179+
adapters.sort_by(|(_, _, a), (_, _, b)| {
4180+
fn get_kind_order(kind: &ContextAdapterKind) -> u8 {
4181+
match kind {
4182+
ContextAdapterKind::Import(_) => 0,
4183+
ContextAdapterKind::Export(_) => 1,
4184+
ContextAdapterKind::Adapter => 2,
4185+
}
4186+
}
4187+
4188+
match (a, b) {
4189+
(ContextAdapterKind::Import(a), ContextAdapterKind::Import(b)) => {
4190+
let a = module.imports.get(*a);
4191+
let b = module.imports.get(*b);
4192+
a.name.cmp(&b.name)
4193+
}
4194+
_ => get_kind_order(a).cmp(&get_kind_order(b)),
4195+
}
4196+
});
4197+
4198+
adapters
4199+
}
4200+
4201+
/// Iterate over the imports in a deterministic order.
4202+
fn iter_by_import<'a, T>(
4203+
map: &'a HashMap<ImportId, T>,
4204+
module: &Module,
4205+
) -> Vec<(&'a ImportId, &'a T)> {
4206+
let mut items: Vec<_> = map.iter().collect();
4207+
4208+
// Sort by import name.
4209+
//
4210+
// Imports have a name and a module, and it's important that we *ignore*
4211+
// the module. The module of an import is set to its final value *during*
4212+
// code generation, so using it here would cause the imports to be sorted
4213+
// differently depending on which part of the code generation process we're
4214+
// in.
4215+
items.sort_by(|&(a, _), &(b, _)| {
4216+
let a = module.imports.get(*a);
4217+
let b = module.imports.get(*b);
4218+
4219+
a.name.cmp(&b.name)
4220+
});
4221+
4222+
items
4223+
}
4224+
41404225
fn check_duplicated_getter_and_setter_names(
41414226
exports: &[(&AdapterId, &AuxExport)],
41424227
) -> Result<(), Error> {

crates/cli-support/src/multivalue.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub fn run(module: &mut Module) -> Result<(), Error> {
1414
let mut to_xform = Vec::new();
1515
let mut slots = Vec::new();
1616

17-
for (_, adapter) in crate::sorted_iter_mut(&mut adapters.adapters) {
17+
for adapter in adapters.adapters.values_mut() {
1818
extract_xform(module, adapter, &mut to_xform, &mut slots);
1919
}
2020
if to_xform.is_empty() {

crates/cli-support/src/wit/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::descriptors::WasmBindgenDescriptorsSection;
44
use crate::intrinsic::Intrinsic;
55
use crate::{decode, Bindgen, PLACEHOLDER_MODULE};
66
use anyhow::{anyhow, bail, Error};
7-
use std::collections::{HashMap, HashSet};
7+
use std::collections::{BTreeSet, HashMap};
88
use std::str;
99
use walrus::MemoryId;
1010
use walrus::{ExportId, FunctionId, ImportId, Module};
@@ -106,7 +106,9 @@ impl<'a> Context<'a> {
106106
// location listed of what to import there for each item.
107107
let mut intrinsics = Vec::new();
108108
let mut duplicate_import_map = HashMap::new();
109-
let mut imports_to_delete = HashSet::new();
109+
// The order in which imports are deleted later might matter, so we
110+
// use an ordered set here to make everything deterministic.
111+
let mut imports_to_delete = BTreeSet::new();
110112
for import in self.module.imports.iter() {
111113
if import.module != PLACEHOLDER_MODULE {
112114
continue;

crates/cli-support/src/wit/standard.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
use crate::descriptor::VectorKind;
22
use crate::wit::{AuxImport, WasmBindgenAux};
33
use std::borrow::Cow;
4-
use std::collections::{HashMap, HashSet};
4+
use std::collections::{BTreeMap, HashSet};
55
use walrus::{FunctionId, ImportId, RefType, TypedCustomSectionId};
66

77
#[derive(Default, Debug)]
88
pub struct NonstandardWitSection {
99
/// A list of adapter functions, keyed by their id.
10-
pub adapters: HashMap<AdapterId, Adapter>,
10+
///
11+
/// This map is iterated over in multiple places, so we use an ordered map
12+
/// to ensure that the order of iteration is deterministic. This map affects
13+
/// all parts of the generated code, so it's important to get this right.
14+
pub adapters: BTreeMap<AdapterId, Adapter>,
1115

1216
/// A list of pairs for adapter functions that implement core Wasm imports.
1317
pub implements: Vec<(ImportId, FunctionId, AdapterId)>,

0 commit comments

Comments
 (0)