From 73eabcc7c32e854e0520245307b5a89b016987fb Mon Sep 17 00:00:00 2001 From: Paul Masurel Date: Tue, 18 Aug 2026 14:06:38 +0200 Subject: [PATCH] only reset string arena when it is used --- jitexpr/src/compile/compile_fn_builder.rs | 1 + jitexpr/src/compile/compiled_fn.rs | 1 - jitexpr/src/compile/mod.rs | 15 ++++++++++++++- jitexpr/src/compile/string_arena.rs | 6 ++---- jitexpr/src/functions/lower.rs | 15 ++++++++------- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/jitexpr/src/compile/compile_fn_builder.rs b/jitexpr/src/compile/compile_fn_builder.rs index f88e6127b..e6274132c 100644 --- a/jitexpr/src/compile/compile_fn_builder.rs +++ b/jitexpr/src/compile/compile_fn_builder.rs @@ -218,6 +218,7 @@ impl<'types, 'names> CompileFnBuilder<'types, 'names> { let mut lowering_context = LoweringContext { args_ptr, string_arena_ptr, + string_arena_was_reset: false, pointer_type, native_functions: &native_functions, }; diff --git a/jitexpr/src/compile/compiled_fn.rs b/jitexpr/src/compile/compiled_fn.rs index b5e5eb28c..19d3de462 100644 --- a/jitexpr/src/compile/compiled_fn.rs +++ b/jitexpr/src/compile/compiled_fn.rs @@ -64,7 +64,6 @@ impl CompiledFn { { debug_assert_eq!(args.len(), self.inputs.len()); let args: &[VariableValue<'output>] = args; - self.string_arena.clear(); let string_arena = &raw mut self.string_arena; // SAFETY: Guaranteed by the caller. Both the input and compiled-function // lifetimes outlive the lifetime selected for the returned value. diff --git a/jitexpr/src/compile/mod.rs b/jitexpr/src/compile/mod.rs index e252e2f46..ceb7ea9dc 100644 --- a/jitexpr/src/compile/mod.rs +++ b/jitexpr/src/compile/mod.rs @@ -45,6 +45,7 @@ pub fn compile_to_assembly( pub(crate) struct LoweringContext<'a> { args_ptr: CraneliftValue, string_arena_ptr: CraneliftValue, + string_arena_was_reset: bool, pointer_type: Type, native_functions: &'a NativeFunctions, } @@ -122,7 +123,17 @@ impl LoweringContext<'_> { self.pointer_type } - pub(crate) fn string_arena_ptr(&self) -> CraneliftValue { + pub(crate) fn string_arena_ptr(&mut self, builder: &mut FunctionBuilder<'_>) -> CraneliftValue { + if !self.string_arena_was_reset { + let zero = builder.ins().iconst(cranelift_types::I64, 0); + builder.ins().store( + MemFlagsData::trusted(), + zero, + self.string_arena_ptr, + StringArena::CURSOR_OFFSET, + ); + self.string_arena_was_reset = true; + } self.string_arena_ptr } @@ -217,10 +228,12 @@ mod tests { let untyped_expr = UntypedExpr::variable("flag"); let variable_types = HashMap::from([("flag", VarType::Bool)]); let mut compiled_fn = compile(&untyped_expr, &variable_types).unwrap(); + assert!(compiled_fn.string_arena.allocate(1).is_some()); let input = [VariableValue::some(true)]; let output = unsafe { compiled_fn.call(&input) }; assert_eq!(unsafe { output.as_bool() }, Some(true)); + assert_eq!(compiled_fn.string_arena.used_bytes(), 1); } #[test] diff --git a/jitexpr/src/compile/string_arena.rs b/jitexpr/src/compile/string_arena.rs index 48611da6d..828ad8b28 100644 --- a/jitexpr/src/compile/string_arena.rs +++ b/jitexpr/src/compile/string_arena.rs @@ -7,6 +7,8 @@ pub(crate) struct StringArena { } impl StringArena { + pub(crate) const CURSOR_OFFSET: i32 = std::mem::offset_of!(StringArena, cursor) as i32; + pub(crate) fn new() -> Self { let buffer = vec![0; STRING_ARENA_CAPACITY].into_boxed_slice(); let buffer = buffer @@ -15,10 +17,6 @@ impl StringArena { Self { buffer, cursor: 0 } } - pub(crate) fn clear(&mut self) { - self.cursor = 0; - } - /// Reserves `len` contiguous bytes without growing the backing allocation. pub(crate) fn allocate(&mut self, len: usize) -> Option<*mut u8> { let end = self.cursor.checked_add(len)?; diff --git a/jitexpr/src/functions/lower.rs b/jitexpr/src/functions/lower.rs index 9942aa136..dfbc0d2af 100644 --- a/jitexpr/src/functions/lower.rs +++ b/jitexpr/src/functions/lower.rs @@ -78,9 +78,11 @@ impl FnCall for LowerFnCall { let arg = context.compile_expr(&self.arg, builder)?; let null = builder.ins().iconst(context.pointer_type(), 0); let input_ptr = builder.ins().select(arg.is_present, arg.value, null); + let string_lowercase = context.native_functions().string_lowercase(); + let string_arena_ptr = context.string_arena_ptr(builder); let call = builder.ins().call( - context.native_functions().string_lowercase(), - &[input_ptr, arg.string_len, context.string_arena_ptr()], + string_lowercase, + &[input_ptr, arg.string_len, string_arena_ptr], ); let value = builder.inst_results(call)[0]; let string_len = builder.inst_results(call)[1]; @@ -265,9 +267,7 @@ mod tests { let mut compiled = compile(&expression, &variable_types).unwrap(); let input = [VariableValue::some("HeLLo")]; - let output = unsafe { compiled.call(&input) }; - - assert_eq!(unsafe { output.as_str() }, Some("hello")); + assert_eq!(unsafe { compiled.call(&input).as_str() }, Some("hello")); } #[test] @@ -275,11 +275,12 @@ mod tests { let expression = deserialize("(EQ (LOWER left) (LOWER right))").unwrap(); let variable_types = HashMap::from([("left", VarType::Str), ("right", VarType::Str)]); let mut compiled = compile(&expression, &variable_types).unwrap(); - let input = [VariableValue::some("FiRsT"), VariableValue::some("fIrSt")]; + let input = [VariableValue::some("FiRsT"), VariableValue::some("SeCoNd")]; let output = unsafe { compiled.call(&input) }; - assert_eq!(unsafe { output.as_bool() }, Some(true)); + assert_eq!(unsafe { output.as_bool() }, Some(false)); + assert_eq!(compiled.string_arena.used_bytes(), 11); } #[test]