fish-shell/fish-rust/src/ffi.rs

228 lines
6.4 KiB
Rust
Raw Normal View History

use crate::io::{IoStreams, OutputStreamFfi};
use crate::wchar;
#[rustfmt::skip]
use ::std::pin::Pin;
#[rustfmt::skip]
use ::std::slice;
pub use crate::wait_handle::{WaitHandleRef, WaitHandleStore};
2023-08-09 06:16:04 +08:00
use crate::wchar::prelude::*;
use crate::wchar_ffi::WCharFromFFI;
2023-01-15 06:56:24 +08:00
use autocxx::prelude::*;
// autocxx has been hacked up to know about this.
pub type wchar_t = u32;
include_cpp! {
#include "autoload.h"
#include "color.h"
#include "common.h"
#include "complete.h"
#include "env.h"
#include "env_dispatch.h"
#include "env_universal_common.h"
#include "event.h"
2023-06-17 05:38:08 +08:00
#include "exec.h"
#include "fallback.h"
2023-01-15 06:56:24 +08:00
#include "fds.h"
Port AST to Rust The translation is fairly direct though it adds some duplication, for example there are multiple "match" statements that mimic function overloading. Rust has no overloading, and we cannot have generic methods in the Node trait (due to a Rust limitation, the error is like "cannot be made into an object") so we include the type name in method names. Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor") that takes care of the AST traversal while the AST consumption remains in C++ for now. In future, "IndentVisitor" should absorb the entirety of "indent_visitor_t". This pattern requires that "fish_indent" be exposed includable header to the CXX bridge. Alternatively, we could define FFI wrappers for recursive AST traversal. Rust requires we separate the AST visitors for "mut" and "const" scenarios. Take this opportunity to concretize both visitors: The only client that requires mutable access is the populator. To match the structure of the C++ populator which makes heavy use of function overloading, we need to add a bunch of functions to the trait. Since there is no other mutable visit, this seems acceptable. The "const" visitors never use "will_visit_fields_of()" or "did_visit_fields_of()", so remove them (though this is debatable). Like in the C++ implementation, the AST nodes themselves are largely defined via macros. Union fields like "Statement" and "ArgumentOrRedirection" do currently not use macros but may in future. This commit also introduces a precedent for a type that is defined in one CXX bridge and used in another one - "ParseErrorList". To make this work we need to manually define "ExternType". There is one annoyance with CXX: functions that take explicit lifetime parameters require to be marked as unsafe. This makes little sense because functions that return `&Foo` with implicit lifetime can be misused the same way on the C++ side. One notable change is that we cannot directly port "find_block_open_keyword()" (which is used to compute an error) because it relies on the stack of visited nodes. We cannot modify a stack of node references while we do the "mut" walk. Happily, an idiomatic solution is easy: we can tell the AST visitor to backtrack to the parent node and create the error there. Since "node_t::accept_base" is no longer a template we don't need the "node_visitation_t" trampoline anymore. The added copying at the FFI boundary makes things slower (memcpy dominates the profile) but it's not unusable, which is good news: $ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'" Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish' Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms] Range (min … max): 193.2 ms … 205.1 ms 15 runs Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish' Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms] Range (min … max): 611.7 ms … 805.5 ms 10 runs Summary './fish.old -c 'source ../share/completions/git.fish'' ran 3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish'' Leftovers: - Enum variants are still snakecase; I didn't get around to changing this yet. - "ast_type_to_string()" still returns a snakecase name. This could be changed since it's not user visible.
2023-04-02 22:42:59 +08:00
#include "fish_indent_common.h"
2023-01-15 06:56:24 +08:00
#include "flog.h"
2023-04-11 01:49:50 +08:00
#include "function.h"
#include "highlight.h"
#include "history.h"
2023-01-15 06:56:24 +08:00
#include "io.h"
#include "input_common.h"
#include "input.h"
#include "parse_constants.h"
2023-01-15 06:56:24 +08:00
#include "parser.h"
#include "parse_util.h"
2023-02-25 04:14:13 +08:00
#include "path.h"
2023-01-15 06:56:24 +08:00
#include "proc.h"
#include "reader.h"
#include "screen.h"
#include "tokenizer.h"
#include "wutil.h"
2023-01-15 06:56:24 +08:00
#include "builtins/bind.h"
#include "builtins/commandline.h"
#include "builtins/ulimit.h"
2023-01-15 06:56:24 +08:00
safety!(unsafe_ffi)
generate_pod!("wcharz_t")
generate!("wcstring_list_ffi_t")
2023-01-15 06:56:24 +08:00
generate!("wperror")
generate!("set_inheriteds_ffi")
generate!("reader_init")
generate!("reader_run_count")
generate!("term_copy_modes")
generate!("set_profiling_active")
generate!("reader_read_ffi")
generate!("fish_is_unwinding_for_exit")
generate!("restore_term_mode")
generate!("update_wait_on_escape_ms_ffi")
generate!("read_generation_count")
generate!("set_flog_output_file_ffi")
generate!("flog_setlinebuf_ffi")
generate!("activate_flog_categories_by_pattern")
generate!("save_term_foreground_process_group")
generate!("restore_term_foreground_process_group_for_exit")
generate!("set_cloexec")
generate!("builtin_bind")
generate!("builtin_commandline")
generate!("builtin_ulimit")
generate!("init_input")
2023-01-15 06:56:24 +08:00
generate_pod!("pipes_ffi_t")
2023-01-15 06:56:24 +08:00
generate!("make_pipes_ffi")
2023-03-28 23:59:51 +08:00
generate!("log_extra_to_flog_file")
2023-01-15 06:56:24 +08:00
generate!("wgettext_ptr")
2023-01-15 06:56:24 +08:00
Port AST to Rust The translation is fairly direct though it adds some duplication, for example there are multiple "match" statements that mimic function overloading. Rust has no overloading, and we cannot have generic methods in the Node trait (due to a Rust limitation, the error is like "cannot be made into an object") so we include the type name in method names. Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor") that takes care of the AST traversal while the AST consumption remains in C++ for now. In future, "IndentVisitor" should absorb the entirety of "indent_visitor_t". This pattern requires that "fish_indent" be exposed includable header to the CXX bridge. Alternatively, we could define FFI wrappers for recursive AST traversal. Rust requires we separate the AST visitors for "mut" and "const" scenarios. Take this opportunity to concretize both visitors: The only client that requires mutable access is the populator. To match the structure of the C++ populator which makes heavy use of function overloading, we need to add a bunch of functions to the trait. Since there is no other mutable visit, this seems acceptable. The "const" visitors never use "will_visit_fields_of()" or "did_visit_fields_of()", so remove them (though this is debatable). Like in the C++ implementation, the AST nodes themselves are largely defined via macros. Union fields like "Statement" and "ArgumentOrRedirection" do currently not use macros but may in future. This commit also introduces a precedent for a type that is defined in one CXX bridge and used in another one - "ParseErrorList". To make this work we need to manually define "ExternType". There is one annoyance with CXX: functions that take explicit lifetime parameters require to be marked as unsafe. This makes little sense because functions that return `&Foo` with implicit lifetime can be misused the same way on the C++ side. One notable change is that we cannot directly port "find_block_open_keyword()" (which is used to compute an error) because it relies on the stack of visited nodes. We cannot modify a stack of node references while we do the "mut" walk. Happily, an idiomatic solution is easy: we can tell the AST visitor to backtrack to the parent node and create the error there. Since "node_t::accept_base" is no longer a template we don't need the "node_visitation_t" trampoline anymore. The added copying at the FFI boundary makes things slower (memcpy dominates the profile) but it's not unusable, which is good news: $ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'" Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish' Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms] Range (min … max): 193.2 ms … 205.1 ms 15 runs Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish' Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms] Range (min … max): 611.7 ms … 805.5 ms 10 runs Summary './fish.old -c 'source ../share/completions/git.fish'' ran 3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish'' Leftovers: - Enum variants are still snakecase; I didn't get around to changing this yet. - "ast_type_to_string()" still returns a snakecase name. This could be changed since it's not user visible.
2023-04-02 22:42:59 +08:00
generate!("pretty_printer_t")
Port fd_monitor (and its needed components) I needed to rename some types already ported to rust so they don't clash with their still-extant cpp counterparts. Helper ffi functions added to avoid needing to dynamically allocate an FdMonitorItem for every fd (we use dozens per basic prompt). I ported some functions from cpp to rust that are used only in the backend but without removing their existing cpp counterparts so cpp code can continue to use their version of them (`wperror` and `make_detached_pthread`). I ran into issues porting line-by-line logic because rust inverts the behavior of `std::remove_if(..)` by making it (basically) `Vec::retain_if(..)` so I replaced bools with an explict enum to make everything clearer. I'll port the cpp tests for this separately, for now they're using ffi. Porting closures was ugly. It's nothing hard, but it's very ugly as now each capturing lambda has been changed into an explicit struct that contains its parameters (that needs to be dynamically allocated), a standalone callback (member) function to replace the lambda contents, and a separate trampoline function to call it from rust over the shared C abi (not really relevant to x86_64 w/ its single calling convention but probably needed on other platforms). I don't like that `fd_monitor.rs` has its own `c_void`. I couldn't find a way to move that to `ffi.rs` but still get cxx bridge to consider it a shared POD. Every time I moved it to a different module, it would consider it to be an opaque rust type instead. I worry this means we're going to have multiple `c_void1`, `c_void2`, etc. types as we continue to port code to use function pointers. Also, rust treats raw pointers as foreign so you can't do `impl Send for * const Foo` even if `Foo` is from the same module. That necessitated a wrapper type (`void_ptr`) that implements `Send` and `Sync` so we can move stuff between threads. The code in fd_monitor_t has been split into two objects, one that is used by the caller and a separate one associated with the background thread (this is made nice and clean by rust's ownership model). Objects not needed under the lock (i.e. accessed by the background thread exclusively) were moved to the separate `BackgroundFdMonitor` type.
2023-02-18 09:21:44 +08:00
generate!("fd_event_signaller_t")
generate!("highlight_role_t")
generate!("highlight_spec_t")
2023-06-17 05:38:08 +08:00
generate!("rgb_color_t")
generate_pod!("color24_t")
generate!("reader_status_count")
generate!("reader_write_title_ffi")
generate!("reader_push_ffi")
generate!("reader_readline_ffi")
generate!("reader_pop")
generate!("commandline_get_state_history_ffi")
generate!("commandline_set_buffer_ffi")
generate!("commandline_get_state_initialized_ffi")
generate!("commandline_get_state_text_ffi")
generate!("completion_apply_to_command_line")
generate!("get_history_variable_text_ffi")
generate_pod!("escape_string_style_t")
generate!("screen_set_midnight_commander_hack")
generate!("screen_clear_layout_cache_ffi")
generate!("escape_code_length_ffi")
generate!("reader_schedule_prompt_repaint")
generate!("reader_change_history")
generate!("reader_change_cursor_selection_mode")
generate!("reader_set_autosuggestion_enabled_ffi")
generate!("update_wait_on_sequence_key_ms_ffi")
}
2023-01-15 06:56:24 +08:00
/// Allow wcharz_t to be "into" wstr.
2023-08-09 06:16:04 +08:00
impl From<wcharz_t> for &wstr {
2023-01-15 06:56:24 +08:00
fn from(w: wcharz_t) -> Self {
let len = w.length();
#[allow(clippy::unnecessary_cast)]
2023-01-15 06:56:24 +08:00
let v = unsafe { slice::from_raw_parts(w.str_ as *const u32, len) };
2023-08-09 06:16:04 +08:00
wstr::from_slice(v).expect("Invalid UTF-32")
2023-01-15 06:56:24 +08:00
}
}
/// Allow wcharz_t to be "into" WString.
2023-08-09 06:16:04 +08:00
impl From<wcharz_t> for WString {
2023-01-15 06:56:24 +08:00
fn from(w: wcharz_t) -> Self {
2023-08-09 06:16:04 +08:00
let w: &wstr = w.into();
w.to_owned()
2023-01-15 06:56:24 +08:00
}
}
/// Allow wcstring_list_ffi_t to be "into" Vec<WString>.
impl From<&wcstring_list_ffi_t> for Vec<wchar::WString> {
fn from(w: &wcstring_list_ffi_t) -> Self {
let mut result = Vec::with_capacity(w.size());
for i in 0..w.size() {
result.push(w.at(i).from_ffi());
}
result
}
}
/// A bogus trait for turning &mut Foo into Pin<&mut Foo>.
/// autocxx enforces that non-const methods must be called through Pin,
/// but this means we can't pass around mutable references to types like Parser.
/// We also don't want to assert that Parser is Unpin.
/// So we just allow constructing a pin from a mutable reference; none of the C++ code.
/// It's worth considering disabling this in cxx; for now we use this trait.
/// Eventually Parser and IoStreams will not require Pin so we just unsafe-it away.
pub trait Repin {
fn pin(&mut self) -> Pin<&mut Self> {
unsafe { Pin::new_unchecked(self) }
}
fn unpin(self: Pin<&mut Self>) -> &mut Self {
unsafe { self.get_unchecked_mut() }
}
}
// Implement Repin for our types.
impl Repin for IoStreams<'_> {}
impl Repin for wcstring_list_ffi_t {}
impl Repin for rgb_color_t {}
impl Repin for OutputStreamFfi<'_> {}
2023-01-15 06:56:24 +08:00
pub use autocxx::c_int;
pub use ffi::*;
pub use libc::c_char;
Port fd_monitor (and its needed components) I needed to rename some types already ported to rust so they don't clash with their still-extant cpp counterparts. Helper ffi functions added to avoid needing to dynamically allocate an FdMonitorItem for every fd (we use dozens per basic prompt). I ported some functions from cpp to rust that are used only in the backend but without removing their existing cpp counterparts so cpp code can continue to use their version of them (`wperror` and `make_detached_pthread`). I ran into issues porting line-by-line logic because rust inverts the behavior of `std::remove_if(..)` by making it (basically) `Vec::retain_if(..)` so I replaced bools with an explict enum to make everything clearer. I'll port the cpp tests for this separately, for now they're using ffi. Porting closures was ugly. It's nothing hard, but it's very ugly as now each capturing lambda has been changed into an explicit struct that contains its parameters (that needs to be dynamically allocated), a standalone callback (member) function to replace the lambda contents, and a separate trampoline function to call it from rust over the shared C abi (not really relevant to x86_64 w/ its single calling convention but probably needed on other platforms). I don't like that `fd_monitor.rs` has its own `c_void`. I couldn't find a way to move that to `ffi.rs` but still get cxx bridge to consider it a shared POD. Every time I moved it to a different module, it would consider it to be an opaque rust type instead. I worry this means we're going to have multiple `c_void1`, `c_void2`, etc. types as we continue to port code to use function pointers. Also, rust treats raw pointers as foreign so you can't do `impl Send for * const Foo` even if `Foo` is from the same module. That necessitated a wrapper type (`void_ptr`) that implements `Send` and `Sync` so we can move stuff between threads. The code in fd_monitor_t has been split into two objects, one that is used by the caller and a separate one associated with the background thread (this is made nice and clean by rust's ownership model). Objects not needed under the lock (i.e. accessed by the background thread exclusively) were moved to the separate `BackgroundFdMonitor` type.
2023-02-18 09:21:44 +08:00
/// A version of [`* const core::ffi::c_void`] (or [`* const libc::c_void`], if you prefer) that
/// implements `Copy` and `Clone`, because those two don't. Used to represent a `void *` ptr for ffi
/// purposes.
#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct void_ptr(pub *const core::ffi::c_void);
impl core::fmt::Debug for void_ptr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:p}", &self.0)
}
}
unsafe impl Send for void_ptr {}
unsafe impl Sync for void_ptr {}
impl core::convert::From<*const core::ffi::c_void> for void_ptr {
fn from(value: *const core::ffi::c_void) -> Self {
Self(value as *const _)
}
}
impl core::convert::From<*const u8> for void_ptr {
fn from(value: *const u8) -> Self {
Self(value as *const _)
}
}
impl core::convert::From<*const autocxx::c_void> for void_ptr {
fn from(value: *const autocxx::c_void) -> Self {
Self(value as *const _)
}
}
impl core::convert::From<void_ptr> for *const u8 {
fn from(value: void_ptr) -> Self {
value.0 as *const _
}
}
impl core::convert::From<void_ptr> for *const core::ffi::c_void {
fn from(value: void_ptr) -> Self {
value.0 as *const _
}
}
impl core::convert::From<void_ptr> for *const autocxx::c_void {
fn from(value: void_ptr) -> Self {
value.0 as *const _
}
}