diff --git a/core/flags.rs b/core/flags.rs new file mode 100644 index 0000000000..2da35734d4 --- /dev/null +++ b/core/flags.rs @@ -0,0 +1,84 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +//! This module wraps libdeno::deno_set_v8_flags +use crate::libdeno::deno_set_v8_flags; +use libc::c_char; +use libc::c_int; +use std::ffi::CStr; +use std::ffi::CString; +use std::mem; +use std::vec::Vec; + +/// Pass the command line arguments to v8. +/// Returns a vector of command line arguments that V8 did not understand. +/// Translates --v8-options into a --help flag for V8. +pub fn v8_set_flags(args: Vec) -> Vec { + // deno_set_v8_flags(int* argc, char** argv) mutates argc and argv to remove + // flags that v8 understands. + // First parse core args, then convert to a vector of C strings. + let (args, rest) = v8_set_flags_preprocess(args); + + // Make a new array, that can be modified by V8::SetFlagsFromCommandLine(), + // containing mutable raw pointers to the individual command line args. + let mut raw_argv = args + .iter() + .map(|arg| CString::new(arg.as_str()).unwrap().into_bytes_with_nul()) + .collect::>(); + let mut c_argv = raw_argv + .iter_mut() + .map(|arg| arg.as_mut_ptr() as *mut c_char) + .collect::>(); + + // Store the length of the c_argv array in a local variable. We'll pass + // a pointer to this local variable to deno_set_v8_flags(), which then + // updates its value. + let mut c_argv_len = c_argv.len() as c_int; + // Let v8 parse the arguments it recognizes and remove them from c_argv. + unsafe { deno_set_v8_flags(&mut c_argv_len, c_argv.as_mut_ptr()) }; + // If c_argv_len was updated we have to change the length of c_argv to match. + c_argv.truncate(c_argv_len as usize); + // Copy the modified arguments list into a proper rust vec and return it. + c_argv + .iter() + .map(|ptr| unsafe { + let cstr = CStr::from_ptr(*ptr as *const c_char); + let slice = cstr.to_str().unwrap(); + slice.to_string() + }).chain(rest.into_iter()) + .collect() +} + +// Returns args passed to V8, followed by args passed to JS +fn v8_set_flags_preprocess(args: Vec) -> (Vec, Vec) { + let (rest, mut v8_args) = + args.into_iter().partition(|ref a| a.as_str() == "--help"); + + // Replace args being sent to V8 + for a in &mut v8_args { + if a == "--v8-options" { + mem::swap(a, &mut String::from("--help")); + } + } + (v8_args, rest) +} + +#[test] +fn test_v8_set_flags_preprocess_1() { + let js_args = v8_set_flags_preprocess(vec![ + "deno".to_string(), + "--v8-options".to_string(), + ]); + assert_eq!( + js_args, + (vec!["deno".to_string(), "--help".to_string()], vec![]) + ); +} + +#[test] +fn test_v8_set_flags_preprocess_2() { + let js_args = + v8_set_flags_preprocess(vec!["deno".to_string(), "--help".to_string()]); + assert_eq!( + js_args, + (vec!["deno".to_string()], vec!["--help".to_string()]) + ); +} diff --git a/core/http_bench.js b/core/http_bench.js index 29a79c4362..aae15a72c6 100644 --- a/core/http_bench.js +++ b/core/http_bench.js @@ -1,24 +1,57 @@ // This is not a real HTTP server. We read blindly one time into 'requestBuf', // then write this fixed 'responseBuf'. The point of this benchmark is to // exercise the event loop in a simple yet semi-realistic way. -const shared32 = new Int32Array(libdeno.shared); - -const INDEX_NUM_RECORDS = 0; -const INDEX_RECORDS = 1; -const RECORD_OFFSET_PROMISE_ID = 0; -const RECORD_OFFSET_OP = 1; -const RECORD_OFFSET_ARG = 2; -const RECORD_OFFSET_RESULT = 3; -const RECORD_SIZE = 4; const OP_LISTEN = 1; const OP_ACCEPT = 2; const OP_READ = 3; const OP_WRITE = 4; const OP_CLOSE = 5; +const INDEX_START = 0; +const INDEX_END = 1; +const NUM_RECORDS = 128; +const RECORD_SIZE = 4; -const NUM_RECORDS = (shared32.length - INDEX_RECORDS) / RECORD_SIZE; -if (NUM_RECORDS != 100) { - throw Error("expected 100 entries"); +const shared32 = new Int32Array(libdeno.shared); + +function idx(i, off) { + return 2 + i * RECORD_SIZE + off; +} + +function recordsPush(promiseId, opId, arg, result) { + let i = shared32[INDEX_END]; + if (i >= NUM_RECORDS) { + return false; + } + shared32[idx(i, 0)] = promiseId; + shared32[idx(i, 1)] = opId; + shared32[idx(i, 2)] = arg; + shared32[idx(i, 3)] = result; + shared32[INDEX_END]++; + return true; +} + +function recordsShift() { + if (shared32[INDEX_START] == shared32[INDEX_END]) { + return null; + } + const i = shared32[INDEX_START]; + const record = { + promiseId: shared32[idx(i, 0)], + opId: shared32[idx(i, 1)], + arg: shared32[idx(i, 2)], + result: shared32[idx(i, 3)] + }; + shared32[INDEX_START]++; + return record; +} + +function recordsReset() { + shared32[INDEX_START] = 0; + shared32[INDEX_END] = 0; +} + +function recordsSize() { + return shared32[INDEX_END] - shared32[INDEX_START]; } const requestBuf = new Uint8Array(64 * 1024); @@ -39,51 +72,35 @@ function createResolvable() { return Object.assign(promise, methods); } -function setRecord(i, off, value) { - if (i >= NUM_RECORDS) { - throw Error("out of range"); - } - shared32[INDEX_RECORDS + RECORD_SIZE * i + off] = value; -} - -function getRecord(i, off) { - if (i >= NUM_RECORDS) { - throw Error("out of range"); - } - return shared32[INDEX_RECORDS + RECORD_SIZE * i + off]; -} - /** Returns Promise */ -function sendAsync(op, arg, zeroCopyData) { - const id = nextPromiseId++; +function sendAsync(opId, arg, zeroCopyData) { + const promiseId = nextPromiseId++; const p = createResolvable(); - shared32[INDEX_NUM_RECORDS] = 1; - setRecord(0, RECORD_OFFSET_PROMISE_ID, id); - setRecord(0, RECORD_OFFSET_OP, op); - setRecord(0, RECORD_OFFSET_ARG, arg); - setRecord(0, RECORD_OFFSET_RESULT, -1); - promiseMap.set(id, p); + recordsReset(); + recordsPush(promiseId, opId, arg, -1); + promiseMap.set(promiseId, p); libdeno.send(null, zeroCopyData); return p; } /** Returns u32 number */ -function sendSync(op, arg) { - shared32[INDEX_NUM_RECORDS] = 1; - setRecord(0, RECORD_OFFSET_PROMISE_ID, 0); - setRecord(0, RECORD_OFFSET_OP, op); - setRecord(0, RECORD_OFFSET_ARG, arg); - setRecord(0, RECORD_OFFSET_RESULT, -1); +function sendSync(opId, arg) { + recordsReset(); + recordsPush(0, opId, arg, -1); libdeno.send(); - return getRecord(0, RECORD_OFFSET_RESULT); + if (recordsSize() != 1) { + throw Error("Expected sharedSimple to have size 1"); + } + let { result } = recordsShift(); + return result; } function handleAsyncMsgFromRust() { - for (let i = 0; i < shared32[INDEX_NUM_RECORDS]; i++) { - let id = getRecord(i, RECORD_OFFSET_PROMISE_ID); - const p = promiseMap.get(id); - promiseMap.delete(id); - p.resolve(getRecord(i, RECORD_OFFSET_RESULT)); + while (recordsSize() > 0) { + const { promiseId, result } = recordsShift(); + const p = promiseMap.get(promiseId); + promiseMap.delete(promiseId); + p.resolve(result); } } @@ -132,7 +149,7 @@ async function serve(rid) { async function main() { libdeno.recv(handleAsyncMsgFromRust); - libdeno.print("http_bench.js start"); + libdeno.print("http_bench.js start\n"); const listenerRid = listen(); libdeno.print(`listening http://127.0.0.1:4544/ rid = ${listenerRid}`); diff --git a/core/http_bench.rs b/core/http_bench.rs index 3da30433a0..8e5a0e10cf 100644 --- a/core/http_bench.rs +++ b/core/http_bench.rs @@ -12,18 +12,11 @@ extern crate log; #[macro_use] extern crate lazy_static; -use deno_core::deno_buf; -use deno_core::AsyncResult; -use deno_core::Isolate; -use deno_core::JSError; -use deno_core::Op; -use deno_core::RECORD_OFFSET_ARG; -use deno_core::RECORD_OFFSET_OP; -use deno_core::RECORD_OFFSET_PROMISE_ID; -use deno_core::RECORD_OFFSET_RESULT; +use deno_core::*; use futures::future::lazy; use std::collections::HashMap; use std::env; +use std::mem; use std::net::SocketAddr; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; @@ -36,11 +29,148 @@ const OP_READ: i32 = 3; const OP_WRITE: i32 = 4; const OP_CLOSE: i32 = 5; +const INDEX_START: usize = 0; +const INDEX_END: usize = 1; + +const NUM_RECORDS: usize = 128; +const RECORD_SIZE: usize = 4; + +#[derive(Clone, Debug)] +pub struct Record { + pub promise_id: i32, + pub op_id: i32, + pub arg: i32, + pub result: i32, +} + +pub type HttpBenchOp = dyn Future + Send; + +struct HttpBench { + shared32: Vec, +} + +impl HttpBench { + fn new() -> Self { + let mut shared32 = Vec::::new(); + let n = 2 + 4 * NUM_RECORDS; + shared32.resize(n, 0); + shared32[INDEX_START] = 0; + shared32[INDEX_END] = 0; + Self { shared32 } + } +} + +fn idx(i: usize, off: usize) -> usize { + 2 + i * RECORD_SIZE + off +} + +impl Behavior for HttpBench { + fn startup_snapshot(&mut self) -> Option { + None + } + + fn startup_shared(&mut self) -> Option { + let ptr = self.shared32.as_ptr() as *const u8; + let len = mem::size_of::() * self.shared32.len(); + Some(unsafe { deno_buf::from_raw_parts(ptr, len) }) + } + + fn resolve(&mut self, _specifier: &str, _referrer: deno_mod) -> deno_mod { + // HttpBench doesn't do ES modules. + unimplemented!() + } + + fn recv( + &mut self, + record: Record, + zero_copy_buf: deno_buf, + ) -> (bool, Box>) { + let is_sync = record.promise_id == 0; + let http_bench_op = match record.op_id { + OP_LISTEN => { + assert!(is_sync); + op_listen() + } + OP_CLOSE => { + assert!(is_sync); + let rid = record.arg; + op_close(rid) + } + OP_ACCEPT => { + assert!(!is_sync); + let listener_rid = record.arg; + op_accept(listener_rid) + } + OP_READ => { + assert!(!is_sync); + let rid = record.arg; + op_read(rid, zero_copy_buf) + } + OP_WRITE => { + assert!(!is_sync); + let rid = record.arg; + op_write(rid, zero_copy_buf) + } + _ => panic!("bad op {}", record.op_id), + }; + let mut record_a = record.clone(); + let mut record_b = record.clone(); + + let op = Box::new( + http_bench_op + .and_then(move |result| { + record_a.result = result; + Ok(record_a) + }).or_else(|err| -> Result { + eprintln!("unexpected err {}", err); + record_b.result = -1; + Ok(record_b) + }), + ); + (is_sync, op) + } + + fn records_reset(&mut self) { + self.shared32[INDEX_START] = 0; + self.shared32[INDEX_END] = 0; + } + + fn records_push(&mut self, record: Record) -> bool { + debug!("push {:?}", record); + let i = self.shared32[INDEX_END] as usize; + if i >= NUM_RECORDS { + return false; + } + self.shared32[idx(i, 0)] = record.promise_id; + self.shared32[idx(i, 1)] = record.op_id; + self.shared32[idx(i, 2)] = record.arg; + self.shared32[idx(i, 3)] = record.result; + self.shared32[INDEX_END] += 1; + true + } + + fn records_shift(&mut self) -> Option { + let i = self.shared32[INDEX_START] as usize; + if i == self.shared32[INDEX_END] as usize { + return None; + } + let record = Record { + promise_id: self.shared32[idx(i, 0)], + op_id: self.shared32[idx(i, 1)], + arg: self.shared32[idx(i, 2)], + result: self.shared32[idx(i, 3)], + }; + self.shared32[INDEX_START] += 1; + Some(record) + } +} + fn main() { let js_source = include_str!("http_bench.js"); - let isolate = deno_core::Isolate::new(recv_cb); let main_future = lazy(move || { + let isolate = deno_core::Isolate::new(HttpBench::new()); + // TODO currently isolate.execute() must be run inside tokio, hence the // lazy(). It would be nice to not have that contraint. Probably requires // using v8::MicrotasksPolicy::kExplicit @@ -77,69 +207,7 @@ fn new_rid() -> i32 { rid as i32 } -fn recv_cb(isolate: &mut Isolate, zero_copy_buf: deno_buf) { - isolate.test_send_counter += 1; // TODO ideally store this in isolate.state? - - let promise_id = isolate.shared.get_record(0, RECORD_OFFSET_PROMISE_ID); - let op_id = isolate.shared.get_record(0, RECORD_OFFSET_OP); - let arg = isolate.shared.get_record(0, RECORD_OFFSET_ARG); - - // dbg!(promise_id); - // dbg!(op_id); - // dbg!(arg); - - let is_sync = promise_id == 0; - - if is_sync { - // sync ops - match op_id { - OP_CLOSE => { - debug!("close"); - assert!(is_sync); - let mut table = RESOURCE_TABLE.lock().unwrap(); - let r = table.remove(&arg); - isolate.shared.set_record( - 0, - RECORD_OFFSET_RESULT, - if r.is_some() { 0 } else { -1 }, - ); - } - OP_LISTEN => { - debug!("listen"); - assert!(is_sync); - - let addr = "127.0.0.1:4544".parse::().unwrap(); - let listener = tokio::net::TcpListener::bind(&addr).unwrap(); - let rid = new_rid(); - isolate.shared.set_record(0, RECORD_OFFSET_RESULT, rid); - let mut guard = RESOURCE_TABLE.lock().unwrap(); - guard.insert(rid, Repr::TcpListener(listener)); - } - _ => panic!("bad op"), - } - } else { - // async ops - let zero_copy_id = zero_copy_buf.zero_copy_id; - let op = match op_id { - OP_ACCEPT => { - let listener_rid = arg; - op_accept(listener_rid) - } - OP_READ => { - let rid = arg; - op_read(rid, zero_copy_buf) - } - OP_WRITE => { - let rid = arg; - op_write(rid, zero_copy_buf) - } - _ => panic!("bad op"), - }; - isolate.add_op(promise_id, op, zero_copy_id); - } -} - -fn op_accept(listener_rid: i32) -> Box { +fn op_accept(listener_rid: i32) -> Box { debug!("accept {}", listener_rid); Box::new( futures::future::poll_fn(move || { @@ -147,7 +215,7 @@ fn op_accept(listener_rid: i32) -> Box { let maybe_repr = table.get_mut(&listener_rid); match maybe_repr { Some(Repr::TcpListener(ref mut listener)) => listener.poll_accept(), - _ => panic!("bad rid"), + _ => panic!("bad rid {}", listener_rid), } }).and_then(move |(stream, addr)| { debug!("accept success {}", addr); @@ -156,12 +224,36 @@ fn op_accept(listener_rid: i32) -> Box { let mut guard = RESOURCE_TABLE.lock().unwrap(); guard.insert(rid, Repr::TcpStream(stream)); - Ok(AsyncResult { result: rid }) + Ok(rid as i32) }), ) } -fn op_read(rid: i32, mut zero_copy_buf: deno_buf) -> Box { +fn op_listen() -> Box { + debug!("listen"); + + Box::new(lazy(move || { + let addr = "127.0.0.1:4544".parse::().unwrap(); + let listener = tokio::net::TcpListener::bind(&addr).unwrap(); + let rid = new_rid(); + + let mut guard = RESOURCE_TABLE.lock().unwrap(); + guard.insert(rid, Repr::TcpListener(listener)); + futures::future::ok(rid) + })) +} + +fn op_close(rid: i32) -> Box { + debug!("close"); + Box::new(lazy(move || { + let mut table = RESOURCE_TABLE.lock().unwrap(); + let r = table.remove(&rid); + let result = if r.is_some() { 0 } else { -1 }; + futures::future::ok(result) + })) +} + +fn op_read(rid: i32, mut zero_copy_buf: deno_buf) -> Box { debug!("read rid={}", rid); Box::new( futures::future::poll_fn(move || { @@ -175,14 +267,12 @@ fn op_read(rid: i32, mut zero_copy_buf: deno_buf) -> Box { } }).and_then(move |nread| { debug!("read success {}", nread); - Ok(AsyncResult { - result: nread as i32, - }) + Ok(nread as i32) }), ) } -fn op_write(rid: i32, zero_copy_buf: deno_buf) -> Box { +fn op_write(rid: i32, zero_copy_buf: deno_buf) -> Box { debug!("write rid={}", rid); Box::new( futures::future::poll_fn(move || { @@ -196,9 +286,7 @@ fn op_write(rid: i32, zero_copy_buf: deno_buf) -> Box { } }).and_then(move |nwritten| { debug!("write success {}", nwritten); - Ok(AsyncResult { - result: nwritten as i32, - }) + Ok(nwritten as i32) }), ) } diff --git a/core/isolate.rs b/core/isolate.rs new file mode 100644 index 0000000000..cdda7b8153 --- /dev/null +++ b/core/isolate.rs @@ -0,0 +1,548 @@ +// Copyright 2018 the Deno authors. All rights reserved. MIT license. +use crate::js_errors::JSError; +use crate::libdeno; +use crate::libdeno::deno_buf; +use crate::libdeno::deno_mod; +use futures::Async; +use futures::Future; +use futures::Poll; +use libc::c_void; +use std::ffi::CStr; +use std::ffi::CString; +use std::sync::{Once, ONCE_INIT}; + +pub type Op = dyn Future + Send; + +struct PendingOp { + op: Box>, + polled_recently: bool, + zero_copy_id: usize, // non-zero if associated zero-copy buffer. +} + +impl Future for PendingOp { + type Item = R; + type Error = (); + + fn poll(&mut self) -> Poll { + // Do not call poll on ops we've already polled this turn. + if self.polled_recently { + Ok(Async::NotReady) + } else { + self.polled_recently = true; + let op = &mut self.op; + op.poll().map_err(|()| { + // Ops should not error. If an op experiences an error it needs to + // encode that error into the record R, so it can be returned to JS. + panic!("ops should not error") + }) + } + } +} + +pub trait Behavior { + fn startup_snapshot(&mut self) -> Option; + fn startup_shared(&mut self) -> Option; + + fn resolve(&mut self, specifier: &str, referrer: deno_mod) -> deno_mod; + + fn recv(&mut self, record: R, zero_copy_buf: deno_buf) -> (bool, Box>); + + /// Clears the shared buffer. + fn records_reset(&mut self); + + /// Returns false if not enough room. + fn records_push(&mut self, record: R) -> bool; + + /// Returns none if empty. + fn records_shift(&mut self) -> Option; +} + +pub struct Isolate> { + libdeno_isolate: *const libdeno::isolate, + behavior: B, + pending_ops: Vec>, + polled_recently: bool, +} + +unsafe impl> Send for Isolate {} + +impl> Drop for Isolate { + fn drop(&mut self) { + unsafe { libdeno::deno_delete(self.libdeno_isolate) } + } +} + +static DENO_INIT: Once = ONCE_INIT; + +impl> Isolate { + pub fn new(mut behavior: B) -> Self { + DENO_INIT.call_once(|| { + unsafe { libdeno::deno_init() }; + }); + + let config = libdeno::deno_config { + will_snapshot: 0, + load_snapshot: match behavior.startup_snapshot() { + Some(s) => s, + None => libdeno::deno_buf::empty(), + }, + shared: match behavior.startup_shared() { + Some(s) => s, + None => libdeno::deno_buf::empty(), + }, + recv_cb: Self::pre_dispatch, + }; + let libdeno_isolate = unsafe { libdeno::deno_new(config) }; + + Self { + libdeno_isolate, + behavior, + pending_ops: Vec::new(), + polled_recently: false, + } + } + + extern "C" fn pre_dispatch( + user_data: *mut c_void, + control_buf: deno_buf, + zero_copy_buf: deno_buf, + ) { + let isolate = unsafe { Isolate::::from_raw_ptr(user_data) }; + assert_eq!(control_buf.len(), 0); + let zero_copy_id = zero_copy_buf.zero_copy_id; + + let req_record = isolate.behavior.records_shift().unwrap(); + + isolate.behavior.records_reset(); + + let (is_sync, op) = isolate.behavior.recv(req_record, zero_copy_buf); + if is_sync { + let res_record = op.wait().unwrap(); + let push_success = isolate.behavior.records_push(res_record); + assert!(push_success); + // TODO check that if JSError thrown during respond(), that it will be + // picked up. + let _ = isolate.respond(); + } else { + isolate.pending_ops.push(PendingOp { + op, + polled_recently: false, + zero_copy_id, + }); + isolate.polled_recently = false; + } + } + + pub fn zero_copy_release(&self, zero_copy_id: usize) { + unsafe { + libdeno::deno_zero_copy_release(self.libdeno_isolate, zero_copy_id) + } + } + + #[inline] + unsafe fn from_raw_ptr<'a>(ptr: *const c_void) -> &'a mut Self { + let ptr = ptr as *mut _; + &mut *ptr + } + + #[inline] + fn as_raw_ptr(&self) -> *const c_void { + self as *const _ as *const c_void + } + + pub fn execute( + &self, + js_filename: &str, + js_source: &str, + ) -> Result<(), JSError> { + let filename = CString::new(js_filename).unwrap(); + let source = CString::new(js_source).unwrap(); + unsafe { + libdeno::deno_execute( + self.libdeno_isolate, + self.as_raw_ptr(), + filename.as_ptr(), + source.as_ptr(), + ) + }; + if let Some(err) = self.last_exception() { + return Err(err); + } + Ok(()) + } + + fn last_exception(&self) -> Option { + let ptr = unsafe { libdeno::deno_last_exception(self.libdeno_isolate) }; + if ptr.is_null() { + None + } else { + let cstr = unsafe { CStr::from_ptr(ptr) }; + let v8_exception = cstr.to_str().unwrap(); + debug!("v8_exception\n{}\n", v8_exception); + let js_error = JSError::from_v8_exception(v8_exception).unwrap(); + Some(js_error) + } + } + + pub fn check_promise_errors(&self) { + unsafe { + libdeno::deno_check_promise_errors(self.libdeno_isolate); + } + } + + fn respond(&mut self) -> Result<(), JSError> { + let buf = deno_buf::empty(); + unsafe { + libdeno::deno_respond(self.libdeno_isolate, self.as_raw_ptr(), buf) + } + if let Some(err) = self.last_exception() { + Err(err) + } else { + Ok(()) + } + } + + /// Low-level module creation. + /// You probably want to use IsolateState::mod_execute instead. + pub fn mod_new( + &self, + main: bool, + name: &str, + source: &str, + ) -> Result { + let name_ = CString::new(name.to_string()).unwrap(); + let name_ptr = name_.as_ptr() as *const libc::c_char; + + let source_ = CString::new(source.to_string()).unwrap(); + let source_ptr = source_.as_ptr() as *const libc::c_char; + + let id = unsafe { + libdeno::deno_mod_new(self.libdeno_isolate, main, name_ptr, source_ptr) + }; + if let Some(js_error) = self.last_exception() { + assert_eq!(id, 0); + return Err(js_error); + } + + Ok(id) + } + + pub fn mod_get_imports(&self, id: deno_mod) -> Vec { + let len = + unsafe { libdeno::deno_mod_imports_len(self.libdeno_isolate, id) }; + let mut out = Vec::new(); + for i in 0..len { + let specifier_ptr = + unsafe { libdeno::deno_mod_imports_get(self.libdeno_isolate, id, i) }; + let specifier_c: &CStr = unsafe { CStr::from_ptr(specifier_ptr) }; + let specifier: &str = specifier_c.to_str().unwrap(); + + out.push(specifier.to_string()); + } + out + } + + pub fn mod_instantiate(&self, id: deno_mod) -> Result<(), JSError> { + unsafe { + libdeno::deno_mod_instantiate( + self.libdeno_isolate, + self.as_raw_ptr(), + id, + Self::resolve_cb, + ) + }; + if let Some(js_error) = self.last_exception() { + return Err(js_error); + } + Ok(()) + } + + pub fn mod_evaluate(&self, id: deno_mod) -> Result<(), JSError> { + unsafe { + libdeno::deno_mod_evaluate(self.libdeno_isolate, self.as_raw_ptr(), id) + }; + if let Some(js_error) = self.last_exception() { + return Err(js_error); + } + Ok(()) + } + + /// Called during mod_instantiate() only. + extern "C" fn resolve_cb( + user_data: *mut libc::c_void, + specifier_ptr: *const libc::c_char, + referrer: deno_mod, + ) -> deno_mod { + let isolate = unsafe { Isolate::::from_raw_ptr(user_data) }; + let specifier_c: &CStr = unsafe { CStr::from_ptr(specifier_ptr) }; + let specifier: &str = specifier_c.to_str().unwrap(); + isolate.behavior.resolve(specifier, referrer) + } +} + +struct LockerScope { + libdeno_isolate: *const libdeno::isolate, +} + +impl LockerScope { + fn new(libdeno_isolate: *const libdeno::isolate) -> LockerScope { + unsafe { libdeno::deno_lock(libdeno_isolate) } + LockerScope { libdeno_isolate } + } +} + +impl Drop for LockerScope { + fn drop(&mut self) { + unsafe { libdeno::deno_unlock(self.libdeno_isolate) } + } +} + +impl> Future for Isolate { + type Item = (); + type Error = JSError; + + fn poll(&mut self) -> Poll<(), JSError> { + // Lock the current thread for V8. + let _locker = LockerScope::new(self.libdeno_isolate); + + // Clear poll_recently state both on the Isolate itself and + // on the pending ops. + self.polled_recently = false; + for pending in self.pending_ops.iter_mut() { + pending.polled_recently = false; + } + + while !self.polled_recently { + let mut completed_count = 0; + + debug!("poll loop"); + + self.polled_recently = true; + + self.behavior.records_reset(); + + let mut i = 0; + while i != self.pending_ops.len() { + let pending = &mut self.pending_ops[i]; + match pending.poll() { + Err(()) => panic!("unexpectd error"), + Ok(Async::NotReady) => { + i += 1; + } + Ok(Async::Ready(record)) => { + let completed = self.pending_ops.remove(i); + completed_count += 1; + + if completed.zero_copy_id > 0 { + self.zero_copy_release(completed.zero_copy_id); + } + + self.behavior.records_push(record); + } + } + } + + if completed_count > 0 { + debug!("respond"); + self.respond()?; + debug!("after respond"); + } + } + + self.check_promise_errors(); + if let Some(err) = self.last_exception() { + return Err(err); + } + + // We're idle if pending_ops is empty. + if self.pending_ops.is_empty() { + Ok(futures::Async::Ready(())) + } else { + Ok(futures::Async::NotReady) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + + fn js_check(r: Result<(), JSError>) { + if let Err(e) = r { + panic!(e.to_string()); + } + } + + struct TestBehavior { + recv_count: usize, + resolve_count: usize, + push_count: usize, + shift_count: usize, + reset_count: usize, + mod_map: HashMap, + } + + impl TestBehavior { + fn new() -> Self { + Self { + recv_count: 0, + resolve_count: 0, + push_count: 0, + shift_count: 0, + reset_count: 0, + mod_map: HashMap::new(), + } + } + + fn register(&mut self, name: &str, id: deno_mod) { + self.mod_map.insert(name.to_string(), id); + } + } + + impl Behavior<()> for TestBehavior { + fn startup_snapshot(&mut self) -> Option { + None + } + + fn startup_shared(&mut self) -> Option { + None + } + + fn recv( + &mut self, + _record: (), + _zero_copy_buf: deno_buf, + ) -> (bool, Box>) { + self.recv_count += 1; + (false, Box::new(futures::future::ok(()))) + } + + fn resolve(&mut self, specifier: &str, _referrer: deno_mod) -> deno_mod { + self.resolve_count += 1; + match self.mod_map.get(specifier) { + Some(id) => *id, + None => 0, + } + } + + fn records_reset(&mut self) { + self.reset_count += 1; + } + + fn records_push(&mut self, _record: ()) -> bool { + self.push_count += 1; + true + } + + fn records_shift(&mut self) -> Option<()> { + self.shift_count += 1; + Some(()) + } + } + + #[test] + fn test_recv() { + let behavior = TestBehavior::new(); + let isolate = Isolate::new(behavior); + js_check(isolate.execute( + "filename.js", + r#" + libdeno.send(); + async function main() { + libdeno.send(); + } + main(); + "#, + )); + assert_eq!(isolate.behavior.recv_count, 2); + } + + #[test] + fn test_mods() { + let behavior = TestBehavior::new(); + let mut isolate = Isolate::new(behavior); + let mod_a = isolate + .mod_new( + true, + "a.js", + r#" + import { b } from 'b.js' + if (b() != 'b') throw Error(); + libdeno.send(); + "#, + ).unwrap(); + assert_eq!(isolate.behavior.recv_count, 0); + assert_eq!(isolate.behavior.resolve_count, 0); + + let imports = isolate.mod_get_imports(mod_a); + assert_eq!(imports, vec!["b.js".to_string()]); + + let mod_b = isolate + .mod_new(false, "b.js", "export function b() { return 'b' }") + .unwrap(); + let imports = isolate.mod_get_imports(mod_b); + assert_eq!(imports.len(), 0); + + js_check(isolate.mod_instantiate(mod_b)); + assert_eq!(isolate.behavior.recv_count, 0); + assert_eq!(isolate.behavior.resolve_count, 0); + + isolate.behavior.register("b.js", mod_b); + js_check(isolate.mod_instantiate(mod_a)); + assert_eq!(isolate.behavior.recv_count, 0); + assert_eq!(isolate.behavior.resolve_count, 1); + + js_check(isolate.mod_evaluate(mod_a)); + assert_eq!(isolate.behavior.recv_count, 1); + assert_eq!(isolate.behavior.resolve_count, 1); + } + + #[test] + fn test_poll_async_immediate_ops() { + let behavior = TestBehavior::new(); + let mut isolate = Isolate::new(behavior); + + js_check(isolate.execute( + "setup.js", + r#" + let nrecv = 0; + libdeno.recv(() => { + nrecv++; + }); + function assertEq(actual, expected) { + if (expected != actual) { + throw Error(`actual ${actual} expected ${expected} `); + } + } + "#, + )); + assert_eq!(isolate.behavior.recv_count, 0); + js_check(isolate.execute( + "check1.js", + r#" + assertEq(nrecv, 0); + libdeno.send(); + assertEq(nrecv, 0); + "#, + )); + assert_eq!(isolate.behavior.recv_count, 1); + assert_eq!(Ok(Async::Ready(())), isolate.poll()); + assert_eq!(isolate.behavior.recv_count, 1); + js_check(isolate.execute( + "check2.js", + r#" + assertEq(nrecv, 1); + libdeno.send(); + assertEq(nrecv, 1); + "#, + )); + assert_eq!(isolate.behavior.recv_count, 2); + assert_eq!(Ok(Async::Ready(())), isolate.poll()); + js_check(isolate.execute("check3.js", "assertEq(nrecv, 2)")); + assert_eq!(isolate.behavior.recv_count, 2); + // We are idle, so the next poll should be the last. + assert_eq!(Ok(Async::Ready(())), isolate.poll()); + } + +} diff --git a/core/lib.rs b/core/lib.rs index 6416704e27..66233ddfcc 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -1,364 +1,28 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. #[macro_use] extern crate log; extern crate futures; extern crate libc; +mod flags; +mod isolate; mod js_errors; mod libdeno; -mod shared; +pub use crate::flags::v8_set_flags; +pub use crate::isolate::*; pub use crate::js_errors::*; pub use crate::libdeno::deno_buf; -pub use crate::shared::*; -use futures::Async; -use futures::Future; -use futures::Poll; -use libc::c_void; -use std::collections::HashMap; -use std::ffi::CStr; -use std::ffi::CString; -use std::sync::{Once, ONCE_INIT}; +pub use crate::libdeno::deno_mod; -pub struct Isolate { - libdeno_isolate: *const libdeno::isolate, - pending_ops: HashMap, // promise_id -> op - polled_recently: bool, - recv_cb: RecvCallback, - - pub shared: Shared, - pub test_send_counter: u32, // TODO only used for testing- REMOVE. +pub fn v8_version() -> &'static str { + use std::ffi::CStr; + let version = unsafe { libdeno::deno_v8_version() }; + let c_str = unsafe { CStr::from_ptr(version) }; + c_str.to_str().unwrap() } -pub type RecvCallback = fn(isolate: &mut Isolate, zero_copy_buf: deno_buf); - -pub const NUM_RECORDS: usize = 100; - -// TODO rename to AsyncResult -pub struct AsyncResult { - pub result: i32, -} - -pub type Op = dyn Future + Send; - -struct PendingOp { - op: Box, - polled_recently: bool, - zero_copy_id: usize, // non-zero if associated zero-copy buffer. -} - -static DENO_INIT: Once = ONCE_INIT; - -unsafe impl Send for Isolate {} - -impl Isolate { - pub fn new(recv_cb: RecvCallback) -> Self { - DENO_INIT.call_once(|| { - unsafe { libdeno::deno_init() }; - }); - - // Allocate unmanaged memory for the shared buffer by creating a Vec, - // grabbing the raw pointer, and then leaking the Vec so it is never freed. - let mut shared = Shared::new(); - let shared_deno_buf = shared.as_deno_buf(); - - let config = libdeno::deno_config { - will_snapshot: 0, - load_snapshot: deno_buf::empty(), // TODO - shared: shared_deno_buf, - recv_cb: pre_dispatch, - }; - let libdeno_isolate = unsafe { libdeno::deno_new(config) }; - - Self { - pending_ops: HashMap::new(), - polled_recently: false, - libdeno_isolate, - test_send_counter: 0, - recv_cb, - shared, - } - } - - fn zero_copy_release(&self, zero_copy_id: usize) { - unsafe { - libdeno::deno_zero_copy_release(self.libdeno_isolate, zero_copy_id) - } - } - - pub fn add_op( - self: &mut Self, - promise_id: i32, - op: Box, - zero_copy_id: usize, - ) { - debug!("add_op {}", zero_copy_id); - self.pending_ops.insert( - promise_id, - PendingOp { - op, - polled_recently: false, - zero_copy_id, - }, - ); - self.polled_recently = false; - } - - #[inline] - pub unsafe fn from_raw_ptr<'a>(ptr: *const c_void) -> &'a mut Self { - let ptr = ptr as *mut _; - &mut *ptr - } - - #[inline] - pub fn as_raw_ptr(&self) -> *const c_void { - self as *const _ as *const c_void - } - - pub fn execute( - &self, - js_filename: &str, - js_source: &str, - ) -> Result<(), JSError> { - let filename = CString::new(js_filename).unwrap(); - let source = CString::new(js_source).unwrap(); - unsafe { - libdeno::deno_execute( - self.libdeno_isolate, - self.as_raw_ptr(), - filename.as_ptr(), - source.as_ptr(), - ) - }; - if let Some(err) = self.last_exception() { - return Err(err); - } - Ok(()) - } - - pub fn last_exception(&self) -> Option { - let ptr = unsafe { libdeno::deno_last_exception(self.libdeno_isolate) }; - if ptr.is_null() { - None - } else { - let cstr = unsafe { CStr::from_ptr(ptr) }; - let v8_exception = cstr.to_str().unwrap(); - debug!("v8_exception\n{}\n", v8_exception); - let js_error = JSError::from_v8_exception(v8_exception).unwrap(); - Some(js_error) - } - } - - fn check_promise_errors(&self) { - unsafe { - libdeno::deno_check_promise_errors(self.libdeno_isolate); - } - } - - fn respond(&mut self) -> Result<(), JSError> { - let buf = deno_buf::empty(); - unsafe { - libdeno::deno_respond(self.libdeno_isolate, self.as_raw_ptr(), buf) - } - if let Some(err) = self.last_exception() { - Err(err) - } else { - Ok(()) - } - } -} - -struct LockerScope { - libdeno_isolate: *const libdeno::isolate, -} - -impl LockerScope { - fn new(isolate: &Isolate) -> LockerScope { - let libdeno_isolate = isolate.libdeno_isolate; - unsafe { libdeno::deno_lock(libdeno_isolate) } - LockerScope { libdeno_isolate } - } -} - -impl Drop for LockerScope { - fn drop(&mut self) { - unsafe { libdeno::deno_unlock(self.libdeno_isolate) } - } -} - -impl Future for Isolate { - type Item = (); - type Error = JSError; - - fn poll(&mut self) -> Poll<(), JSError> { - // Lock the current thread for V8. - let _locker = LockerScope::new(self); - - // Clear - self.polled_recently = false; - for (_, pending) in self.pending_ops.iter_mut() { - pending.polled_recently = false; - } - - while !self.polled_recently { - let mut complete = HashMap::::new(); - - self.polled_recently = true; - for (promise_id, pending) in self.pending_ops.iter_mut() { - // Do not call poll on futures we've already polled this turn. - if pending.polled_recently { - continue; - } - pending.polled_recently = true; - - let promise_id = *promise_id; - let op = &mut pending.op; - match op.poll() { - Err(op_err) => { - eprintln!("op err {:?}", op_err); - complete.insert(promise_id, AsyncResult { result: -1 }); - debug!("pending op {} complete err", promise_id); - } - Ok(Async::Ready(async_result)) => { - complete.insert(promise_id, async_result); - debug!("pending op {} complete ready", promise_id); - } - Ok(Async::NotReady) => { - debug!("pending op {} not ready", promise_id); - continue; - } - } - } - - self.shared.set_num_records(complete.len() as i32); - if complete.len() > 0 { - // self.zero_copy_release() and self.respond() need Locker. - let mut i = 0; - for (promise_id, async_result) in complete.iter_mut() { - let pending = self.pending_ops.remove(promise_id).unwrap(); - - if pending.zero_copy_id > 0 { - self.zero_copy_release(pending.zero_copy_id); - } - - self - .shared - .set_record(i, RECORD_OFFSET_PROMISE_ID, *promise_id); - self - .shared - .set_record(i, RECORD_OFFSET_RESULT, async_result.result); - i += 1; - } - self.respond()?; - } - } - - self.check_promise_errors(); - if let Some(err) = self.last_exception() { - return Err(err); - } - - // We're idle if pending_ops is empty. - if self.pending_ops.is_empty() { - Ok(futures::Async::Ready(())) - } else { - Ok(futures::Async::NotReady) - } - } -} - -extern "C" fn pre_dispatch( - user_data: *mut c_void, - control_buf: deno_buf, - zero_copy_buf: deno_buf, -) { - let isolate = unsafe { Isolate::from_raw_ptr(user_data) }; - assert_eq!(control_buf.len(), 0); - (isolate.recv_cb)(isolate, zero_copy_buf); -} - -#[cfg(test)] -mod tests { - use super::*; - - fn inc_counter(isolate: &mut Isolate, zero_copy_buf: deno_buf) { - assert_eq!(zero_copy_buf.len(), 0); - isolate.test_send_counter += 1; // TODO ideally store this in isolate.state? - } - - fn js_check(r: Result<(), JSError>) { - if let Err(e) = r { - panic!(e.to_string()); - } - } - - #[test] - fn test_execute() { - let isolate = Isolate::new(inc_counter); - js_check(isolate.execute( - "filename.js", - r#" - libdeno.send(); - async function main() { - libdeno.send(); - } - main(); - "#, - )); - // We expect that main is executed even tho we didn't poll. - assert_eq!(isolate.test_send_counter, 2); - } - - fn async_immediate(isolate: &mut Isolate, zero_copy_buf: deno_buf) { - assert_eq!(zero_copy_buf.len(), 0); - isolate.test_send_counter += 1; // TODO ideally store this in isolate.state? - - let promise_id = 0; - let op = Box::new(futures::future::ok(AsyncResult { result: 0 })); - isolate.add_op(promise_id, op, zero_copy_buf.zero_copy_id); - } - - #[test] - fn test_poll_async_immediate_ops() { - let mut isolate = Isolate::new(async_immediate); - js_check(isolate.execute( - "setup.js", - r#" - let nrecv = 0; - libdeno.recv(() => { - nrecv++; - }); - function assertEq(actual, expected) { - if (expected != actual) { - throw Error(`actual ${actual} expected ${expected} `); - } - } - "#, - )); - assert_eq!(isolate.test_send_counter, 0); - js_check(isolate.execute( - "check1.js", - r#" - assertEq(nrecv, 0); - libdeno.send(); - assertEq(nrecv, 0); - "#, - )); - assert_eq!(isolate.test_send_counter, 1); - assert_eq!(Ok(Async::Ready(())), isolate.poll()); - assert_eq!(isolate.test_send_counter, 1); - js_check(isolate.execute( - "check2.js", - r#" - assertEq(nrecv, 1); - libdeno.send(); - assertEq(nrecv, 1); - "#, - )); - assert_eq!(isolate.test_send_counter, 2); - assert_eq!(Ok(Async::Ready(())), isolate.poll()); - js_check(isolate.execute("check3.js", "assertEq(nrecv, 2)")); - assert_eq!(isolate.test_send_counter, 2); - // We are idle, so the next poll should be the last. - assert_eq!(Ok(Async::Ready(())), isolate.poll()); - } +#[test] +fn test_v8_version() { + assert!(v8_version().len() > 3); } diff --git a/src/flags.rs b/src/flags.rs index 376e0ab95b..471fc58ede 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -1,14 +1,7 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::libdeno; - +use deno_core::v8_set_flags; use getopts; use getopts::Options; -use libc::c_char; -use libc::c_int; -use std::ffi::CStr; -use std::ffi::CString; -use std::mem; -use std::vec::Vec; // Creates vector of strings, Vec #[cfg(test)] @@ -291,80 +284,3 @@ fn test_set_flags_8() { } ) } - -// Returns args passed to V8, followed by args passed to JS -fn v8_set_flags_preprocess(args: Vec) -> (Vec, Vec) { - let (rest, mut v8_args) = - args.into_iter().partition(|ref a| a.as_str() == "--help"); - - // Replace args being sent to V8 - for a in &mut v8_args { - if a == "--v8-options" { - mem::swap(a, &mut String::from("--help")); - } - } - (v8_args, rest) -} - -#[test] -fn test_v8_set_flags_preprocess_1() { - let js_args = v8_set_flags_preprocess(vec![ - "deno".to_string(), - "--v8-options".to_string(), - ]); - assert_eq!( - js_args, - (vec!["deno".to_string(), "--help".to_string()], vec![]) - ); -} - -#[test] -fn test_v8_set_flags_preprocess_2() { - let js_args = - v8_set_flags_preprocess(vec!["deno".to_string(), "--help".to_string()]); - assert_eq!( - js_args, - (vec!["deno".to_string()], vec!["--help".to_string()]) - ); -} - -// Pass the command line arguments to v8. -// Returns a vector of command line arguments that v8 did not understand. -#[cfg_attr(feature = "cargo-clippy", allow(stutter))] -pub fn v8_set_flags(args: Vec) -> Vec { - // deno_set_v8_flags(int* argc, char** argv) mutates argc and argv to remove - // flags that v8 understands. - // First parse core args, then convert to a vector of C strings. - let (args, rest) = v8_set_flags_preprocess(args); - - // Make a new array, that can be modified by V8::SetFlagsFromCommandLine(), - // containing mutable raw pointers to the individual command line args. - let mut raw_argv = args - .iter() - .map(|arg| CString::new(arg.as_str()).unwrap().into_bytes_with_nul()) - .collect::>(); - let mut c_argv = raw_argv - .iter_mut() - .map(|arg| arg.as_mut_ptr() as *mut c_char) - .collect::>(); - - // Store the length of the c_argv array in a local variable. We'll pass - // a pointer to this local variable to deno_set_v8_flags(), which then - // updates its value. - let mut c_argv_len = c_argv.len() as c_int; - // Let v8 parse the arguments it recognizes and remove them from c_argv. - unsafe { - libdeno::deno_set_v8_flags(&mut c_argv_len, c_argv.as_mut_ptr()); - }; - // If c_argv_len was updated we have to change the length of c_argv to match. - c_argv.truncate(c_argv_len as usize); - // Copy the modified arguments list into a proper rust vec and return it. - c_argv - .iter() - .map(|ptr| unsafe { - let cstr = CStr::from_ptr(*ptr as *const c_char); - let slice = cstr.to_str().unwrap(); - slice.to_string() - }).chain(rest.into_iter()) - .collect() -} diff --git a/src/version.rs b/src/version.rs index 1cf0822576..e6ec9008b0 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,12 +1,6 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::libdeno; - -use std::ffi::CStr; - pub const DENO: &str = env!("CARGO_PKG_VERSION"); pub fn v8() -> &'static str { - let version = unsafe { libdeno::deno_v8_version() }; - let c_str = unsafe { CStr::from_ptr(version) }; - c_str.to_str().unwrap() + deno_core::v8_version() }