--- a/bindings/c/libjsonnet_test_file.c +++ b/bindings/c/libjsonnet_test_file.c @@ -1,16 +1,3 @@ -/* -Copyright 2015 Google Inc. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - #include #include --- a/bindings/jsonnet/Cargo.toml +++ b/bindings/jsonnet/Cargo.toml @@ -27,11 +27,17 @@ [lib] name = "jsonnet" -crate-type = ["cdylib"] +crate-type = ["cdylib", "staticlib"] [features] +default = ["interop-common", "interop-wasm", "interop-threading"] # Export additional functions for native integration, i.e ability to set custom trace format -interop = [] +interop-common = [] +# Provide ability to statically override callbacks from WASM (by using imports) +interop-wasm = [] +# Provide ability to move jsonnet vm state between threads +interop-threading = [] + experimental = ["exp-preserve-order", "exp-destruct"] exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order"] exp-destruct = ["jrsonnet-evaluator/exp-destruct"] --- a/bindings/jsonnet/src/import.rs +++ b/bindings/jsonnet/src/import.rs @@ -15,7 +15,7 @@ use jrsonnet_evaluator::{ bail, error::{ErrorKind::*, Result}, - FileImportResolver, ImportResolver, + ImportResolver, }; use jrsonnet_gcmodule::Trace; use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath}; @@ -106,6 +106,10 @@ fn as_any(&self) -> &dyn Any { self } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } } /// # Safety @@ -117,7 +121,7 @@ cb: JsonnetImportCallback, ctx: *mut c_void, ) { - vm.state.set_import_resolver(CallbackImportResolver { + vm.replace_import_resolver(CallbackImportResolver { cb, ctx, out: RefCell::new(HashMap::new()), @@ -131,10 +135,5 @@ pub unsafe extern "C" fn jsonnet_jpath_add(vm: &VM, path: *const c_char) { let cstr = unsafe { CStr::from_ptr(path) }; let path = PathBuf::from(cstr.to_str().unwrap()); - let any_resolver = vm.state.import_resolver(); - let resolver = any_resolver - .as_any() - .downcast_ref::() - .expect("jpaths are not compatible with callback imports!"); - resolver.add_jpath(path); + vm.add_jpath(path); } --- a/bindings/jsonnet/src/interop.rs +++ b/bindings/jsonnet/src/interop.rs @@ -1,53 +1,156 @@ //! Jrsonnet specific additional binding helpers -use std::{ - ffi::c_void, - os::raw::{c_char, c_int}, -}; +use crate::VM; + +#[cfg(feature = "interop-wasm")] +pub mod wasm { + use std::ffi::{c_char, c_int, c_void}; + + use jrsonnet_evaluator::Val; + + use crate::VM; + + extern "C" { + + pub fn _jrsonnet_static_import_callback( + ctx: *mut c_void, + base: *const c_char, + rel: *const c_char, + found_here: *mut *const c_char, + buf: *mut *mut c_char, + buflen: *mut usize, + ) -> c_int; -use jrsonnet_evaluator::Val; + #[allow(improper_ctypes)] + pub fn _jrsonnet_static_native_callback( + ctx: *const c_void, + argv: *const *const Val, + success: *mut c_int, + ) -> *mut Val; + } -use crate::{import::jsonnet_import_callback, native::jsonnet_native_callback}; + #[no_mangle] + #[cfg(feature = "interop-wasm")] + // ctx arg is passed as-is to callback + #[allow(clippy::not_unsafe_ptr_arg_deref)] + pub extern "C" fn jrsonnet_apply_static_import_callback(vm: &VM, ctx: *mut c_void) { + unsafe { crate::import::jsonnet_import_callback(vm, _jrsonnet_static_import_callback, ctx) } + } -extern "C" { - pub fn _jrsonnet_static_import_callback( + /// # Safety + /// + /// `name` and `raw_params` should be correctly initialized + #[no_mangle] + #[cfg(feature = "interop-wasm")] + pub unsafe extern "C" fn jrsonnet_apply_static_native_callback( + vm: &VM, + name: *const c_char, ctx: *mut c_void, - base: *const c_char, - rel: *const c_char, - found_here: *mut *const c_char, - success: &mut c_int, - ) -> *const c_char; - - #[allow(improper_ctypes)] - pub fn _jrsonnet_static_native_callback( - ctx: *const c_void, - argv: *const *const Val, - success: *mut c_int, - ) -> *mut Val; + raw_params: *const *const c_char, + ) { + unsafe { + crate::native::jsonnet_native_callback( + vm, + name, + _jrsonnet_static_native_callback, + ctx, + raw_params, + ); + } + } } -/// # Safety -#[no_mangle] -pub unsafe extern "C" fn jrsonnet_apply_static_import_callback(vm: &VM, ctx: *mut c_void) { - jsonnet_import_callback(vm, _jrsonnet_static_import_callback, ctx) -} +#[cfg(feature = "interop-common")] +mod common { + use jrsonnet_evaluator::trace::{CompactFormat, ExplainingFormat, JsFormat, PathResolver}; -/// # Safety -#[no_mangle] -pub unsafe extern "C" fn jrsonnet_apply_static_native_callback( - vm: &VM, - name: *const c_char, - ctx: *mut c_void, - raw_params: *const *const c_char, -) { - jsonnet_native_callback(vm, name, _jrsonnet_static_native_callback, ctx, raw_params) + use crate::VM; + + #[no_mangle] + pub extern "C" fn jrsonnet_set_trace_format(vm: &mut VM, format: u8) { + match format { + 0 => { + vm.trace_format = Box::new(CompactFormat { + max_trace: 20, + resolver: PathResolver::new_cwd_fallback(), + padding: 4, + }); + } + 1 => vm.trace_format = Box::new(JsFormat { max_trace: 20 }), + 2 => { + vm.trace_format = Box::new(ExplainingFormat { + resolver: PathResolver::new_cwd_fallback(), + max_trace: 20, + }); + } + _ => panic!("unknown trace format"), + } + } } -#[no_mangle] -pub extern "C" fn jrsonnet_set_trace_format(vm: &VM, format: u8) { - use jrsonnet_evaluator::trace::JsFormat; - match format { - 1 => vm.set_trace_format(Box::new(JsFormat)), - _ => panic!("unknown trace format"), +#[cfg(feature = "interop-threading")] +mod threading { + use std::{ffi::c_int, thread::ThreadId}; + + pub struct ThreadCTX { + interner: *mut jrsonnet_interner::interop::PoolState, + gc: *mut jrsonnet_gcmodule::interop::GcState, + } + + /// Golang jrsonnet bindings require Jsonnet VM to be movable. + /// Jrsonnet uses `thread_local` in some places, thus making VM + /// immovable by default. By using `jrsonnet_exit_thread` and + /// `jrsonnet_reenter_thread`, you can move `thread_local` state to + /// where it is more convinient to use it. + /// + /// # Safety + /// + /// Current thread GC will be broken after this call, need to call + /// `jrsonet_enter_thread` before doing anything. + #[no_mangle] + pub unsafe extern "C" fn jrsonnet_exit_thread() -> *mut ThreadCTX { + Box::into_raw(Box::new(ThreadCTX { + interner: jrsonnet_interner::interop::exit_thread(), + gc: unsafe { jrsonnet_gcmodule::interop::exit_thread() }, + })) + } + + #[no_mangle] + pub extern "C" fn jrsonnet_reenter_thread(mut ctx: Box) { + use std::ptr::null_mut; + assert!( + !ctx.interner.is_null() && !ctx.gc.is_null(), + "reused context?" + ); + unsafe { jrsonnet_interner::interop::reenter_thread(ctx.interner) } + unsafe { jrsonnet_gcmodule::interop::reenter_thread(ctx.gc) } + // Just in case + ctx.interner = null_mut(); + ctx.gc = null_mut(); + } + + // ThreadId is compatible with u64, and there is unstable cast + // method... But until it is stabilized, lets erase its type by + // boxing. + pub enum JrThreadId {} + + #[no_mangle] + pub extern "C" fn jrsonnet_thread_id() -> *mut JrThreadId { + Box::into_raw(Box::new(std::thread::current().id())).cast() + } + + #[no_mangle] + pub extern "C" fn jrsonnet_thread_id_compare( + a: *const JrThreadId, + b: *const JrThreadId, + ) -> c_int { + let a: &ThreadId = unsafe { *a.cast() }; + let b: &ThreadId = unsafe { *b.cast() }; + i32::from(*a == *b) + } + + #[no_mangle] + pub unsafe extern "C" fn jrsonnet_thread_id_free(id: *mut JrThreadId) { + let _id: Box = unsafe { Box::from_raw(id.cast()) }; } } --- a/bindings/jsonnet/src/lib.rs +++ b/bindings/jsonnet/src/lib.rs @@ -1,6 +1,5 @@ #![allow(clippy::box_default)] -#[cfg(feature = "interop")] pub mod interop; pub mod import; @@ -12,22 +11,27 @@ use std::{ alloc::Layout, + any::Any, borrow::Cow, + cell::RefCell, ffi::{CStr, CString, OsStr}, os::raw::{c_char, c_double, c_int, c_uint}, - path::Path, + path::{Path, PathBuf}, }; use jrsonnet_evaluator::{ apply_tla, bail, function::TlaArg, - gc::GcHashMap, + gc::{GcHashMap, TraceBox}, manifest::{JsonFormat, ManifestFormat, ToStringFormat}, stack::set_stack_depth_limit, tb, trace::{CompactFormat, PathResolver, TraceFormat}, - FileImportResolver, IStr, Result, State, Val, + FileImportResolver, IStr, ImportResolver, Result, State, Val, }; +use jrsonnet_gcmodule::Trace; +use jrsonnet_parser::SourcePath; +use jrsonnet_stdlib::ContextInitializer; /// WASM stub #[cfg(target_arch = "wasm32")] @@ -72,22 +76,84 @@ } } +#[derive(Trace)] +struct VMImportResolver { + #[trace(tracking(force))] + inner: RefCell>, +} +impl VMImportResolver { + fn new(value: impl ImportResolver) -> Self { + Self { + inner: RefCell::new(tb!(value)), + } + } +} +impl ImportResolver for VMImportResolver { + fn load_file_contents(&self, resolved: &SourcePath) -> Result> { + self.inner.borrow().load_file_contents(resolved) + } + + fn resolve_from(&self, from: &SourcePath, path: &str) -> Result { + self.inner.borrow().resolve_from(from, path) + } + + fn resolve_from_default(&self, path: &str) -> Result { + self.inner.borrow().resolve_from_default(path) + } + + fn resolve(&self, path: &Path) -> Result { + self.inner.borrow().resolve(path) + } + + fn as_any(&self) -> &dyn Any { + self + } + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} + pub struct VM { state: State, manifest_format: Box, trace_format: Box, tla_args: GcHashMap, } +impl VM { + fn replace_import_resolver(&self, resolver: impl ImportResolver) { + *self + .state + .import_resolver() + .as_any() + .downcast_ref::() + .expect("valid resolver ty") + .inner + .borrow_mut() = tb!(resolver); + } + fn add_jpath(&self, path: PathBuf) { + self.state + .import_resolver() + .as_any() + .downcast_ref::() + .expect("valid resolver ty") + .inner + .borrow_mut() + .as_any_mut() + .downcast_mut::() + .expect("jpaths are not compatible with callback imports!") + .add_jpath(path); + } +} /// Creates a new Jsonnet virtual machine. #[no_mangle] #[allow(clippy::box_default)] pub extern "C" fn jsonnet_make() -> *mut VM { - let state = State::default(); - state.settings_mut().import_resolver = tb!(FileImportResolver::default()); - state.settings_mut().context_initializer = tb!(jrsonnet_stdlib::ContextInitializer::new( - PathResolver::new_cwd_fallback(), - )); + let mut state = State::builder(); + state + .import_resolver(VMImportResolver::new(FileImportResolver::default())) + .context_initializer(ContextInitializer::new(PathResolver::new_cwd_fallback())); + let state = state.build(); Box::into_raw(Box::new(VM { state, manifest_format: Box::new(JsonFormat::default()),