Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: use fast ops for tty #15976

Merged
merged 12 commits into from
Sep 23, 2022
21 changes: 21 additions & 0 deletions cli/bench/tty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
const queueMicrotask = globalThis.queueMicrotask || process.nextTick;
let [total, count] = typeof Deno !== "undefined"
? Deno.args
: [process.argv[2], process.argv[3]];

total = total ? parseInt(total, 0) : 50;
count = count ? parseInt(count, 10) : 100000;

function bench(fun) {
const start = Date.now();
for (let i = 0; i < count; i++) fun();
const elapsed = Date.now() - start;
const rate = Math.floor(count / (elapsed / 1000));
console.log(`time ${elapsed} ms rate ${rate}`);
if (--total) queueMicrotask(() => bench(fun));
}

bench(() => {
Deno.consoleSize(0);
});
8 changes: 7 additions & 1 deletion ops/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,9 @@ fn codegen_fast_impl(
if slices.get(&idx).is_some() {
return quote! {
match unsafe { &* #ident }.get_storage_if_aligned() {
Some(s) => s,
Some(s) => {
s
},
None => {
unsafe { &mut * fast_api_callback_options }.fallback = true;
return Default::default();
Expand Down Expand Up @@ -558,6 +560,10 @@ fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option<FastApiSyn> {
args.push(arg);
}
None => match is_ref_slice(&ty) {
Some(SliceType::U32Mut) => {
args.push(quote! { #core::v8::fast_api::Type::TypedArray(#core::v8::fast_api::CType::Uint32) });
slices.insert(pos, quote!(u32));
}
Some(_) => {
args.push(quote! { #core::v8::fast_api::Type::TypedArray(#core::v8::fast_api::CType::Uint8) });
slices.insert(pos, quote!(u8));
Expand Down
24 changes: 16 additions & 8 deletions runtime/js/40_tty.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,29 @@
const core = window.Deno.core;
const ops = core.ops;

function unwrapErr(r) {
if (r === false) {
// A fast call indicating failure. We insert the error in OpState.
// Let's throw the error using a slow call.
ops.op_take_last_error();
}
}

const size = new Uint32Array(2);
function consoleSize(rid) {
return ops.op_console_size(rid);
unwrapErr(ops.op_console_size.fast(rid, size));
return { columns: size[0], rows: size[1] };
}

const isattyBuffer = new Uint8Array(1);
function isatty(rid) {
return ops.op_isatty(rid);
unwrapErr(ops.op_isatty.fast(rid, isattyBuffer));
return !!isattyBuffer[0];
}

const DEFAULT_SET_RAW_OPTIONS = {
cbreak: false,
};

const DEFAULT_CBREAK = false;
function setRaw(rid, mode, options = {}) {
const rOptions = { ...DEFAULT_SET_RAW_OPTIONS, ...options };
ops.op_set_raw({ rid, mode, options: rOptions });
unwrapErr(ops.op_set_raw.fast(rid, mode, options.cbreak || DEFAULT_CBREAK));
}

window.__bootstrap.tty = {
Expand Down
116 changes: 59 additions & 57 deletions runtime/ops/tty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ use deno_core::error::AnyError;
use deno_core::op;
use deno_core::Extension;
use deno_core::OpState;
use deno_core::ResourceId;
use serde::Deserialize;
use serde::Serialize;
use std::io::Error;

#[cfg(unix)]
Expand Down Expand Up @@ -46,31 +43,42 @@ pub fn init() -> Extension {
op_set_raw::decl(),
op_isatty::decl(),
op_console_size::decl(),
op_take_last_error::decl(),
])
.build()
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetRawOptions {
cbreak: bool,
// #[op(fast)] cannot throw errors themselves, so we send an error code
// to JS and the op puts a TtyError in state.
//
// JS should call op_take_last_error() to throw the error.
struct TtyError(AnyError);

#[inline]
fn wrap_err(state: &mut OpState, r: Result<(), AnyError>) -> bool {
match r {
Ok(_) => true,
Err(err) => {
state.put::<TtyError>(TtyError(err));
false
}
}
}

#[derive(Deserialize)]
pub struct SetRawArgs {
rid: ResourceId,
mode: bool,
options: SetRawOptions,
#[op]
fn op_take_last_error(op_state: &mut OpState) -> Result<(), AnyError> {
Err(op_state.take::<TtyError>().0)
}

#[op]
fn op_set_raw(state: &mut OpState, args: SetRawArgs) -> Result<(), AnyError> {
#[op(fast)]
fn op_set_raw(
state: &mut OpState,
rid: u32,
is_raw: bool,
cbreak: bool,
) -> bool {
super::check_unstable(state, "Deno.setRaw");

let rid = args.rid;
let is_raw = args.mode;
let cbreak = args.options.cbreak;

// From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs
// and https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs
// and https://github.com/crossterm-rs/crossterm/blob/e35d4d2c1cc4c919e36d242e014af75f6127ab50/src/terminal/sys/windows.rs
Expand All @@ -83,10 +91,10 @@ fn op_set_raw(state: &mut OpState, args: SetRawArgs) -> Result<(), AnyError> {
use winapi::um::{consoleapi, handleapi};

if cbreak {
return Err(deno_core::error::not_supported());
return wrap_err(state, Err(deno_core::error::not_supported()));
}

StdFileResource::with_file(state, rid, move |std_file| {
let result = StdFileResource::with_file(state, rid, move |std_file| {
let handle = std_file.as_raw_handle();

if handle == handleapi::INVALID_HANDLE_VALUE {
Expand All @@ -112,13 +120,14 @@ fn op_set_raw(state: &mut OpState, args: SetRawArgs) -> Result<(), AnyError> {
}

Ok(())
})
});
wrap_err(state, result);
}
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;

StdFileResource::with_file_and_metadata(
let result = StdFileResource::with_file_and_metadata(
state,
rid,
move |std_file, meta_data| {
Expand Down Expand Up @@ -164,13 +173,14 @@ fn op_set_raw(state: &mut OpState, args: SetRawArgs) -> Result<(), AnyError> {

Ok(())
},
)
);
wrap_err(state, result)
}
}

#[op]
fn op_isatty(state: &mut OpState, rid: ResourceId) -> Result<bool, AnyError> {
let isatty: bool = StdFileResource::with_file(state, rid, move |std_file| {
#[op(fast)]
fn op_isatty(state: &mut OpState, rid: u32, out: &mut [u8]) -> bool {
let result = StdFileResource::with_file(state, rid, move |std_file| {
#[cfg(windows)]
{
use winapi::shared::minwindef::FALSE;
Expand All @@ -181,34 +191,32 @@ fn op_isatty(state: &mut OpState, rid: ResourceId) -> Result<bool, AnyError> {
// If I cannot get mode out of console, it is not a console.
// TODO(bartlomieju):
#[allow(clippy::undocumented_unsafe_blocks)]
Ok(unsafe { consoleapi::GetConsoleMode(handle, &mut test_mode) != FALSE })
{
out[0] = unsafe {
consoleapi::GetConsoleMode(handle, &mut test_mode) != FALSE
} as u8;
}
}
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
let raw_fd = std_file.as_raw_fd();
// TODO(bartlomieju):
#[allow(clippy::undocumented_unsafe_blocks)]
Ok(unsafe { libc::isatty(raw_fd as libc::c_int) == 1 })
{
out[0] = unsafe { libc::isatty(raw_fd as libc::c_int) == 1 } as u8;
}
}
})?;
Ok(isatty)
}

#[derive(Serialize)]
struct ConsoleSize {
columns: u32,
rows: u32,
Ok(())
});
wrap_err(state, result)
}

#[op]
fn op_console_size(
state: &mut OpState,
rid: ResourceId,
) -> Result<ConsoleSize, AnyError> {
#[op(fast)]
fn op_console_size(state: &mut OpState, rid: u32, result: &mut [u32]) -> bool {
println!("{}", result.len());
super::check_unstable(state, "Deno.consoleSize");

let size = StdFileResource::with_file(state, rid, move |std_file| {
let result = StdFileResource::with_file(state, rid, move |std_file| {
#[cfg(windows)]
{
use std::os::windows::io::AsRawHandle;
Expand All @@ -224,11 +232,9 @@ fn op_console_size(
{
return Err(Error::last_os_error().into());
}

Ok(ConsoleSize {
columns: bufinfo.dwSize.X as u32,
rows: bufinfo.dwSize.Y as u32,
})
result[0] = bufinfo.dwSize.X as u32;
result[1] = bufinfo.dwSize.Y as u32;
Ok(())
}
}

Expand All @@ -244,15 +250,11 @@ fn op_console_size(
if libc::ioctl(fd, libc::TIOCGWINSZ, &mut size as *mut _) != 0 {
return Err(Error::last_os_error().into());
}

// TODO (caspervonb) return a tuple instead
Ok(ConsoleSize {
columns: size.ws_col as u32,
rows: size.ws_row as u32,
})
result[0] = size.ws_col as u32;
result[1] = size.ws_row as u32;
Ok(())
}
}
})?;

Ok(size)
});
wrap_err(state, result)
}