Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make CI deterministic try 2 #4190

Merged
merged 10 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/cli-support/src/externref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn process(module: &mut Module) -> Result<()> {

// Transform all exported functions in the module, using the bindings listed
// for each exported function.
for (id, adapter) in crate::sorted_iter_mut(&mut section.adapters) {
for (id, adapter) in &mut section.adapters {
let instructions = match &mut adapter.kind {
AdapterKind::Local { instructions } => instructions,
AdapterKind::Import { .. } => continue,
Expand Down Expand Up @@ -78,7 +78,7 @@ pub fn process(module: &mut Module) -> Result<()> {
// Additionally we may need to update some adapter instructions other than
// those found for the externref pass. These are some general "fringe support"
// things necessary to get absolutely everything working.
for (_, adapter) in crate::sorted_iter_mut(&mut section.adapters) {
for adapter in &mut section.adapters.values_mut() {
let instrs = match &mut adapter.kind {
AdapterKind::Local { instructions } => instructions,
AdapterKind::Import { .. } => continue,
Expand Down
157 changes: 121 additions & 36 deletions crates/cli-support/src/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ impl<'a> Context<'a> {

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

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

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

for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
let import = self.module.imports.get_mut(*id);
for (id, js) in iter_by_import(&self.wasm_import_definitions, self.module) {
let import = self.module.imports.get(*id);
footer.push_str("\nmodule.exports.");
footer.push_str(&import.name);
footer.push_str(" = ");
Expand Down Expand Up @@ -455,7 +455,7 @@ impl<'a> Context<'a> {
// and let the bundler/runtime take care of it.
// With Node we manually read the Wasm file from the filesystem and instantiate it.
OutputMode::Bundler { .. } | OutputMode::Node { module: true } => {
for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
for (id, js) in iter_by_import(&self.wasm_import_definitions, self.module) {
let import = self.module.imports.get_mut(*id);
import.module = format!("./{}_bg.js", module_name);
if let Some(body) = js.strip_prefix("function") {
Expand Down Expand Up @@ -783,7 +783,7 @@ __wbg_set_wasm(wasm);"
imports_init.push_str(module_name);
imports_init.push_str(" = {};\n");

for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
for (id, js) in iter_by_import(&self.wasm_import_definitions, self.module) {
let import = self.module.imports.get_mut(*id);
import.module = module_name.to_string();
imports_init.push_str("imports.");
Expand Down Expand Up @@ -2523,12 +2523,13 @@ __wbg_set_wasm(wasm);"
if self.config.symbol_dispose && !self.aux.structs.is_empty() {
self.expose_symbol_dispose()?;
}
for (id, adapter) in crate::sorted_iter(&self.wit.adapters) {

for (id, adapter, kind) in iter_adapeter(self.aux, self.wit, self.module) {
let instrs = match &adapter.kind {
AdapterKind::Import { .. } => continue,
AdapterKind::Local { instructions } => instructions,
};
self.generate_adapter(*id, adapter, instrs)?;
self.generate_adapter(id, adapter, instrs, kind)?;
}

let mut pairs = self.aux.export_map.iter().collect::<Vec<_>>();
Expand Down Expand Up @@ -2606,26 +2607,10 @@ __wbg_set_wasm(wasm);"
id: AdapterId,
adapter: &Adapter,
instrs: &[InstructionData],
kind: ContextAdapterKind,
) -> Result<(), Error> {
enum Kind<'a> {
Export(&'a AuxExport),
Import(walrus::ImportId),
Adapter,
}

let kind = match self.aux.export_map.get(&id) {
Some(export) => Kind::Export(export),
None => {
let core = self.wit.implements.iter().find(|pair| pair.2 == id);
match core {
Some((core, _, _)) => Kind::Import(*core),
None => Kind::Adapter,
}
}
};

let catch = self.aux.imports_with_catch.contains(&id);
if let Kind::Import(core) = kind {
if let ContextAdapterKind::Import(core) = kind {
if !catch && self.attempt_direct_import(core, instrs)? {
return Ok(());
}
Expand All @@ -2635,16 +2620,16 @@ __wbg_set_wasm(wasm);"
// export that we're generating.
let mut builder = binding::Builder::new(self);
builder.log_error(match kind {
Kind::Export(_) | Kind::Adapter => false,
Kind::Import(_) => builder.cx.config.debug,
ContextAdapterKind::Export(_) | ContextAdapterKind::Adapter => false,
ContextAdapterKind::Import(_) => builder.cx.config.debug,
});
builder.catch(catch);
let mut arg_names = &None;
let mut asyncness = false;
let mut variadic = false;
let mut generate_jsdoc = false;
match kind {
Kind::Export(export) => {
ContextAdapterKind::Export(export) => {
arg_names = &export.arg_names;
asyncness = export.asyncness;
variadic = export.variadic;
Expand All @@ -2659,8 +2644,8 @@ __wbg_set_wasm(wasm);"
},
}
}
Kind::Import(_) => {}
Kind::Adapter => {}
ContextAdapterKind::Import(_) => {}
ContextAdapterKind::Adapter => {}
}

// Process the `binding` and generate a bunch of JS/TypeScript/etc.
Expand All @@ -2684,23 +2669,27 @@ __wbg_set_wasm(wasm);"
generate_jsdoc,
)
.with_context(|| match kind {
Kind::Export(e) => format!("failed to generate bindings for `{}`", e.debug_name),
Kind::Import(i) => {
ContextAdapterKind::Export(e) => {
format!("failed to generate bindings for `{}`", e.debug_name)
}
ContextAdapterKind::Import(i) => {
let i = builder.cx.module.imports.get(i);
format!(
"failed to generate bindings for import of `{}::{}`",
i.module, i.name
)
}
Kind::Adapter => "failed to generates bindings for adapter".to_string(),
ContextAdapterKind::Adapter => {
"failed to generates bindings for adapter".to_string()
}
})?;

self.typescript_refs.extend(ts_refs);

// Once we've got all the JS then put it in the right location depending
// on what's being exported.
match kind {
Kind::Export(export) => {
ContextAdapterKind::Export(export) => {
assert!(!catch);
assert!(!log_error);

Expand Down Expand Up @@ -2787,7 +2776,7 @@ __wbg_set_wasm(wasm);"
}
}
}
Kind::Import(core) => {
ContextAdapterKind::Import(core) => {
let code = if catch {
format!(
"function() {{ return handleError(function {}, arguments) }}",
Expand All @@ -2804,7 +2793,7 @@ __wbg_set_wasm(wasm);"

self.wasm_import_definitions.insert(core, code);
}
Kind::Adapter => {
ContextAdapterKind::Adapter => {
assert!(!catch);
assert!(!log_error);

Expand Down Expand Up @@ -4122,6 +4111,102 @@ __wbg_set_wasm(wasm);"
}
}

/// A categorization of adapters for the purpose of code generation.
///
/// This is different from [`AdapterKind`] and is only used internally in the
/// code generation process.
enum ContextAdapterKind<'a> {
/// An exported function, method, constrctor, or getter/setter.
Export(&'a AuxExport),
/// An imported function or intrinsic.
Import(walrus::ImportId),
Adapter,
}
impl<'a> ContextAdapterKind<'a> {
fn get(id: AdapterId, aux: &'a WasmBindgenAux, wit: &'a NonstandardWitSection) -> Self {
match aux.export_map.get(&id) {
Some(export) => ContextAdapterKind::Export(export),
None => {
let core = wit.implements.iter().find(|pair| pair.2 == id);
match core {
Some((core, _, _)) => ContextAdapterKind::Import(*core),
None => ContextAdapterKind::Adapter,
}
}
}
}
}

/// Iterate over the adapters in a deterministic order.
fn iter_adapeter<'a>(
aux: &'a WasmBindgenAux,
wit: &'a NonstandardWitSection,
module: &Module,
) -> Vec<(AdapterId, &'a Adapter, ContextAdapterKind<'a>)> {
let mut adapters: Vec<_> = wit
.adapters
.iter()
.map(|(id, adapter)| {
// we need the kind of the adapter to properly sort them
let kind = ContextAdapterKind::get(*id, aux, wit);
(*id, adapter, kind)
})
.collect();

// Since `wit.adapters` is a BTreeMap, the adapters are already sorted by
// their ID. This is good enough for exports and adapters, but imports need
// to be sorted by their name.
//
// Note: we do *NOT* want to sort exports by name. By default, exports are
// the order in which they were defined in the Rust code. Sorting them by
// name would break that order and take away control from the user.

adapters.sort_by(|(_, _, a), (_, _, b)| {
fn get_kind_order(kind: &ContextAdapterKind) -> u8 {
match kind {
ContextAdapterKind::Import(_) => 0,
ContextAdapterKind::Export(_) => 1,
ContextAdapterKind::Adapter => 2,
}
}

match (a, b) {
(ContextAdapterKind::Import(a), ContextAdapterKind::Import(b)) => {
let a = module.imports.get(*a);
let b = module.imports.get(*b);
a.name.cmp(&b.name)
}
_ => get_kind_order(a).cmp(&get_kind_order(b)),
}
});

adapters
}

/// Iterate over the imports in a deterministic order.
fn iter_by_import<'a, T>(
map: &'a HashMap<ImportId, T>,
module: &Module,
) -> Vec<(&'a ImportId, &'a T)> {
let mut items: Vec<_> = map.iter().collect();

// Sort by import name.
//
// Imports have a name and a module, and it's important that we *ignore*
// the module. The module of an import is set to its final value *during*
// code generation, so using it here would cause the imports to be sorted
// differently depending on which part of the code generation process we're
// in.
items.sort_by(|&(a, _), &(b, _)| {
let a = module.imports.get(*a);
let b = module.imports.get(*b);

a.name.cmp(&b.name)
});
daxpedda marked this conversation as resolved.
Show resolved Hide resolved

items
}

fn check_duplicated_getter_and_setter_names(
exports: &[(&AdapterId, &AuxExport)],
) -> Result<(), Error> {
Expand Down
2 changes: 1 addition & 1 deletion crates/cli-support/src/multivalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub fn run(module: &mut Module) -> Result<(), Error> {
let mut to_xform = Vec::new();
let mut slots = Vec::new();

for (_, adapter) in crate::sorted_iter_mut(&mut adapters.adapters) {
for adapter in adapters.adapters.values_mut() {
extract_xform(module, adapter, &mut to_xform, &mut slots);
}
if to_xform.is_empty() {
Expand Down
6 changes: 4 additions & 2 deletions crates/cli-support/src/wit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::descriptors::WasmBindgenDescriptorsSection;
use crate::intrinsic::Intrinsic;
use crate::{decode, Bindgen, PLACEHOLDER_MODULE};
use anyhow::{anyhow, bail, Error};
use std::collections::{HashMap, HashSet};
use std::collections::{BTreeSet, HashMap};
use std::str;
use walrus::MemoryId;
use walrus::{ExportId, FunctionId, ImportId, Module};
Expand Down Expand Up @@ -106,7 +106,9 @@ impl<'a> Context<'a> {
// location listed of what to import there for each item.
let mut intrinsics = Vec::new();
let mut duplicate_import_map = HashMap::new();
let mut imports_to_delete = HashSet::new();
// The order in which imports are deleted later might matter, so we
// use an ordered set here to make everything deterministic.
let mut imports_to_delete = BTreeSet::new();
for import in self.module.imports.iter() {
if import.module != PLACEHOLDER_MODULE {
continue;
Expand Down
8 changes: 6 additions & 2 deletions crates/cli-support/src/wit/standard.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use crate::descriptor::VectorKind;
use crate::wit::{AuxImport, WasmBindgenAux};
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::collections::{BTreeMap, HashSet};
use walrus::{FunctionId, ImportId, RefType, TypedCustomSectionId};

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

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