@@ -18,6 +18,7 @@ use alloc::vec::Vec;
18
18
19
19
use flatbuffers:: FlatBufferBuilder ;
20
20
21
+ use crate :: flatbuffer_wrappers:: function_types:: ParameterValue ;
21
22
use crate :: flatbuffers:: hyperlight:: generated:: {
22
23
FunctionCallResult as FbFunctionCallResult , FunctionCallResultArgs as FbFunctionCallResultArgs ,
23
24
ReturnValue as FbReturnValue , hlbool as Fbhlbool , hlboolArgs as FbhlboolArgs ,
@@ -169,3 +170,350 @@ impl FlatbufferSerializable for bool {
169
170
}
170
171
}
171
172
}
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