2020-01-02 15:13:47 -05:00
|
|
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
2020-01-21 11:50:06 -05:00
|
|
|
use super::compiler_worker::CompilerWorker;
|
2019-11-14 08:31:39 -05:00
|
|
|
use crate::compilers::CompiledModule;
|
|
|
|
use crate::file_fetcher::SourceFile;
|
2020-02-06 23:05:02 -05:00
|
|
|
use crate::global_state::GlobalState;
|
2019-11-14 08:31:39 -05:00
|
|
|
use crate::startup_data;
|
|
|
|
use crate::state::*;
|
2020-02-18 14:47:11 -05:00
|
|
|
use crate::tokio_util;
|
2020-02-11 04:04:59 -05:00
|
|
|
use crate::worker::WorkerEvent;
|
|
|
|
use crate::worker::WorkerHandle;
|
|
|
|
use deno_core::Buf;
|
2020-02-03 18:08:44 -05:00
|
|
|
use deno_core::ErrBox;
|
2020-02-04 14:24:33 -05:00
|
|
|
use deno_core::ModuleSpecifier;
|
2019-11-14 08:31:39 -05:00
|
|
|
use serde_derive::Deserialize;
|
|
|
|
use serde_json;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::sync::atomic::Ordering;
|
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
use url::Url;
|
|
|
|
|
2020-02-06 21:24:51 -05:00
|
|
|
// TODO(ry) The entire concept of spawning a thread, sending data to JS,
|
|
|
|
// compiling WASM there, and moving the data back into the calling thread is
|
|
|
|
// completelly wrong. V8 has native facilities for getting this information.
|
|
|
|
// We might be lacking bindings for this currently in rusty_v8 but ultimately
|
|
|
|
// this "compiler" should be calling into rusty_v8 directly, not spawning
|
|
|
|
// threads.
|
|
|
|
|
2019-11-14 08:31:39 -05:00
|
|
|
// TODO(kevinkassimo): This is a hack to encode/decode data as base64 string.
|
|
|
|
// (Since Deno namespace might not be available, Deno.read can fail).
|
|
|
|
// Binary data is already available through source_file.source_code.
|
|
|
|
// If this is proven too wasteful in practice, refactor this.
|
|
|
|
|
|
|
|
// Ref: https://webassembly.github.io/esm-integration/js-api/index.html#esm-integration
|
|
|
|
// https://github.com/nodejs/node/blob/35ec01097b2a397ad0a22aac536fe07514876e21/lib/internal/modules/esm/translators.js#L190-L210
|
|
|
|
|
|
|
|
// Dynamically construct JS wrapper with custom static imports and named exports.
|
|
|
|
// Boots up an internal worker to resolve imports/exports through query from V8.
|
|
|
|
|
|
|
|
static WASM_WRAP: &str = include_str!("./wasm_wrap.js");
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
struct WasmModuleInfo {
|
|
|
|
import_list: Vec<String>,
|
|
|
|
export_list: Vec<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct WasmCompiler {
|
|
|
|
cache: Arc<Mutex<HashMap<Url, CompiledModule>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl WasmCompiler {
|
|
|
|
/// Create a new V8 worker with snapshot of WASM compiler and setup compiler's runtime.
|
2020-02-06 23:05:02 -05:00
|
|
|
fn setup_worker(global_state: GlobalState) -> CompilerWorker {
|
2020-02-04 14:24:33 -05:00
|
|
|
let entry_point =
|
|
|
|
ModuleSpecifier::resolve_url_or_path("./__$deno$wasm_compiler.ts")
|
|
|
|
.unwrap();
|
2020-04-03 13:40:11 -04:00
|
|
|
let worker_state =
|
|
|
|
State::new(global_state.clone(), None, entry_point, DebugType::Internal)
|
|
|
|
.expect("Unable to create worker state");
|
2019-11-14 08:31:39 -05:00
|
|
|
|
|
|
|
// Count how many times we start the compiler worker.
|
2020-02-11 11:23:40 -05:00
|
|
|
global_state.compiler_starts.fetch_add(1, Ordering::SeqCst);
|
2019-11-14 08:31:39 -05:00
|
|
|
|
2020-01-21 11:50:06 -05:00
|
|
|
let mut worker = CompilerWorker::new(
|
2019-11-14 08:31:39 -05:00
|
|
|
"WASM".to_string(),
|
|
|
|
startup_data::compiler_isolate_init(),
|
|
|
|
worker_state,
|
|
|
|
);
|
2020-01-27 21:12:25 -05:00
|
|
|
worker.execute("bootstrapWasmCompilerRuntime()").unwrap();
|
2019-11-14 08:31:39 -05:00
|
|
|
worker
|
|
|
|
}
|
|
|
|
|
2020-02-25 14:42:00 -05:00
|
|
|
pub async fn compile(
|
2020-01-04 05:20:52 -05:00
|
|
|
&self,
|
2020-02-06 23:05:02 -05:00
|
|
|
global_state: GlobalState,
|
2019-11-14 08:31:39 -05:00
|
|
|
source_file: &SourceFile,
|
2020-02-06 21:24:51 -05:00
|
|
|
) -> Result<CompiledModule, ErrBox> {
|
2019-11-14 08:31:39 -05:00
|
|
|
let cache = self.cache.clone();
|
2020-02-06 21:24:51 -05:00
|
|
|
let cache_ = self.cache.clone();
|
2020-02-03 18:08:44 -05:00
|
|
|
let source_file = source_file.clone();
|
2020-02-06 21:24:51 -05:00
|
|
|
|
2019-11-14 08:31:39 -05:00
|
|
|
let maybe_cached = { cache.lock().unwrap().get(&source_file.url).cloned() };
|
|
|
|
if let Some(m) = maybe_cached {
|
2020-02-06 21:24:51 -05:00
|
|
|
return Ok(m);
|
2019-11-14 08:31:39 -05:00
|
|
|
}
|
2020-02-25 14:42:00 -05:00
|
|
|
debug!(">>>>> wasm_compile START");
|
2020-02-11 04:04:59 -05:00
|
|
|
let base64_data = base64::encode(&source_file.source_code);
|
|
|
|
let url = source_file.url.clone();
|
|
|
|
let req_msg = serde_json::to_string(&base64_data)
|
|
|
|
.unwrap()
|
|
|
|
.into_boxed_str()
|
|
|
|
.into_boxed_bytes();
|
|
|
|
let msg = execute_in_thread(global_state.clone(), req_msg).await?;
|
|
|
|
debug!("Received message from worker");
|
|
|
|
let module_info: WasmModuleInfo = serde_json::from_slice(&msg).unwrap();
|
|
|
|
debug!("WASM module info: {:#?}", &module_info);
|
|
|
|
let code = wrap_wasm_code(
|
|
|
|
&base64_data,
|
|
|
|
&module_info.import_list,
|
|
|
|
&module_info.export_list,
|
|
|
|
);
|
|
|
|
debug!("Generated code: {}", &code);
|
|
|
|
let module = CompiledModule {
|
|
|
|
code,
|
|
|
|
name: url.to_string(),
|
|
|
|
};
|
|
|
|
{
|
|
|
|
cache_.lock().unwrap().insert(url.clone(), module.clone());
|
|
|
|
}
|
2020-02-25 14:42:00 -05:00
|
|
|
debug!("<<<<< wasm_compile END");
|
2020-02-11 04:04:59 -05:00
|
|
|
Ok(module)
|
2019-11-14 08:31:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-11 04:04:59 -05:00
|
|
|
async fn execute_in_thread(
|
|
|
|
global_state: GlobalState,
|
|
|
|
req: Buf,
|
|
|
|
) -> Result<Buf, ErrBox> {
|
|
|
|
let (handle_sender, handle_receiver) =
|
|
|
|
std::sync::mpsc::sync_channel::<Result<WorkerHandle, ErrBox>>(1);
|
|
|
|
let builder =
|
|
|
|
std::thread::Builder::new().name("deno-wasm-compiler".to_string());
|
|
|
|
let join_handle = builder.spawn(move || {
|
2020-02-18 14:47:11 -05:00
|
|
|
let worker = WasmCompiler::setup_worker(global_state);
|
2020-02-11 04:04:59 -05:00
|
|
|
handle_sender.send(Ok(worker.thread_safe_handle())).unwrap();
|
|
|
|
drop(handle_sender);
|
2020-02-18 14:47:11 -05:00
|
|
|
tokio_util::run_basic(worker).expect("Panic in event loop");
|
2020-02-11 04:04:59 -05:00
|
|
|
})?;
|
|
|
|
let mut handle = handle_receiver.recv().unwrap()?;
|
|
|
|
handle.post_message(req).await?;
|
|
|
|
let event = handle.get_event().await.expect("Compiler didn't respond");
|
|
|
|
let buf = match event {
|
|
|
|
WorkerEvent::Message(buf) => Ok(buf),
|
|
|
|
WorkerEvent::Error(error) => Err(error),
|
|
|
|
}?;
|
2020-03-05 05:13:10 -05:00
|
|
|
// Shutdown worker and wait for thread to finish
|
2020-02-11 04:04:59 -05:00
|
|
|
handle.sender.close_channel();
|
|
|
|
join_handle.join().unwrap();
|
|
|
|
Ok(buf)
|
|
|
|
}
|
|
|
|
|
2019-11-14 08:31:39 -05:00
|
|
|
fn build_single_import(index: usize, origin: &str) -> String {
|
|
|
|
let origin_json = serde_json::to_string(origin).unwrap();
|
|
|
|
format!(
|
|
|
|
r#"import * as m{} from {};
|
|
|
|
importObject[{}] = m{};
|
|
|
|
"#,
|
|
|
|
index, &origin_json, &origin_json, index
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build_imports(imports: &[String]) -> String {
|
|
|
|
let mut code = String::from("");
|
|
|
|
for (index, origin) in imports.iter().enumerate() {
|
|
|
|
code.push_str(&build_single_import(index, origin));
|
|
|
|
}
|
|
|
|
code
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build_single_export(name: &str) -> String {
|
|
|
|
format!("export const {} = instance.exports.{};\n", name, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build_exports(exports: &[String]) -> String {
|
|
|
|
let mut code = String::from("");
|
|
|
|
for e in exports {
|
|
|
|
code.push_str(&build_single_export(e));
|
|
|
|
}
|
|
|
|
code
|
|
|
|
}
|
|
|
|
|
|
|
|
fn wrap_wasm_code(
|
|
|
|
base64_data: &str,
|
|
|
|
imports: &[String],
|
|
|
|
exports: &[String],
|
|
|
|
) -> String {
|
|
|
|
let imports_code = build_imports(imports);
|
|
|
|
let exports_code = build_exports(exports);
|
|
|
|
String::from(WASM_WRAP)
|
|
|
|
.replace("//IMPORTS\n", &imports_code)
|
|
|
|
.replace("//EXPORTS\n", &exports_code)
|
|
|
|
.replace("BASE64_DATA", base64_data)
|
|
|
|
}
|