Skip to content

Commit

Permalink
feat: wasm text format and import support (vercel/turborepo#5636)
Browse files Browse the repository at this point in the history
  • Loading branch information
ForsakenHarmony committed Aug 3, 2023
1 parent 53cff8e commit 3f25683
Show file tree
Hide file tree
Showing 20 changed files with 749 additions and 239 deletions.
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..];
}

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

Ok(analysis.cell())
}
Loading

0 comments on commit 3f25683

Please sign in to comment.