|
2 | 2 | use crate::compilers::CompiledModule;
|
3 | 3 | use crate::compilers::CompiledModuleFuture;
|
4 | 4 | use crate::file_fetcher::SourceFile;
|
| 5 | +use crate::global_state::ThreadSafeGlobalState; |
| 6 | +use crate::startup_data; |
| 7 | +use crate::state::*; |
| 8 | +use crate::worker::Worker; |
| 9 | +use deno::Buf; |
| 10 | +use futures::Future; |
| 11 | +use futures::IntoFuture; |
| 12 | +use serde_derive::Deserialize; |
| 13 | +use serde_json; |
| 14 | +use std::collections::HashMap; |
| 15 | +use std::sync::atomic::Ordering; |
| 16 | +use std::sync::{Arc, Mutex}; |
| 17 | +use url::Url; |
5 | 18 |
|
6 | 19 | // TODO(kevinkassimo): This is a hack to encode/decode data as base64 string.
|
7 | 20 | // (Since Deno namespace might not be available, Deno.read can fail).
|
8 | 21 | // Binary data is already available through source_file.source_code.
|
9 | 22 | // If this is proven too wasteful in practice, refactor this.
|
10 | 23 |
|
11 | 24 | // Ref: https://webassembly.github.io/esm-integration/js-api/index.html#esm-integration
|
12 |
| - |
13 |
| -// Only default exports is support ATM. |
14 |
| -// Node.js supports named import since its dynamic module creation allows |
15 |
| -// running some code before transformation: |
16 | 25 | // https://github.com/nodejs/node/blob/35ec01097b2a397ad0a22aac536fe07514876e21/lib/internal/modules/esm/translators.js#L190-L210
|
17 |
| -// We need to expose worker to compilers to achieve that. |
18 | 26 |
|
19 |
| -pub struct WasmCompiler {} |
| 27 | +// Dynamically construct JS wrapper with custom static imports and named exports. |
| 28 | +// Boots up an internal worker to resolve imports/exports through query from V8. |
| 29 | + |
| 30 | +static WASM_WRAP: &str = include_str!("./wasm_wrap.js"); |
| 31 | + |
| 32 | +#[derive(Deserialize, Debug)] |
| 33 | +#[serde(rename_all = "camelCase")] |
| 34 | +struct WasmModuleInfo { |
| 35 | + import_list: Vec<String>, |
| 36 | + export_list: Vec<String>, |
| 37 | +} |
| 38 | + |
| 39 | +pub struct WasmCompiler { |
| 40 | + cache: Arc<Mutex<HashMap<Url, CompiledModule>>>, |
| 41 | +} |
20 | 42 |
|
21 | 43 | impl WasmCompiler {
|
| 44 | + pub fn new() -> WasmCompiler { |
| 45 | + Self { |
| 46 | + cache: Arc::new(Mutex::new(HashMap::new())), |
| 47 | + } |
| 48 | + } |
| 49 | + |
| 50 | + /// Create a new V8 worker with snapshot of WASM compiler and setup compiler's runtime. |
| 51 | + fn setup_worker(global_state: ThreadSafeGlobalState) -> Worker { |
| 52 | + let (int, ext) = ThreadSafeState::create_channels(); |
| 53 | + let worker_state = |
| 54 | + ThreadSafeState::new(global_state.clone(), None, true, int) |
| 55 | + .expect("Unable to create worker state"); |
| 56 | + |
| 57 | + // Count how many times we start the compiler worker. |
| 58 | + global_state |
| 59 | + .metrics |
| 60 | + .compiler_starts |
| 61 | + .fetch_add(1, Ordering::SeqCst); |
| 62 | + |
| 63 | + let mut worker = Worker::new( |
| 64 | + "WASM".to_string(), |
| 65 | + startup_data::compiler_isolate_init(), |
| 66 | + worker_state, |
| 67 | + ext, |
| 68 | + ); |
| 69 | + worker.execute("denoMain('WASM')").unwrap(); |
| 70 | + worker.execute("workerMain()").unwrap(); |
| 71 | + worker.execute("wasmCompilerMain()").unwrap(); |
| 72 | + worker |
| 73 | + } |
| 74 | + |
22 | 75 | pub fn compile_async(
|
23 | 76 | self: &Self,
|
| 77 | + global_state: ThreadSafeGlobalState, |
24 | 78 | source_file: &SourceFile,
|
25 | 79 | ) -> Box<CompiledModuleFuture> {
|
26 |
| - let code = wrap_wasm_code(&source_file.source_code); |
27 |
| - let module = CompiledModule { |
28 |
| - code, |
29 |
| - name: source_file.url.to_string(), |
| 80 | + let cache = self.cache.clone(); |
| 81 | + let maybe_cached = { |
| 82 | + cache |
| 83 | + .lock() |
| 84 | + .unwrap() |
| 85 | + .get(&source_file.url) |
| 86 | + .map(|c| c.clone()) |
30 | 87 | };
|
31 |
| - Box::new(futures::future::ok(module)) |
| 88 | + if let Some(m) = maybe_cached { |
| 89 | + return Box::new(futures::future::ok(m.clone())); |
| 90 | + } |
| 91 | + let cache_ = self.cache.clone(); |
| 92 | + |
| 93 | + debug!(">>>>> wasm_compile_async START"); |
| 94 | + let base64_data = base64::encode(&source_file.source_code); |
| 95 | + let worker = WasmCompiler::setup_worker(global_state.clone()); |
| 96 | + let worker_ = worker.clone(); |
| 97 | + let url = source_file.url.clone(); |
| 98 | + |
| 99 | + let fut = worker |
| 100 | + .post_message( |
| 101 | + serde_json::to_string(&base64_data) |
| 102 | + .unwrap() |
| 103 | + .into_boxed_str() |
| 104 | + .into_boxed_bytes(), |
| 105 | + ) |
| 106 | + .into_future() |
| 107 | + .then(move |_| worker) |
| 108 | + .then(move |result| { |
| 109 | + if let Err(err) = result { |
| 110 | + // TODO(ry) Need to forward the error instead of exiting. |
| 111 | + eprintln!("{}", err.to_string()); |
| 112 | + std::process::exit(1); |
| 113 | + } |
| 114 | + debug!("Sent message to worker"); |
| 115 | + worker_.get_message() |
| 116 | + }) |
| 117 | + .map_err(|_| panic!("not handled")) |
| 118 | + .and_then(move |maybe_msg: Option<Buf>| { |
| 119 | + debug!("Received message from worker"); |
| 120 | + let json_msg = maybe_msg.unwrap(); |
| 121 | + let module_info: WasmModuleInfo = |
| 122 | + serde_json::from_slice(&json_msg).unwrap(); |
| 123 | + debug!("WASM module info: {:#?}", &module_info); |
| 124 | + let code = wrap_wasm_code( |
| 125 | + &base64_data, |
| 126 | + &module_info.import_list, |
| 127 | + &module_info.export_list, |
| 128 | + ); |
| 129 | + debug!("Generated code: {}", &code); |
| 130 | + let module = CompiledModule { |
| 131 | + code, |
| 132 | + name: url.to_string(), |
| 133 | + }; |
| 134 | + { |
| 135 | + cache_.lock().unwrap().insert(url.clone(), module.clone()); |
| 136 | + } |
| 137 | + debug!("<<<<< wasm_compile_async END"); |
| 138 | + Ok(module) |
| 139 | + }); |
| 140 | + Box::new(fut) |
| 141 | + } |
| 142 | +} |
| 143 | + |
| 144 | +fn build_single_import(index: usize, origin: &str) -> String { |
| 145 | + let origin_json = serde_json::to_string(origin).unwrap(); |
| 146 | + format!( |
| 147 | + r#"import * as m{} from {}; |
| 148 | +importObject[{}] = m{}; |
| 149 | +"#, |
| 150 | + index, &origin_json, &origin_json, index |
| 151 | + ) |
| 152 | +} |
| 153 | + |
| 154 | +fn build_imports(imports: &Vec<String>) -> String { |
| 155 | + let mut code = String::from(""); |
| 156 | + for (index, origin) in imports.iter().enumerate() { |
| 157 | + code.push_str(&build_single_import(index, origin)); |
| 158 | + } |
| 159 | + code |
| 160 | +} |
| 161 | + |
| 162 | +fn build_single_export(name: &str) -> String { |
| 163 | + format!("export const {} = instance.exports.{};\n", name, name) |
| 164 | +} |
| 165 | + |
| 166 | +fn build_exports(exports: &Vec<String>) -> String { |
| 167 | + let mut code = String::from(""); |
| 168 | + for e in exports { |
| 169 | + code.push_str(&build_single_export(e)); |
32 | 170 | }
|
| 171 | + code |
33 | 172 | }
|
34 | 173 |
|
35 |
| -pub fn wrap_wasm_code<T: ?Sized + AsRef<[u8]>>(input: &T) -> String { |
36 |
| - format!(include_str!("./wasm_wrap.js"), base64::encode(input)) |
| 174 | +fn wrap_wasm_code( |
| 175 | + base64_data: &str, |
| 176 | + imports: &Vec<String>, |
| 177 | + exports: &Vec<String>, |
| 178 | +) -> String { |
| 179 | + let imports_code = build_imports(imports); |
| 180 | + let exports_code = build_exports(exports); |
| 181 | + String::from(WASM_WRAP) |
| 182 | + .replace("//IMPORTS\n", &imports_code) |
| 183 | + .replace("//EXPORTS\n", &exports_code) |
| 184 | + .replace("BASE64_DATA", base64_data) |
37 | 185 | }
|
0 commit comments