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

feat: wasm text format and import support #5636

Merged
merged 2 commits into from
Aug 3, 2023
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
154 changes: 94 additions & 60 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion crates/turbopack-ecmascript/src/chunk/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use crate::{
};

#[turbo_tasks::value(shared)]
#[derive(Default)]
#[derive(Default, Clone)]
pub struct EcmascriptChunkItemContent {
pub inner_code: Rope,
pub source_map: Option<Vc<ParseResultSourceMap>>,
Expand Down
2 changes: 1 addition & 1 deletion crates/turbopack-ecmascript/src/references/esm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub use self::{
base::EsmAssetReference,
binding::EsmBinding,
dynamic::EsmAsyncAssetReference,
export::EsmExports,
export::{EsmExport, EsmExports},
meta::{ImportMetaBinding, ImportMetaRef},
module_item::EsmModuleItem,
url::UrlAssetReference,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Adapted from webpack
https://github.com/webpack/webpack/blob/6be4065ade1e252c1d8dcba4af0f43e32af1bdc1/examples/wasm-complex/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
describe("complex wasm", () => {
it("should be possible to use imported memory", async () => {
// magic.js is an async module, so we import it inside this function to make sure the entrypoint isn't async.
const { get, set } = await import("./magic.js");

set(42);
expect(get()).toEqual(42);
set(123);
expect(get()).toEqual(123);
});

it("should be possible to use imported functions", async () => {
// magic.js is an async module, so we import it inside this function to make sure the entrypoint isn't async.
const { getNumber } = await import("./magic.js");

// random numbers
expect(getNumber()).toBeGreaterThanOrEqual(0);
expect(getNumber()).toBeGreaterThanOrEqual(0);
});
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function getNumber() {
return 42;
}

export function getRandomNumber() {
return Math.floor(Math.random() * 256);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// reexporting
export * from "./magic.wat";
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
(module
(type $t0 (func (result i32)))
(type $t1 (func (param i32)))
(import "./memory.js" "memory" (memory 1))
(import "./magic-number.js" "getRandomNumber" (func $getRandomNumber (type $t0)))
(func $get (export "get") (type $t0) (result i32)
(i32.load
(i32.const 0)))
(func $set (export "set") (type $t1) (param $p i32)
(i32.store
(i32.const 0)
(get_local $p)))
(func $getNumber (export "getNumber") (type $t0) (result i32)
(call $getRandomNumber))
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
async function getMemoryFromParentInWorker() {
await new Promise(r => setTimeout(r, 200));
// fake
return new WebAssembly.Memory({ initial: 1 });
}

export const memory = await getMemoryFromParentInWorker();
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
it("should handle wasm imports", async () => {
// math.js is an async module, so we import it in here
// magic.js is an async module, so we import it inside this function to make sure the entrypoint isn't async.
const {
add,
factorial,
Expand Down
8 changes: 4 additions & 4 deletions crates/turbopack-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ bench = false
[dependencies]
anyhow = { workspace = true }
indexmap = { workspace = true }

indoc = { workspace = true }
serde = { workspace = true }
turbo-tasks = { workspace = true }
turbo-tasks-fs = { workspace = true }
turbo-tasks-hash = { workspace = true }
turbopack-core = { workspace = true }
turbopack-css = { workspace = true }
turbopack-ecmascript = { workspace = true }

serde = { workspace = true }
wasmparser = "0.110.0"
wat = "1.0.69"

[build-dependencies]
turbo-tasks-build = { workspace = true }
77 changes: 77 additions & 0 deletions crates/turbopack-wasm/src/analysis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use std::collections::BTreeMap;

use anyhow::Result;
use turbo_tasks::Vc;
use turbo_tasks_fs::FileContent;
use turbopack_core::asset::Asset;
use wasmparser::{Chunk, Parser, Payload};

use crate::source::WebAssemblySource;

/// Imports and exports of a WebAssembly file.
#[turbo_tasks::value]
#[derive(Default)]
pub(crate) struct WebAssemblyAnalysis {
pub imports: BTreeMap<String, Vec<String>>,
pub exports: Vec<String>,
}

/// Analyse a WebAssembly file.
///
/// Extracts imports and exports.
#[turbo_tasks::function]
pub(crate) async fn analyze(source: Vc<WebAssemblySource>) -> Result<Vc<WebAssemblyAnalysis>> {
let content = source.content().file_content().await?;

let mut analysis = WebAssemblyAnalysis::default();

let FileContent::Content(file) = &*content else {
return Ok(analysis.cell());
};

let mut bytes = &*file.content().to_bytes()?;

let mut parser = Parser::new(0);
loop {
let payload = match parser.parse(bytes, true)? {
Chunk::Parsed { consumed, payload } => {
bytes = &bytes[consumed..];
payload
}
// this state isn't possible with `eof = true`
Chunk::NeedMoreData(_) => unreachable!(),
};

match payload {
Payload::ImportSection(s) => {
for import in s {
let import = import?;

analysis
.imports
.entry(import.module.to_string())
.or_default()
.push(import.name.to_string());
}
}
Payload::ExportSection(s) => {
for export in s {
let export = export?;

analysis.exports.push(export.name.to_string());
}
}

// skip over code sections
Payload::CodeSectionStart { size, .. } => {
parser.skip_section();
bytes = &bytes[size as usize..];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this double skip? We're already advancing consumed bytes, does consumed not include the range of the code section?

Copy link
Contributor Author

@ForsakenHarmony ForsakenHarmony Aug 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because we tell the parser to skip the next section

weird API tbh, I would expect to give it the slice of bytes once, and have it keep a cursor internally

}

Payload::End(_) => break,
_ => {}
}
}

Ok(analysis.cell())
}
Loading
Loading