diff --git a/core/isolate.rs b/core/isolate.rs index 6795f25f06bfbe..2f544a20a6e25f 100644 --- a/core/isolate.rs +++ b/core/isolate.rs @@ -34,9 +34,6 @@ use std::fmt; use std::ptr::null; use std::sync::{Arc, Mutex, Once}; -/// Args: op_id, control_buf, zero_copy_buf -type CoreDispatchFn = dyn Fn(OpId, &[u8], Option) -> CoreOp; - /// Stores a script used to initalize a Isolate pub struct Script<'a> { pub source: &'a str, @@ -147,12 +144,11 @@ type JSErrorCreateFn = dyn Fn(V8Exception) -> ErrBox; /// pending ops have completed. /// /// Ops are created in JavaScript by calling Deno.core.dispatch(), and in Rust -/// by implementing deno::Dispatch::dispatch. An async Op corresponds exactly to -/// a Promise in JavaScript. +/// by implementing dispatcher function that takes control buffer and optional zero copy buffer +/// as arguments. An async Op corresponds exactly to a Promise in JavaScript. pub struct Isolate { libdeno_isolate: *const libdeno::isolate, shared_libdeno_isolate: Arc>>, - dispatch: Option>, dyn_import: Option>, js_error_create: Arc, needs_init: bool, @@ -218,7 +214,6 @@ impl Isolate { Self { libdeno_isolate, shared_libdeno_isolate: Arc::new(Mutex::new(Some(libdeno_isolate))), - dispatch: None, dyn_import: None, js_error_create: Arc::new(CoreJSError::from_v8_exception), shared, @@ -235,29 +230,11 @@ impl Isolate { /// Called whenever Deno.core.dispatch() is called in JavaScript. zero_copy_buf /// corresponds to the second argument of Deno.core.dispatch(). /// - /// If this method is used then ops registered using `op_register` function are - /// ignored and all dispatching must be handled manually in provided callback. - // TODO: we want to deprecate and remove this API and move to `register_op` API - pub fn set_dispatch(&mut self, f: F) - where - F: Fn(OpId, &[u8], Option) -> CoreOp + Send + Sync + 'static, - { - self.dispatch = Some(Arc::new(f)); - } - - /// New dispatch mechanism. Requires runtime to explicitly ask for op ids - /// before using any of the ops. - /// - /// Ops added using this method are only usable if `dispatch` is not set - /// (using `set_dispatch` method). + /// Requires runtime to explicitly ask for op ids before using any of the ops. pub fn register_op(&mut self, name: &str, op: F) -> OpId where F: Fn(&[u8], Option) -> CoreOp + Send + Sync + 'static, { - assert!( - self.dispatch.is_none(), - "set_dispatch should not be used in conjunction with register_op" - ); self.op_registry.register(name, op) } @@ -332,19 +309,11 @@ impl Isolate { ) { let isolate = unsafe { Isolate::from_raw_ptr(user_data) }; - let op = if let Some(ref f) = isolate.dispatch { - assert!( - op_id != 0, - "op_id 0 is a special value that shouldn't be used with dispatch" - ); - f(op_id, control_buf.as_ref(), PinnedBuf::new(zero_copy_buf)) - } else { - isolate.op_registry.call( - op_id, - control_buf.as_ref(), - PinnedBuf::new(zero_copy_buf), - ) - }; + let op = isolate.op_registry.call( + op_id, + control_buf.as_ref(), + PinnedBuf::new(zero_copy_buf), + ); debug_assert_eq!(isolate.shared.size(), 0); match op { @@ -792,46 +761,50 @@ pub mod tests { let dispatch_count_ = dispatch_count.clone(); let mut isolate = Isolate::new(StartupData::None, false); - isolate.set_dispatch(move |op_id, control, _| -> CoreOp { - println!("op_id {}", op_id); - dispatch_count_.fetch_add(1, Ordering::Relaxed); - match mode { - Mode::AsyncImmediate => { - assert_eq!(control.len(), 1); - assert_eq!(control[0], 42); - let buf = vec![43u8, 0, 0, 0].into_boxed_slice(); - Op::Async(Box::new(futures::future::ok(buf))) - } - Mode::OverflowReqSync => { - assert_eq!(control.len(), 100 * 1024 * 1024); - let buf = vec![43u8, 0, 0, 0].into_boxed_slice(); - Op::Sync(buf) - } - Mode::OverflowResSync => { - assert_eq!(control.len(), 1); - assert_eq!(control[0], 42); - let mut vec = Vec::::new(); - vec.resize(100 * 1024 * 1024, 0); - vec[0] = 99; - let buf = vec.into_boxed_slice(); - Op::Sync(buf) - } - Mode::OverflowReqAsync => { - assert_eq!(control.len(), 100 * 1024 * 1024); - let buf = vec![43u8, 0, 0, 0].into_boxed_slice(); - Op::Async(Box::new(futures::future::ok(buf))) - } - Mode::OverflowResAsync => { - assert_eq!(control.len(), 1); - assert_eq!(control[0], 42); - let mut vec = Vec::::new(); - vec.resize(100 * 1024 * 1024, 0); - vec[0] = 4; - let buf = vec.into_boxed_slice(); - Op::Async(Box::new(futures::future::ok(buf))) + + let dispatcher = + move |control: &[u8], _zero_copy: Option| -> CoreOp { + dispatch_count_.fetch_add(1, Ordering::Relaxed); + match mode { + Mode::AsyncImmediate => { + assert_eq!(control.len(), 1); + assert_eq!(control[0], 42); + let buf = vec![43u8, 0, 0, 0].into_boxed_slice(); + Op::Async(Box::new(futures::future::ok(buf))) + } + Mode::OverflowReqSync => { + assert_eq!(control.len(), 100 * 1024 * 1024); + let buf = vec![43u8, 0, 0, 0].into_boxed_slice(); + Op::Sync(buf) + } + Mode::OverflowResSync => { + assert_eq!(control.len(), 1); + assert_eq!(control[0], 42); + let mut vec = Vec::::new(); + vec.resize(100 * 1024 * 1024, 0); + vec[0] = 99; + let buf = vec.into_boxed_slice(); + Op::Sync(buf) + } + Mode::OverflowReqAsync => { + assert_eq!(control.len(), 100 * 1024 * 1024); + let buf = vec![43u8, 0, 0, 0].into_boxed_slice(); + Op::Async(Box::new(futures::future::ok(buf))) + } + Mode::OverflowResAsync => { + assert_eq!(control.len(), 1); + assert_eq!(control[0], 42); + let mut vec = Vec::::new(); + vec.resize(100 * 1024 * 1024, 0); + vec[0] = 4; + let buf = vec.into_boxed_slice(); + Op::Async(Box::new(futures::future::ok(buf))) + } } - } - }); + }; + + isolate.register_op("test", dispatcher); + js_check(isolate.execute( "setup.js", r#" @@ -853,9 +826,9 @@ pub mod tests { "filename.js", r#" let control = new Uint8Array([42]); - Deno.core.send(42, control); + Deno.core.send(1, control); async function main() { - Deno.core.send(42, control); + Deno.core.send(1, control); } main(); "#, @@ -874,7 +847,7 @@ pub mod tests { import { b } from 'b.js' if (b() != 'b') throw Error(); let control = new Uint8Array([42]); - Deno.core.send(42, control); + Deno.core.send(1, control); "#, ) .unwrap(); @@ -931,7 +904,7 @@ pub mod tests { r#" assert(nrecv == 0); let control = new Uint8Array([42]); - Deno.core.send(42, control); + Deno.core.send(1, control); assert(nrecv == 0); "#, )); @@ -942,7 +915,7 @@ pub mod tests { "check2.js", r#" assert(nrecv == 1); - Deno.core.send(42, control); + Deno.core.send(1, control); assert(nrecv == 1); "#, )); @@ -1223,7 +1196,7 @@ pub mod tests { Deno.core.setAsyncHandler((opId, buf) => { asyncRecv++ }); // Large message that will overflow the shared space. let control = new Uint8Array(100 * 1024 * 1024); - let response = Deno.core.dispatch(99, control); + let response = Deno.core.dispatch(1, control); assert(response instanceof Uint8Array); assert(response.length == 4); assert(response[0] == 43); @@ -1245,7 +1218,7 @@ pub mod tests { Deno.core.setAsyncHandler((opId, buf) => { asyncRecv++ }); // Large message that will overflow the shared space. let control = new Uint8Array([42]); - let response = Deno.core.dispatch(99, control); + let response = Deno.core.dispatch(1, control); assert(response instanceof Uint8Array); assert(response.length == 100 * 1024 * 1024); assert(response[0] == 99); @@ -1264,14 +1237,14 @@ pub mod tests { r#" let asyncRecv = 0; Deno.core.setAsyncHandler((opId, buf) => { - assert(opId == 99); + assert(opId == 1); assert(buf.byteLength === 4); assert(buf[0] === 43); asyncRecv++; }); // Large message that will overflow the shared space. let control = new Uint8Array(100 * 1024 * 1024); - let response = Deno.core.dispatch(99, control); + let response = Deno.core.dispatch(1, control); // Async messages always have null response. assert(response == null); assert(asyncRecv == 0); @@ -1294,14 +1267,14 @@ pub mod tests { r#" let asyncRecv = 0; Deno.core.setAsyncHandler((opId, buf) => { - assert(opId == 99); + assert(opId == 1); assert(buf.byteLength === 100 * 1024 * 1024); assert(buf[0] === 4); asyncRecv++; }); // Large message that will overflow the shared space. let control = new Uint8Array([42]); - let response = Deno.core.dispatch(99, control); + let response = Deno.core.dispatch(1, control); assert(response == null); assert(asyncRecv == 0); "#, @@ -1323,19 +1296,19 @@ pub mod tests { r#" let asyncRecv = 0; Deno.core.setAsyncHandler((opId, buf) => { - assert(opId === 99); + assert(opId === 1); assert(buf.byteLength === 100 * 1024 * 1024); assert(buf[0] === 4); asyncRecv++; }); // Large message that will overflow the shared space. let control = new Uint8Array([42]); - let response = Deno.core.dispatch(99, control); + let response = Deno.core.dispatch(1, control); assert(response == null); assert(asyncRecv == 0); // Dispatch another message to verify that pending ops // are done even if shared space overflows - Deno.core.dispatch(99, control); + Deno.core.dispatch(1, control); "#, )); assert_eq!(dispatch_count.load(Ordering::Relaxed), 2); diff --git a/core/shared_queue_test.js b/core/shared_queue_test.js index 42b58052c7e1f7..ff9bb4dd805732 100644 --- a/core/shared_queue_test.js +++ b/core/shared_queue_test.js @@ -11,15 +11,15 @@ function fullRecords(q) { q.reset(); const oneByte = new Uint8Array([42]); for (let i = 0; i < q.MAX_RECORDS; i++) { - assert(q.push(99, oneByte)); + assert(q.push(1, oneByte)); } - assert(!q.push(99, oneByte)); + assert(!q.push(1, oneByte)); const [opId, r] = q.shift(); - assert(opId == 99); + assert(opId == 1); assert(r.byteLength == 1); assert(r[0] == 42); // Even if we shift one off, we still cannot push a new record. - assert(!q.push(99, oneByte)); + assert(!q.push(1, oneByte)); } function main() { @@ -30,14 +30,14 @@ function main() { let r = new Uint8Array([1, 2, 3, 4, 5]); const len = r.byteLength + h; - assert(q.push(99, r)); + assert(q.push(1, r)); assert(q.head() == len); r = new Uint8Array([6, 7]); - assert(q.push(99, r)); + assert(q.push(1, r)); r = new Uint8Array([8, 9, 10, 11]); - assert(q.push(99, r)); + assert(q.push(1, r)); assert(q.numRecords() == 3); assert(q.size() == 3); @@ -60,7 +60,7 @@ function main() { assert(q.size() == 1); [opId, r] = q.shift(); - assert(opId == 99); + assert(opId == 1); assert(r.byteLength == 4); assert(r[0] == 8); assert(r[1] == 9); diff --git a/deno_typescript/compiler_main.js b/deno_typescript/compiler_main.js index c83bbdec3c713c..fa155f12d85f9f 100644 --- a/deno_typescript/compiler_main.js +++ b/deno_typescript/compiler_main.js @@ -13,6 +13,7 @@ const ASSETS = "$asset$"; */ // eslint-disable-next-line @typescript-eslint/no-unused-vars function main(configText, rootNames) { + ops = Deno.core.ops(); println(`>>> ts version ${ts.version}`); println(`>>> rootNames ${rootNames}`); @@ -97,17 +98,11 @@ function encode(str) { } // -/** **Warning!** The op_id values below are shared between this code and the - * Rust side. Update with care! +/** **Warning!** Op ids must be acquired from Rust using `Deno.core.ops()` + * before dispatching any action. * @type {Record} */ -const ops = { - readFile: 49, - exit: 50, - writeFile: 51, - resolveModuleNames: 52, - setEmitResult: 53 -}; +let ops; /** * @type {Map} @@ -315,9 +310,15 @@ function configure(configurationText) { * @param {Record} obj */ function dispatch(opName, obj) { + const opId = ops[opName]; + + if (!opId) { + throw new Error(`Unknown op: ${opName}`); + } + const s = JSON.stringify(obj); const msg = encode(s); - const resUi8 = Deno.core.dispatch(ops[opName], msg); + const resUi8 = Deno.core.dispatch(opId, msg); const resStr = decodeAscii(resUi8); const res = JSON.parse(resStr); if (!res["ok"]) { diff --git a/deno_typescript/lib.rs b/deno_typescript/lib.rs index f6ec3104b15fe7..d130ee4c608c55 100644 --- a/deno_typescript/lib.rs +++ b/deno_typescript/lib.rs @@ -6,9 +6,11 @@ extern crate serde_json; mod ops; use deno::js_check; pub use deno::v8_set_flags; +use deno::CoreOp; use deno::ErrBox; use deno::Isolate; use deno::ModuleSpecifier; +use deno::PinnedBuf; use deno::StartupData; pub use ops::EmitResult; use ops::WrittenFile; @@ -45,6 +47,20 @@ impl TSState { } } +fn compiler_op( + ts_state: Arc>, + dispatcher: D, +) -> impl Fn(&[u8], Option) -> CoreOp +where + D: Fn(&mut TSState, &[u8]) -> CoreOp, +{ + move |control: &[u8], zero_copy_buf: Option| -> CoreOp { + assert!(zero_copy_buf.is_none()); // zero_copy_buf unused in compiler. + let mut s = ts_state.lock().unwrap(); + dispatcher(&mut s, control) + } +} + pub struct TSIsolate { isolate: Isolate, state: Arc>, @@ -62,12 +78,26 @@ impl TSIsolate { emit_result: None, written_files: Vec::new(), })); - let state_ = state.clone(); - isolate.set_dispatch(move |op_id, control_buf, zero_copy_buf| { - assert!(zero_copy_buf.is_none()); // zero_copy_buf unused in compiler. - let mut s = state_.lock().unwrap(); - ops::dispatch_op(&mut s, op_id, control_buf) - }); + + isolate.register_op( + "readFile", + compiler_op(state.clone(), ops::json_op(ops::read_file)), + ); + isolate + .register_op("exit", compiler_op(state.clone(), ops::json_op(ops::exit))); + isolate.register_op( + "writeFile", + compiler_op(state.clone(), ops::json_op(ops::write_file)), + ); + isolate.register_op( + "resolveModuleNames", + compiler_op(state.clone(), ops::json_op(ops::resolve_module_names)), + ); + isolate.register_op( + "setEmitResult", + compiler_op(state.clone(), ops::json_op(ops::set_emit_result)), + ); + TSIsolate { isolate, state } } diff --git a/deno_typescript/ops.rs b/deno_typescript/ops.rs index ca7c085481cd18..7e7d24c7279295 100644 --- a/deno_typescript/ops.rs +++ b/deno_typescript/ops.rs @@ -3,7 +3,6 @@ use deno::CoreOp; use deno::ErrBox; use deno::ModuleSpecifier; use deno::Op; -use deno::OpId; use serde::Deserialize; use serde_json::json; use serde_json::Value; @@ -15,33 +14,23 @@ pub struct WrittenFile { pub source_code: String, } -fn dispatch2( - s: &mut TSState, - op_id: OpId, - control_buf: &[u8], -) -> Result { - let v = serde_json::from_slice(control_buf)?; - // Warning! The op_id values below are shared between this code and - // compiler_main.js. Update with care! - match op_id { - 49 => read_file(s, v), - 50 => exit(s, v), - 51 => write_file(s, v), - 52 => resolve_module_names(s, v), - 53 => set_emit_result(s, v), - _ => unreachable!(), - } -} +type Dispatcher = fn(state: &mut TSState, args: Value) -> Result; -pub fn dispatch_op(s: &mut TSState, op_id: OpId, control_buf: &[u8]) -> CoreOp { - let result = dispatch2(s, op_id, control_buf); - let response = match result { - Ok(v) => json!({ "ok": v }), - Err(err) => json!({ "err": err.to_string() }), - }; - let x = serde_json::to_string(&response).unwrap(); - let vec = x.into_bytes(); - Op::Sync(vec.into_boxed_slice()) +pub fn json_op(d: Dispatcher) -> impl Fn(&mut TSState, &[u8]) -> CoreOp { + move |state: &mut TSState, control: &[u8]| { + let result = serde_json::from_slice(control) + .map_err(ErrBox::from) + .and_then(move |args| d(state, args)); + + let response = match result { + Ok(v) => json!({ "ok": v }), + Err(err) => json!({ "err": err.to_string() }), + }; + + let x = serde_json::to_string(&response).unwrap(); + let vec = x.into_bytes(); + Op::Sync(vec.into_boxed_slice()) + } } #[derive(Debug, Deserialize)] @@ -52,7 +41,7 @@ struct ReadFile { should_create_new_source_file: bool, } -fn read_file(_s: &mut TSState, v: Value) -> Result { +pub fn read_file(_s: &mut TSState, v: Value) -> Result { let v: ReadFile = serde_json::from_value(v)?; let (module_name, source_code) = if v.file_name.starts_with("$asset$/") { let asset = v.file_name.replace("$asset$/", ""); @@ -82,7 +71,7 @@ struct WriteFile { module_name: String, } -fn write_file(s: &mut TSState, v: Value) -> Result { +pub fn write_file(s: &mut TSState, v: Value) -> Result { let v: WriteFile = serde_json::from_value(v)?; let module_specifier = ModuleSpecifier::resolve_url_or_path(&v.file_name)?; if s.bundle { @@ -103,7 +92,10 @@ struct ResolveModuleNames { containing_file: String, } -fn resolve_module_names(_s: &mut TSState, v: Value) -> Result { +pub fn resolve_module_names( + _s: &mut TSState, + v: Value, +) -> Result { let v: ResolveModuleNames = serde_json::from_value(v).unwrap(); let mut resolved = Vec::::new(); let referrer = ModuleSpecifier::resolve_url_or_path(&v.containing_file)?; @@ -124,7 +116,7 @@ struct Exit { code: i32, } -fn exit(s: &mut TSState, v: Value) -> Result { +pub fn exit(s: &mut TSState, v: Value) -> Result { let v: Exit = serde_json::from_value(v)?; s.exit_code = v.code; std::process::exit(v.code) @@ -138,7 +130,7 @@ pub struct EmitResult { pub emitted_files: Vec, } -fn set_emit_result(s: &mut TSState, v: Value) -> Result { +pub fn set_emit_result(s: &mut TSState, v: Value) -> Result { let v: EmitResult = serde_json::from_value(v)?; s.emit_result = Some(v); Ok(json!(true))