Skip to content

Commit

Permalink
feat: basic wasm support for turbopack
Browse files Browse the repository at this point in the history
  • Loading branch information
ForsakenHarmony committed Jul 26, 2023
1 parent 5ff0ab2 commit 282591c
Show file tree
Hide file tree
Showing 39 changed files with 526 additions and 72 deletions.
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ default-members = [
"crates/turbopack-swc-utils",
"crates/turbopack-test-utils",
"crates/turbopack-tests",
"crates/turbopack-wasm",
"xtask",
]

Expand Down Expand Up @@ -126,6 +127,7 @@ turbopack-static = { path = "crates/turbopack-static" }
turbopack-swc-utils = { path = "crates/turbopack-swc-utils" }
turbopack-test-utils = { path = "crates/turbopack-test-utils" }
turbopack-tests = { path = "crates/turbopack-tests" }
turbopack-wasm = { path = "crates/turbopack-wasm" }
turbopath = { path = "crates/turborepo-paths" }
turborepo = { path = "crates/turborepo" }
turborepo-api-client = { path = "crates/turborepo-api-client" }
Expand Down
10 changes: 9 additions & 1 deletion crates/turbopack-ecmascript-runtime/js/src/build/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// <reference path="../shared/runtime-utils.ts" />
/// <reference path="../shared-node/require.ts" />
/// <reference path="../shared-node/node-utils.ts" />

declare var RUNTIME_PUBLIC_PATH: string;

Expand Down Expand Up @@ -75,6 +75,13 @@ function loadChunkAsync(source: SourceInfo, chunkPath: string): Promise<void> {
});
}

function loadWebAssembly(chunkPath: ChunkPath, imports: WebAssembly.Imports) {
const resolved = require.resolve(path.resolve(RUNTIME_ROOT, chunkPath));
delete require.cache[resolved];

return loadWebAssemblyFromPath(resolved, imports)
}

function instantiateModule(id: ModuleId, source: SourceInfo): Module {
const moduleFactory = moduleFactories[id];
if (typeof moduleFactory !== "function") {
Expand Down Expand Up @@ -135,6 +142,7 @@ function instantiateModule(id: ModuleId, source: SourceInfo): Module {
m: module,
c: moduleCache,
l: loadChunkAsync.bind(null, { type: SourceType.Parent, parentId: id }),
w: loadWebAssembly,
g: globalThis,
__dirname: module.id.replace(/(^|\/)[\/]+$/, ""),
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
// environment
"lib": ["ESNext"],
// environment, we need WebWorker for WebAssembly types (not part of @types/node yet)
"lib": ["ESNext", "WebWorker"],
"types": ["node"]
},
"include": ["*.ts"]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ declare var augmentContext: (
context: TurbopackDevBaseContext
) => TurbopackDevContext;
declare var commonJsRequireContext: CommonJsRequireContext;
declare var loadWebAssembly: (source: SourceInfo, wasmChunkPath: ChunkPath, imports: WebAssembly.Imports) => Exports;
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ function instantiateModule(id: ModuleId, source: SourceInfo): Module {
m: module,
c: moduleCache,
l: loadChunk.bind(null, { type: SourceType.Parent, parentId: id }),
w: loadWebAssembly.bind(null, { type: SourceType.Parent, parentId: id }),
g: globalThis,
k: refresh,
__dirname: module.id.replace(/(^|\/)\/+$/, ""),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
// environment
"lib": ["ESNext"],
"target": "ESNext"
// environment, we need WebWorker for WebAssembly types
"lib": ["ESNext", "WebWorker"]
},
"include": ["runtime-base.ts", "dummy.ts"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ function commonJsRequireContext(
return commonJsRequire(sourceModule, entry.id());
}

async function loadWebAssembly(
_source: SourceInfo,
wasmChunkPath: ChunkPath,
importsObj: WebAssembly.Imports
): Promise<Exports> {
const chunkUrl = `/${getChunkRelativeUrl(wasmChunkPath)}`;

const req = fetch(chunkUrl);
const { instance } = await WebAssembly.instantiateStreaming(req, importsObj);

return instance.exports;
}

(() => {
BACKEND = {
async registerChunk(chunkPath, params) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

/// <reference path="../base/runtime-base.ts" />
/// <reference path="../../../shared-node/require.ts" />
/// <reference path="../../../shared-node/node-utils.ts" />

interface RequireContextEntry {
// Only the Node.js backend has this flag.
Expand All @@ -28,6 +28,38 @@ function augmentContext(context: TurbopackDevBaseContext): TurbopackDevContext {
return nodejsContext;
}

function resolveChunkPath(chunkPath: ChunkPath, source: SourceInfo) {
let fromChunkPath = undefined;
switch (source.type) {
case SourceType.Runtime:
fromChunkPath = source.chunkPath;
break;
case SourceType.Parent:
fromChunkPath = getFirstModuleChunk(source.parentId);
break;
case SourceType.Update:
break;
}

const path = require("path");
const resolved = require.resolve(
"./" + path.relative(path.dirname(fromChunkPath), chunkPath)
);
delete require.cache[resolved];

return resolved;
}

function loadWebAssembly(
source: SourceInfo,
chunkPath: ChunkPath,
imports: WebAssembly.Imports
) {
const resolved = resolveChunkPath(chunkPath, source);

return loadWebAssemblyFromPath(resolved, imports);
}

let BACKEND: RuntimeBackend;

(() => {
Expand Down Expand Up @@ -67,25 +99,10 @@ let BACKEND: RuntimeBackend;
return;
}

let fromChunkPath = undefined;
switch (source.type) {
case SourceType.Runtime:
fromChunkPath = source.chunkPath;
break;
case SourceType.Parent:
fromChunkPath = getFirstModuleChunk(source.parentId);
break;
case SourceType.Update:
break;
}

// We'll only mark the chunk as loaded once the script has been executed,
// which happens in `registerChunk`. Hence the absence of `resolve()`.
const path = require("path");
const resolved = require.resolve(
"./" + path.relative(path.dirname(fromChunkPath), chunkPath)
);
delete require.cache[resolved];
const resolved = resolveChunkPath(chunkPath, source);

require(resolved);
}
})();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
// environment
"lib": ["ESNext"],
// environment, we need WebWorker for WebAssembly types (not part of @types/node yet)
"lib": ["ESNext", "WebWorker"],
"types": ["node"]
},
"include": ["*.ts"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ function commonJsRequireContext(
return commonJsRequire(sourceModule, entry.id());
}

async function loadWebAssembly(
_source: SourceInfo,
_id: ModuleId,
_importsObj: any
): Promise<Exports> {
throw new Error("loading WebAssemly is not supported");
}

(() => {
BACKEND = {
// The "none" runtime expects all chunks within the same chunk group to be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,15 @@ externalRequire.resolve = (
) => {
return require.resolve(id, options);
};

async function loadWebAssemblyFromPath(
path: string,
importsObj: WebAssembly.Imports
): Promise<Exports> {
const { readFile } = require("fs/promises") as typeof import("fs/promises");

const buffer = await readFile(path);
const { instance } = await WebAssembly.instantiate(buffer, importsObj);

return instance.exports;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
// environment
"lib": ["ESNext"]
// environment, we need WebWorker for WebAssembly types (not part of @types/node yet)
"lib": ["ESNext", "WebWorker"],
"types": ["node"]
},
"include": ["*.ts"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type ExportNamespace = (namespace: any) => void;
type DynamicExport = (object: Record<string, any>) => void;

type LoadChunk = (chunkPath: ChunkPath) => Promise<any> | undefined;
type LoadWebAssembly = (wasmChunkPath: ChunkPath, imports: WebAssembly.Imports) => Exports;

type ModuleCache = Record<ModuleId, Module>;
type ModuleFactories = Record<ModuleId, ModuleFactory>;
Expand Down Expand Up @@ -57,6 +58,7 @@ interface TurbopackBaseContext {
m: Module;
c: ModuleCache;
l: LoadChunk;
w: LoadWebAssembly,
g: typeof globalThis;
__dirname: string;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
// environment
"lib": ["ESNext"]
// environment, we need WebWorker for WebAssembly types
"lib": ["ESNext", "WebWorker"]
},
"include": ["*.ts"]
}
2 changes: 1 addition & 1 deletion crates/turbopack-ecmascript-runtime/src/build_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub async fn get_build_runtime_code(environment: Vc<Environment>) -> Result<Vc<C
let shared_runtime_utils_code =
embed_static_code(asset_context, "shared/runtime-utils.ts".to_string());
let shared_node_utils_code =
embed_static_code(asset_context, "shared-node/require.ts".to_string());
embed_static_code(asset_context, "shared-node/node-utils.ts".to_string());
let runtime_code = embed_static_code(asset_context, "build/runtime.ts".to_string());

let mut code = CodeBuilder::default();
Expand Down
2 changes: 1 addition & 1 deletion crates/turbopack-ecmascript-runtime/src/dev_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ pub async fn get_dev_runtime_code(

if matches!(chunk_loading, ChunkLoading::NodeJs) {
code.push_code(
&*embed_static_code(asset_context, "shared-node/require.ts".to_string()).await?,
&*embed_static_code(asset_context, "shared-node/node-utils.ts".to_string()).await?,
);
}

Expand Down
6 changes: 6 additions & 0 deletions crates/turbopack-ecmascript/src/chunk/esm_scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ pub(crate) struct EsmScope {
scc_graph: DiGraphMap<Vc<EsmScopeScc>, ()>,
}

/// Represents a strongly connected component in the EsmScope graph.
///
/// See https://en.wikipedia.org/wiki/Strongly_connected_component
#[turbo_tasks::value(transparent)]
pub(crate) struct EsmScopeScc(Vec<Vc<Box<dyn EcmascriptChunkPlaceable>>>);

Expand All @@ -33,6 +36,7 @@ pub(crate) struct EsmScopeSccs(Vec<Vc<EsmScopeScc>>);

#[turbo_tasks::value_impl]
impl EsmScope {
/// Create a new [EsmScope] from the availability root given.
#[turbo_tasks::function]
pub(crate) async fn new(availability_info: Value<AvailabilityInfo>) -> Result<Vc<Self>> {
let assets = if let Some(root) = availability_info.current_availability_root() {
Expand Down Expand Up @@ -78,6 +82,7 @@ impl EsmScope {
Ok(Self::cell(EsmScope { scc_map, scc_graph }))

Check failure on line 82 in crates/turbopack-ecmascript/src/chunk/esm_scope.rs

View workflow job for this annotation

GitHub Actions / Rust lints

Diff in /home/runner/work/turbo/turbo/crates/turbopack-ecmascript/src/chunk/esm_scope.rs
}

/// Gets the [EsmScopeScc] for a given [EcmascriptChunkPlaceable] if it's part of this graph.
#[turbo_tasks::function]
pub(crate) async fn get_scc(
self: Vc<Self>,
Expand All @@ -88,6 +93,7 @@ impl EsmScope {
Ok(Vc::cell(this.scc_map.get(&placeable).copied()))
}

/// Returns all direct children of an [EsmScopeScc].
#[turbo_tasks::function]
pub(crate) async fn get_scc_children(
self: Vc<Self>,
Expand Down
6 changes: 6 additions & 0 deletions crates/turbopack-ecmascript/src/chunk/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ impl EcmascriptChunkItemContent {
if this.options.exports {
args.push("e: exports");
}
if this.options.wasm {
args.push("w: __turbopack_wasm__");
}
let mut code = CodeBuilder::default();
let args = FormatIter(|| args.iter().copied().intersperse(", "));
if this.options.this {
Expand Down Expand Up @@ -157,6 +160,9 @@ pub struct EcmascriptChunkItemOptions {
/// or is importing async modules).
pub async_module: Option<AsyncModuleOptions>,
pub this: bool,
/// Whether this chunk item's module factory should include
/// `__turbopack_wasm__` to load WebAssembly.
pub wasm: bool,
pub placeholder_for_future_extensions: (),
}

Expand Down
6 changes: 4 additions & 2 deletions crates/turbopack-ecmascript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub mod magic_identifier;
pub(crate) mod manifest;
pub mod parse;
mod path_visitor;
pub(crate) mod references;
pub mod references;
pub mod resolve;
pub(crate) mod special_cases;
pub(crate) mod static_code;
Expand All @@ -36,7 +36,9 @@ use code_gen::CodeGenerateable;
pub use parse::ParseResultSourceMap;

Check failure on line 36 in crates/turbopack-ecmascript/src/lib.rs

View workflow job for this annotation

GitHub Actions / Rust lints

Diff in /home/runner/work/turbo/turbo/crates/turbopack-ecmascript/src/lib.rs
use parse::{parse, ParseResult};
use path_visitor::ApplyVisitors;
pub use references::{AnalyzeEcmascriptModuleResult, TURBOPACK_HELPER};
pub use references::{
AnalyzeEcmascriptModuleResult, TURBOPACK_HELPER,
};
pub use static_code::StaticEcmascriptCode;
use swc_core::{
common::GLOBALS,
Expand Down
Loading

0 comments on commit 282591c

Please sign in to comment.