diff --git a/TODO.md b/TODO.md index 9972f83..623d31d 100644 --- a/TODO.md +++ b/TODO.md @@ -26,14 +26,13 @@ - Store them next to the output file. - Pass an environment variable from a `build.rs` pointing to the target folder and go from there. This seems to have failed. No build script instruction can reach the linker on Wasm. +- Deterministic output of JS files. # Medium Priority - Provide an absolutely minimal allocator. - The `js_sys` proc-macro should remove the `extern "C" { ... }` part of the input on error to avoid triggering the `unsafe` requirement downstream. -- Optimize linker file interactions by using memory mapped files instead of reading and writing - everything into memory. - Run the assembly compiler on the proc-macro level so users see errors without having to engage the linker. - Parse the JS on the proc-macro level so users see errors. E.g. `oxc-parser`. diff --git a/host/Cargo.toml b/host/Cargo.toml index 33b091a..bd3257c 100644 --- a/host/Cargo.toml +++ b/host/Cargo.toml @@ -11,6 +11,7 @@ anyhow = "1" hashbrown = "0.16" itertools = { version = "0.14", default-features = false } js-bindgen-shared = { path = "shared" } +memmap2 = "0.9" object = { version = "0.38", default-features = false, features = ["archive", "read_core", "std"] } prettyplease = "0.2" proc-macro2 = { version = "1", default-features = false } diff --git a/host/ld/Cargo.toml b/host/ld/Cargo.toml index 4290b40..0f6ff3b 100644 --- a/host/ld/Cargo.toml +++ b/host/ld/Cargo.toml @@ -6,6 +6,7 @@ rust-version = "1.91" [dependencies] hashbrown = { workspace = true } itertools = { workspace = true } +memmap2 = { workspace = true } object = { workspace = true } wasm-encoder = { workspace = true } wasmparser = { workspace = true } diff --git a/host/ld/src/main.rs b/host/ld/src/main.rs index b4db367..b5bb843 100644 --- a/host/ld/src/main.rs +++ b/host/ld/src/main.rs @@ -3,6 +3,7 @@ mod wasm_ld; use std::borrow::Cow; use std::ffi::{OsStr, OsString}; use std::fmt::Write as _; +use std::fs::File; use std::io::{Error, Write as _}; use std::path::Path; use std::process::{self, Command, Stdio}; @@ -10,6 +11,7 @@ use std::{env, fs}; use hashbrown::{HashMap, HashSet}; use itertools::{Itertools, Position}; +use memmap2::Mmap; use object::read::archive::ArchiveFile; use wasm_encoder::{EntityType, ImportSection, Module, RawSection, Section}; use wasmparser::{CustomSectionReader, Encoding, Parser, Payload, TypeRef}; @@ -91,7 +93,7 @@ fn main() { // We found a UNIX archive. if input.as_encoded_bytes().ends_with(b".rlib") { let archive_path = Path::new(&input); - let archive_data = match fs::read(archive_path) { + let archive_data = match map_file(archive_path) { Ok(archive_data) => archive_data, Err(error) => { eprintln!( @@ -153,7 +155,7 @@ fn main() { } } else if input.as_encoded_bytes().ends_with(b".o") { let object_path = Path::new(&input); - let object = match fs::read(object_path) { + let object = match map_file(object_path) { Ok(object) => object, Err(error) => { eprintln!( @@ -298,7 +300,7 @@ fn post_processing(output_path: &Path, main_memory: MainMemory<'_>) { // Unfortunately we don't receive the final output path adjustments Cargo makes. // So for the JS file we just figure it out ourselves. let package = env::var_os("CARGO_CRATE_NAME").expect("`CARGO_CRATE_NAME` should be present"); - let wasm_input = fs::read(output_path).expect("output file should be readable"); + let wasm_input = map_file(output_path).expect("output file should be readable"); let mut wasm_output = Vec::new(); let mut found_import: HashMap<&str, HashMap<&str, &str>> = HashMap::new(); @@ -528,8 +530,6 @@ fn post_processing(output_path: &Path, main_memory: MainMemory<'_>) { "missing JS embed: {expected_embed:?}" ); - fs::write(output_path, wasm_output).expect("output Wasm file should be writable"); - let mut js_output = String::new(); // Create our `WebAssembly.Memory`. @@ -612,6 +612,12 @@ fn post_processing(output_path: &Path, main_memory: MainMemory<'_>) { js_output.push_str("}\n"); + // Due to the limitations of mmap, writing back to the original file is actually dangerous, + // and we need to ensure that we will no longer read from it. + // + // Should we need to write to a new file, such as `package.bg.wasm`? + fs::write(output_path, wasm_output).expect("output Wasm file should be writable"); + fs::write( output_path.with_file_name(package).with_extension("js"), js_output, @@ -619,6 +625,12 @@ fn post_processing(output_path: &Path, main_memory: MainMemory<'_>) { .expect("output JS file should be writable"); } +fn map_file(path: &Path) -> Result { + let file = File::open(path)?; + // Safety: the file is not mutated while the mapping is in use. + unsafe { Mmap::map(&file) } +} + struct CustomSectionParser<'cs> { name: &'cs str, data: &'cs [u8],