git.delta.rocks / jrsonnet / refs/commits / c6e9f5b704f3

difftreelog

refactor(bindings) extra interop methods

Yaroslav Bolyukin2024-05-28parent: #7b38a7f.patch.diff
in: master

5 files changed

modifiedbindings/c/libjsonnet_test_file.cdiffbeforeafterboth
1/*
2Copyright 2015 Google Inc. All rights reserved.
3Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6 http://www.apache.org/licenses/LICENSE-2.0
7Unless required by applicable law or agreed to in writing, software
8distributed under the License is distributed on an "AS IS" BASIS,
9WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10See the License for the specific language governing permissions and
11limitations under the License.
12*/
13
14#include <stdlib.h>1#include <stdlib.h>
15#include <stdio.h>2#include <stdio.h>
modifiedbindings/jsonnet/Cargo.tomldiffbeforeafterboth
--- 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"]
modifiedbindings/jsonnet/src/import.rsdiffbeforeafterboth
--- 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::<FileImportResolver>()
-		.expect("jpaths are not compatible with callback imports!");
-	resolver.add_jpath(path);
+	vm.add_jpath(path);
 }
modifiedbindings/jsonnet/src/interop.rsdiffbeforeafterboth
--- 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<ThreadCTX>) {
+		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<ThreadId> = unsafe { Box::from_raw(id.cast()) };
 	}
 }
modifiedbindings/jsonnet/src/lib.rsdiffbeforeafterboth
--- 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<TraceBox<dyn ImportResolver>>,
+}
+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<Vec<u8>> {
+		self.inner.borrow().load_file_contents(resolved)
+	}
+
+	fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {
+		self.inner.borrow().resolve_from(from, path)
+	}
+
+	fn resolve_from_default(&self, path: &str) -> Result<SourcePath> {
+		self.inner.borrow().resolve_from_default(path)
+	}
+
+	fn resolve(&self, path: &Path) -> Result<SourcePath> {
+		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<dyn ManifestFormat>,
 	trace_format: Box<dyn TraceFormat>,
 	tla_args: GcHashMap<IStr, TlaArg>,
 }
+impl VM {
+	fn replace_import_resolver(&self, resolver: impl ImportResolver) {
+		*self
+			.state
+			.import_resolver()
+			.as_any()
+			.downcast_ref::<VMImportResolver>()
+			.expect("valid resolver ty")
+			.inner
+			.borrow_mut() = tb!(resolver);
+	}
+	fn add_jpath(&self, path: PathBuf) {
+		self.state
+			.import_resolver()
+			.as_any()
+			.downcast_ref::<VMImportResolver>()
+			.expect("valid resolver ty")
+			.inner
+			.borrow_mut()
+			.as_any_mut()
+			.downcast_mut::<FileImportResolver>()
+			.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()),