@@ -18,6 +18,7 @@ use alloc::vec::Vec;
1818
1919use flatbuffers:: FlatBufferBuilder ;
2020
21+ use crate :: flatbuffer_wrappers:: function_types:: ParameterValue ;
2122use crate :: flatbuffers:: hyperlight:: generated:: {
2223 FunctionCallResult as FbFunctionCallResult , FunctionCallResultArgs as FbFunctionCallResultArgs ,
2324 ReturnValue as FbReturnValue , hlbool as Fbhlbool , hlboolArgs as FbhlboolArgs ,
@@ -169,3 +170,350 @@ impl FlatbufferSerializable for bool {
169170 }
170171 }
171172}
173+
174+ /// Estimates the required buffer capacity for encoding a FunctionCall with the given parameters.
175+ /// This helps avoid reallocation during FlatBuffer encoding when passing large slices and strings.
176+ ///
177+ /// The function aims to be lightweight and fast and run in O(1) as long as the number of parameters is limited
178+ /// (which it is since hyperlight only currently supports up to 12).
179+ ///
180+ /// Note: This estimates the capacity needed for the inner vec inside a FlatBufferBuilder. It does not
181+ /// necessarily match the size of the final encoded buffer. The estimation always rounds up to the
182+ /// nearest power of two to match FlatBufferBuilder's allocation strategy.
183+ ///
184+ /// The estimations are numbers used are empirically derived based on the tests below and vaguely based
185+ /// on https://flatbuffers.dev/internals/ and https://github.com/dvidelabs/flatcc/blob/master/doc/binary-format.md#flatbuffers-binary-format
186+ #[ inline] // allow cross-crate inlining (for hyperlight-host calls)
187+ pub fn estimate_flatbuffer_capacity ( function_name : & str , args : & [ ParameterValue ] ) -> usize {
188+ let mut estimated_capacity = 20 ;
189+
190+ // Function name overhead
191+ estimated_capacity += function_name. len ( ) + 12 ;
192+
193+ // Parameters vector overhead
194+ estimated_capacity += 12 + args. len ( ) * 6 ;
195+
196+ // Per-parameter overhead
197+ for arg in args {
198+ estimated_capacity += 16 ; // Base parameter structure
199+ estimated_capacity += match arg {
200+ ParameterValue :: String ( s) => s. len ( ) + 20 ,
201+ ParameterValue :: VecBytes ( v) => v. len ( ) + 20 ,
202+ ParameterValue :: Int ( _) | ParameterValue :: UInt ( _) => 16 ,
203+ ParameterValue :: Long ( _) | ParameterValue :: ULong ( _) => 20 ,
204+ ParameterValue :: Float ( _) => 16 ,
205+ ParameterValue :: Double ( _) => 20 ,
206+ ParameterValue :: Bool ( _) => 12 ,
207+ } ;
208+ }
209+
210+ // match how vec grows
211+ estimated_capacity. next_power_of_two ( )
212+ }
213+
214+ #[ cfg( test) ]
215+ mod tests {
216+ use alloc:: string:: ToString ;
217+ use alloc:: vec;
218+ use alloc:: vec:: Vec ;
219+
220+ use super :: * ;
221+ use crate :: flatbuffer_wrappers:: function_call:: { FunctionCall , FunctionCallType } ;
222+ use crate :: flatbuffer_wrappers:: function_types:: { ParameterValue , ReturnType } ;
223+
224+ /// Helper function to check that estimation is within reasonable bounds (±25%)
225+ fn assert_estimation_accuracy (
226+ function_name : & str ,
227+ args : Vec < ParameterValue > ,
228+ call_type : FunctionCallType ,
229+ return_type : ReturnType ,
230+ ) {
231+ let estimated = estimate_flatbuffer_capacity ( function_name, & args) ;
232+
233+ let fc = FunctionCall :: new (
234+ function_name. to_string ( ) ,
235+ Some ( args) ,
236+ call_type. clone ( ) ,
237+ return_type,
238+ ) ;
239+ // Important that this FlatBufferBuilder is created with capacity 0 so it grows to its needed capacity
240+ let mut builder = FlatBufferBuilder :: new ( ) ;
241+ let _buffer = fc. encode ( & mut builder) ;
242+ let actual = builder. collapse ( ) . 0 . capacity ( ) ;
243+
244+ let lower_bound = ( actual as f64 * 0.75 ) as usize ;
245+ let upper_bound = ( actual as f64 * 1.25 ) as usize ;
246+
247+ assert ! (
248+ estimated >= lower_bound && estimated <= upper_bound,
249+ "Estimation {} outside bounds [{}, {}] for actual size {} (function: {}, call_type: {:?}, return_type: {:?})" ,
250+ estimated,
251+ lower_bound,
252+ upper_bound,
253+ actual,
254+ function_name,
255+ call_type,
256+ return_type
257+ ) ;
258+ }
259+
260+ #[ test]
261+ fn test_estimate_no_parameters ( ) {
262+ assert_estimation_accuracy (
263+ "simple_function" ,
264+ vec ! [ ] ,
265+ FunctionCallType :: Guest ,
266+ ReturnType :: Void ,
267+ ) ;
268+ }
269+
270+ #[ test]
271+ fn test_estimate_single_int_parameter ( ) {
272+ assert_estimation_accuracy (
273+ "add_one" ,
274+ vec ! [ ParameterValue :: Int ( 42 ) ] ,
275+ FunctionCallType :: Guest ,
276+ ReturnType :: Int ,
277+ ) ;
278+ }
279+
280+ #[ test]
281+ fn test_estimate_multiple_scalar_parameters ( ) {
282+ assert_estimation_accuracy (
283+ "calculate" ,
284+ vec ! [
285+ ParameterValue :: Int ( 10 ) ,
286+ ParameterValue :: UInt ( 20 ) ,
287+ ParameterValue :: Long ( 30 ) ,
288+ ParameterValue :: ULong ( 40 ) ,
289+ ParameterValue :: Float ( 1.5 ) ,
290+ ParameterValue :: Double ( 2.5 ) ,
291+ ParameterValue :: Bool ( true ) ,
292+ ] ,
293+ FunctionCallType :: Guest ,
294+ ReturnType :: Double ,
295+ ) ;
296+ }
297+
298+ #[ test]
299+ fn test_estimate_string_parameters ( ) {
300+ assert_estimation_accuracy (
301+ "process_strings" ,
302+ vec ! [
303+ ParameterValue :: String ( "hello" . to_string( ) ) ,
304+ ParameterValue :: String ( "world" . to_string( ) ) ,
305+ ParameterValue :: String ( "this is a longer string for testing" . to_string( ) ) ,
306+ ] ,
307+ FunctionCallType :: Host ,
308+ ReturnType :: String ,
309+ ) ;
310+ }
311+
312+ #[ test]
313+ fn test_estimate_very_long_string ( ) {
314+ let long_string = "a" . repeat ( 1000 ) ;
315+ assert_estimation_accuracy (
316+ "process_long_string" ,
317+ vec ! [ ParameterValue :: String ( long_string) ] ,
318+ FunctionCallType :: Guest ,
319+ ReturnType :: String ,
320+ ) ;
321+ }
322+
323+ #[ test]
324+ fn test_estimate_vector_parameters ( ) {
325+ assert_estimation_accuracy (
326+ "process_vectors" ,
327+ vec ! [
328+ ParameterValue :: VecBytes ( vec![ 1 , 2 , 3 , 4 , 5 ] ) ,
329+ ParameterValue :: VecBytes ( vec![ ] ) ,
330+ ParameterValue :: VecBytes ( vec![ 0 ; 100 ] ) ,
331+ ] ,
332+ FunctionCallType :: Host ,
333+ ReturnType :: VecBytes ,
334+ ) ;
335+ }
336+
337+ #[ test]
338+ fn test_estimate_mixed_parameters ( ) {
339+ assert_estimation_accuracy (
340+ "complex_function" ,
341+ vec ! [
342+ ParameterValue :: String ( "test" . to_string( ) ) ,
343+ ParameterValue :: Int ( 42 ) ,
344+ ParameterValue :: VecBytes ( vec![ 1 , 2 , 3 , 4 , 5 ] ) ,
345+ ParameterValue :: Bool ( true ) ,
346+ ParameterValue :: Double ( 553.14159 ) ,
347+ ParameterValue :: String ( "another string" . to_string( ) ) ,
348+ ParameterValue :: Long ( 9223372036854775807 ) ,
349+ ] ,
350+ FunctionCallType :: Guest ,
351+ ReturnType :: VecBytes ,
352+ ) ;
353+ }
354+
355+ #[ test]
356+ fn test_estimate_large_function_name ( ) {
357+ let long_name = "very_long_function_name_that_exceeds_normal_lengths_for_testing_purposes" ;
358+ assert_estimation_accuracy (
359+ long_name,
360+ vec ! [ ParameterValue :: Int ( 1 ) ] ,
361+ FunctionCallType :: Host ,
362+ ReturnType :: Long ,
363+ ) ;
364+ }
365+
366+ #[ test]
367+ fn test_estimate_large_vector ( ) {
368+ let large_vector = vec ! [ 42u8 ; 10000 ] ;
369+ assert_estimation_accuracy (
370+ "process_large_data" ,
371+ vec ! [ ParameterValue :: VecBytes ( large_vector) ] ,
372+ FunctionCallType :: Guest ,
373+ ReturnType :: Bool ,
374+ ) ;
375+ }
376+
377+ #[ test]
378+ fn test_estimate_all_parameter_types ( ) {
379+ assert_estimation_accuracy (
380+ "comprehensive_test" ,
381+ vec ! [
382+ ParameterValue :: Int ( i32 :: MIN ) ,
383+ ParameterValue :: UInt ( u32 :: MAX ) ,
384+ ParameterValue :: Long ( i64 :: MIN ) ,
385+ ParameterValue :: ULong ( u64 :: MAX ) ,
386+ ParameterValue :: Float ( f32 :: MIN ) ,
387+ ParameterValue :: Double ( f64 :: MAX ) ,
388+ ParameterValue :: Bool ( false ) ,
389+ ParameterValue :: String ( "test string" . to_string( ) ) ,
390+ ParameterValue :: VecBytes ( vec![ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] ) ,
391+ ] ,
392+ FunctionCallType :: Host ,
393+ ReturnType :: ULong ,
394+ ) ;
395+ }
396+
397+ #[ test]
398+ fn test_different_function_call_types ( ) {
399+ assert_estimation_accuracy (
400+ "guest_function" ,
401+ vec ! [ ParameterValue :: String ( "guest call" . to_string( ) ) ] ,
402+ FunctionCallType :: Guest ,
403+ ReturnType :: String ,
404+ ) ;
405+
406+ assert_estimation_accuracy (
407+ "host_function" ,
408+ vec ! [ ParameterValue :: String ( "host call" . to_string( ) ) ] ,
409+ FunctionCallType :: Host ,
410+ ReturnType :: String ,
411+ ) ;
412+ }
413+
414+ #[ test]
415+ fn test_different_return_types ( ) {
416+ let args = vec ! [
417+ ParameterValue :: Int ( 42 ) ,
418+ ParameterValue :: String ( "test" . to_string( ) ) ,
419+ ] ;
420+
421+ let void_est = estimate_flatbuffer_capacity ( "test_void" , & args) ;
422+ let int_est = estimate_flatbuffer_capacity ( "test_int" , & args) ;
423+ let string_est = estimate_flatbuffer_capacity ( "test_string" , & args) ;
424+
425+ assert ! ( ( void_est as i32 - int_est as i32 ) . abs( ) < 10 ) ;
426+ assert ! ( ( int_est as i32 - string_est as i32 ) . abs( ) < 10 ) ;
427+
428+ assert_estimation_accuracy (
429+ "test_void" ,
430+ args. clone ( ) ,
431+ FunctionCallType :: Guest ,
432+ ReturnType :: Void ,
433+ ) ;
434+ assert_estimation_accuracy (
435+ "test_int" ,
436+ args. clone ( ) ,
437+ FunctionCallType :: Guest ,
438+ ReturnType :: Int ,
439+ ) ;
440+ assert_estimation_accuracy (
441+ "test_string" ,
442+ args,
443+ FunctionCallType :: Guest ,
444+ ReturnType :: String ,
445+ ) ;
446+ }
447+
448+ #[ test]
449+ fn test_estimate_many_large_vectors_and_strings ( ) {
450+ assert_estimation_accuracy (
451+ "process_bulk_data" ,
452+ vec ! [
453+ ParameterValue :: String ( "Large string data: " . to_string( ) + & "x" . repeat( 2000 ) ) ,
454+ ParameterValue :: VecBytes ( vec![ 1u8 ; 5000 ] ) ,
455+ ParameterValue :: String (
456+ "Another large string with lots of content " . to_string( ) + & "y" . repeat( 3000 ) ,
457+ ) ,
458+ ParameterValue :: VecBytes ( vec![ 255u8 ; 7500 ] ) ,
459+ ParameterValue :: String (
460+ "Third massive string parameter " . to_string( ) + & "z" . repeat( 1500 ) ,
461+ ) ,
462+ ParameterValue :: VecBytes ( vec![ 128u8 ; 10000 ] ) ,
463+ ParameterValue :: Int ( 42 ) ,
464+ ParameterValue :: String ( "Final large string " . to_string( ) + & "a" . repeat( 4000 ) ) ,
465+ ParameterValue :: VecBytes ( vec![ 64u8 ; 2500 ] ) ,
466+ ParameterValue :: Bool ( true ) ,
467+ ] ,
468+ FunctionCallType :: Host ,
469+ ReturnType :: VecBytes ,
470+ ) ;
471+ }
472+
473+ #[ test]
474+ fn test_estimate_twenty_parameters ( ) {
475+ assert_estimation_accuracy (
476+ "function_with_many_parameters" ,
477+ vec ! [
478+ ParameterValue :: Int ( 1 ) ,
479+ ParameterValue :: String ( "param2" . to_string( ) ) ,
480+ ParameterValue :: Bool ( true ) ,
481+ ParameterValue :: Float ( 3213.14 ) ,
482+ ParameterValue :: VecBytes ( vec![ 1 , 2 , 3 ] ) ,
483+ ParameterValue :: Long ( 1000000 ) ,
484+ ParameterValue :: Double ( 322.718 ) ,
485+ ParameterValue :: UInt ( 42 ) ,
486+ ParameterValue :: String ( "param9" . to_string( ) ) ,
487+ ParameterValue :: Bool ( false ) ,
488+ ParameterValue :: ULong ( 9999999999 ) ,
489+ ParameterValue :: VecBytes ( vec![ 4 , 5 , 6 , 7 , 8 ] ) ,
490+ ParameterValue :: Int ( -100 ) ,
491+ ParameterValue :: Float ( 1.414 ) ,
492+ ParameterValue :: String ( "param15" . to_string( ) ) ,
493+ ParameterValue :: Double ( 1.732 ) ,
494+ ParameterValue :: Bool ( true ) ,
495+ ParameterValue :: VecBytes ( vec![ 9 , 10 ] ) ,
496+ ParameterValue :: Long ( -5000000 ) ,
497+ ParameterValue :: UInt ( 12345 ) ,
498+ ] ,
499+ FunctionCallType :: Guest ,
500+ ReturnType :: Int ,
501+ ) ;
502+ }
503+
504+ #[ test]
505+ fn test_estimate_megabyte_parameters ( ) {
506+ assert_estimation_accuracy (
507+ "process_megabyte_data" ,
508+ vec ! [
509+ ParameterValue :: String ( "MB String 1: " . to_string( ) + & "x" . repeat( 1_048_576 ) ) , // 1MB string
510+ ParameterValue :: VecBytes ( vec![ 42u8 ; 2_097_152 ] ) , // 2MB vector
511+ ParameterValue :: String ( "MB String 2: " . to_string( ) + & "y" . repeat( 1_572_864 ) ) , // 1.5MB string
512+ ParameterValue :: VecBytes ( vec![ 128u8 ; 3_145_728 ] ) , // 3MB vector
513+ ParameterValue :: String ( "MB String 3: " . to_string( ) + & "z" . repeat( 2_097_152 ) ) , // 2MB string
514+ ] ,
515+ FunctionCallType :: Host ,
516+ ReturnType :: VecBytes ,
517+ ) ;
518+ }
519+ }
0 commit comments