polyplug 0.1.1

Universal high-performance zero-overhead cross-language plugin runtime
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
#![allow(clippy::expect_used)]

// THIS IS A BENCHMARK FILE — do not add #[test] functions here
// Run with: cargo bench -p polyplug --bench contract_dispatch

use core::cell::RefCell;
use core::hint::black_box;

use criterion::BenchmarkId;
use criterion::Criterion;
use criterion::Throughput;
use criterion::criterion_group;
use criterion::criterion_main;

use polyplug::runtime_store::RuntimeStore;
use polyplug_abi::AbiError;
use polyplug_abi::AbiErrorCode;
use polyplug_abi::Array;
use polyplug_abi::Buffer;
use polyplug_abi::DispatchType;
use polyplug_abi::GuestContractHandle;
use polyplug_abi::GuestContractInstance;
use polyplug_abi::GuestContractInterface;
use polyplug_abi::HostApi;
use polyplug_abi::PluginDescriptor;
use polyplug_abi::StringView;
use polyplug_abi::ffi::polyplug_host_alloc;
use polyplug_abi::ffi::polyplug_host_free;
use polyplug_utils::BundleId;

// ─── Plugin paths from build.rs ──────────────────────────────────────────────

const TEST_PLUGIN_SO: &str = env!("TEST_PLUGIN_SO");
const MEMORY_PLUGIN_SO: &str = env!("MEMORY_PLUGIN_SO");
#[allow(dead_code)]
const ERROR_PLUGIN_SO: &str = env!("ERROR_PLUGIN_SO");

// ─── Shared argument structs ─────────────────────────────────────────────────

#[repr(C)]
struct AddArgs {
    a: u32,
    b: u32,
}

#[repr(C)]
struct FillArgs {
    buf: Buffer,
    fill_byte: u8,
}

// ─── Thread-local registry and captured interface state ────────────────────────

thread_local! {
    static BENCH_REGISTRY: RefCell<Option<RuntimeStore>> = RefCell::new(Some(RuntimeStore::new()));
    static LAST_INTERFACE: core::cell::Cell<*const GuestContractInterface> = const { core::cell::Cell::new(core::ptr::null()) };
    static LAST_CONTRACT_ID: core::cell::Cell<u64> = const { core::cell::Cell::new(0) };
}

/// Registration callback used by all benchmarks.
///
/// # Safety
/// `descriptor` and `interface` must be valid pointers for the call duration.
/// `out_err` must be non-null and writable.
unsafe extern "C" fn bench_register_callback(
    _this: *const HostApi,
    descriptor: *const PluginDescriptor,
    interface: *const GuestContractInterface,
    out_err: *mut AbiError,
) {
    if descriptor.is_null() || interface.is_null() {
        if !out_err.is_null() {
            // SAFETY: out_err is non-null (just checked) and writable per the ABI contract.
            unsafe {
                out_err.write(AbiError {
                    code: AbiErrorCode::Generic as u32,
                    message: StringView::null(),
                })
            };
        }
        return;
    }

    // SAFETY: descriptor and interface are valid for this call per ABI contract.
    let desc: &PluginDescriptor = unsafe { &*descriptor };
    // SAFETY: interface is valid for this call per ABI contract.
    let iface: &GuestContractInterface = unsafe { &*interface };

    // SAFETY: desc.contract_name is set from a &'static str in the benchmark fixture.
    // The bytes are valid UTF-8 by construction.
    let contract_name: &str = unsafe {
        let bytes: &[u8] =
            core::slice::from_raw_parts(desc.contract_name.ptr, desc.contract_name.len);
        core::str::from_utf8_unchecked(bytes) // SAFETY: see comment above
    };

    let result: Result<GuestContractHandle, _> =
        BENCH_REGISTRY.with(|cell: &core::cell::RefCell<Option<RuntimeStore>>| {
            // SAFETY: interface pointer is 'static — extracted from a loaded library that outlives registry.
            let borrowed = cell.borrow();
            let registry = borrowed.as_ref().expect("registry not initialized");
            // SAFETY: `interface` is a 'static pointer captured from a loaded plugin library
            // that outlives the registry, and `desc` is valid for the duration of this call
            // per the register_guest_contract ABI contract, so register_guest_contract's
            // preconditions are met.
            unsafe {
                registry.register_guest_contract(
                    *desc,
                    interface,
                    contract_name.to_owned(),
                    BundleId::from_u64(iface.contract_id.id()),
                )
            }
        });

    let err: AbiError = match result {
        Ok(_) => {
            // Capture the interface pointer and contract_id for easy retrieval.
            LAST_INTERFACE.with(|cell| cell.set(interface));
            LAST_CONTRACT_ID.with(|cell| cell.set(iface.contract_id.id()));
            AbiError::ok()
        }
        Err(_) => AbiError {
            code: AbiErrorCode::Generic as u32,
            message: StringView::null(),
        },
    };
    if !out_err.is_null() {
        // SAFETY: out_err is non-null (just checked) and writable per the ABI contract.
        unsafe { out_err.write(err) };
    }
}

// ─── Stub HostApi functions for cross-plugin dispatch ──────────────────────

/// Finds a plugin by contract_id in the thread-local BENCH_REGISTRY.
///
/// # Safety
/// Must only be called from a bench thread where BENCH_REGISTRY is initialised.
unsafe extern "C" fn bench_find_guest_contract(
    _this: *const HostApi,
    contract_id: u64,
    min_version: u32,
) -> GuestContractHandle {
    BENCH_REGISTRY.with(|cell: &core::cell::RefCell<Option<RuntimeStore>>| {
        let registry = cell.borrow();
        let reg = registry.as_ref().expect("registry not initialized");
        reg.find(
            polyplug_utils::GuestContractId::from_u64(contract_id),
            min_version,
        )
        .unwrap_or_else(|_| GuestContractHandle::null())
    })
}

/// find_all_by_contract stub — returns empty array (not used in benches).
///
/// # Safety
/// Always safe to call; returns empty array.
unsafe extern "C" fn bench_find_all_guest_contracts(
    _this: *const HostApi,
    _contract_id: u64,
    _min_version: u32,
) -> Array<GuestContractHandle> {
    Array::empty()
}

/// Resolves a plugin handle to an interface pointer via the thread-local BENCH_REGISTRY.
///
/// # Safety
/// The returned pointer is valid and 'static — the library is kept alive via mem::forget.
unsafe extern "C" fn bench_resolve_guest_contract(
    _this: *const HostApi,
    handle: GuestContractHandle,
) -> *const GuestContractInterface {
    BENCH_REGISTRY.with(|cell: &core::cell::RefCell<Option<RuntimeStore>>| {
        cell.borrow()
            .as_ref()
            .expect("registry not initialized")
            .resolve_guest_contract(handle)
            .unwrap_or(core::ptr::null())
    })
}

/// Returns a null host contract instance.
unsafe extern "C" fn bench_get_host_contract(
    _this: *const HostApi,
    _contract_id: u64,
    _min_version: u32,
) -> polyplug_abi::HostContractInstance {
    polyplug_abi::HostContractInstance::null()
}

/// Returns null pointer for host contract interface.
unsafe extern "C" fn bench_resolve_host_contract_interface(
    _this: *const HostApi,
    _contract_id: u64,
    _min_version: u32,
) -> *const polyplug_abi::HostContractInterface {
    core::ptr::null()
}

/// Returns empty array of bundle IDs.
unsafe extern "C" fn bench_list_bundles(_this: *const HostApi) -> Array<BundleId> {
    Array::empty()
}

/// Returns empty array of dependencies.
unsafe extern "C" fn bench_get_dependencies(
    _this: *const HostApi,
) -> Array<polyplug_abi::DependencyInfo> {
    Array::empty()
}

/// load_bundle stub — writes error to out_err (not used in benches).
unsafe extern "C" fn bench_load_bundle(
    _this: *const HostApi,
    _path: *const u8,
    _path_len: usize,
    out_err: *mut AbiError,
) {
    if !out_err.is_null() {
        // SAFETY: out_err is non-null (just checked) and writable per the ABI contract.
        unsafe {
            out_err.write(AbiError {
                code: AbiErrorCode::Generic as u32,
                message: StringView::null(),
            })
        };
    }
}

/// reload_bundle stub — writes error to out_err (not used in benches).
unsafe extern "C" fn bench_reload_bundle(
    _this: *const HostApi,
    _path: *const u8,
    _path_len: usize,
    out_err: *mut AbiError,
) {
    if !out_err.is_null() {
        // SAFETY: out_err is non-null (just checked) and writable per the ABI contract.
        unsafe {
            out_err.write(AbiError {
                code: AbiErrorCode::Generic as u32,
                message: StringView::null(),
            })
        };
    }
}

/// register_host_contract stub — writes error to out_err (not used in benches).
unsafe extern "C" fn bench_register_host_contract(
    _this: *const HostApi,
    _interface: *const polyplug_abi::HostContractInterface,
    out_err: *mut AbiError,
) {
    if !out_err.is_null() {
        // SAFETY: out_err is non-null (just checked) and writable per the ABI contract.
        unsafe {
            out_err.write(AbiError {
                code: AbiErrorCode::Generic as u32,
                message: StringView::null(),
            })
        };
    }
}

/// register_loader stub — writes error to out_err (not used in benches).
unsafe extern "C" fn bench_register_loader(
    _this: *const HostApi,
    _loader_ptr: *mut core::ffi::c_void,
    out_err: *mut AbiError,
) {
    if !out_err.is_null() {
        // SAFETY: out_err is non-null (just checked) and writable per the ABI contract.
        unsafe {
            out_err.write(AbiError {
                code: AbiErrorCode::Generic as u32,
                message: StringView::null(),
            })
        };
    }
}

/// get_last_error stub — returns 0 (not used in benches).
unsafe extern "C" fn bench_get_last_error(
    _this: *const HostApi,
    _buf: *mut u8,
    _buf_len: usize,
) -> usize {
    0
}

/// get_error_len stub — returns 0 (not used in benches).
unsafe extern "C" fn bench_get_error_len(_this: *const HostApi) -> usize {
    0
}

/// unload_bundle stub — writes ok to out_err (not used in benches).
unsafe extern "C" fn bench_unload_bundle(
    _this: *const HostApi,
    _bundle_id: BundleId,
    out_err: *mut AbiError,
) {
    if !out_err.is_null() {
        // SAFETY: out_err is non-null (just checked) and writable per the ABI contract.
        unsafe { out_err.write(AbiError::ok()) };
    }
}

/// Alloc wrapper that ignores this (uses global allocator).
///
/// # Safety
/// Delegates to polyplug_host_alloc which is safe for any size/align.
unsafe extern "C" fn bench_alloc(_this: *const HostApi, size: usize, align: usize) -> *mut u8 {
    polyplug_host_alloc(size, align)
}

/// Free wrapper that ignores this (uses global allocator).
///
/// # Safety
/// Delegates to polyplug_host_free which requires ptr was allocated by polyplug_host_alloc.
unsafe extern "C" fn bench_free(_this: *const HostApi, ptr: *mut u8, size: usize, align: usize) {
    // SAFETY: ptr was allocated by polyplug_host_alloc (caller's responsibility).
    unsafe { polyplug_host_free(ptr, size, align) };
}

// ─── Setup helpers ────────────────────────────────────────────────────────────

/// Build a `HostApi` whose function pointers are all backed by the thread-local
/// `BENCH_REGISTRY` stubs above. Every benchmark that needs to hand a plugin a
/// host table (to register, resolve, or allocate) uses this single definition.
fn bench_host_api() -> HostApi {
    HostApi {
        runtime: core::ptr::null_mut(),
        register_guest_contract: bench_register_callback,
        alloc: bench_alloc,
        free: bench_free,
        find_guest_contract: bench_find_guest_contract,
        find_all_guest_contracts: bench_find_all_guest_contracts,
        resolve_guest_contract: bench_resolve_guest_contract,
        get_host_contract: bench_get_host_contract,
        resolve_host_contract_interface: bench_resolve_host_contract_interface,
        list_bundles: bench_list_bundles,
        get_dependencies: bench_get_dependencies,
        load_bundle: bench_load_bundle,
        reload_bundle: bench_reload_bundle,
        register_host_contract: bench_register_host_contract,
        register_loader: bench_register_loader,
        get_last_error: bench_get_last_error,
        get_error_len: bench_get_error_len,
        unload_bundle: bench_unload_bundle,
        log: stub_host_log,
        create_guest_instance: stub_create_guest_instance,
        destroy_guest_instance: stub_destroy_guest_instance,
        revision_counter: stub_revision_counter,
        reserved: core::ptr::null(),
    }
}

/// Load a plugin cdylib and call `polyplug_init`, registering into BENCH_REGISTRY.
/// After this call, LAST_INTERFACE holds the interface pointer of the plugin just loaded.
fn load_and_init_plugin(path: &str) -> libloading::Library {
    // SAFETY: path is a valid compiled cdylib built by build.rs.
    let library: libloading::Library =
        unsafe { libloading::Library::new(path).expect("failed to load plugin") };

    // SAFETY: polyplug_init matches the expected 2-arg ABI:
    // unsafe extern "C" fn(host: *const HostApi, ctx: *const BundleInitContext) -> AbiError
    let init_fn: libloading::Symbol<
        '_,
        unsafe extern "C" fn(*const HostApi, *const polyplug_abi::BundleInitContext) -> AbiError,
    > = unsafe {
        library
            .get(b"polyplug_init\0")
            .expect("polyplug_init not found")
    };

    let host_interface: HostApi = bench_host_api();

    let plugin_ctx: polyplug_abi::BundleInitContext = polyplug_abi::BundleInitContext {
        bundle_path: StringView::null(),
        bundle_id: 0,
    };

    // SAFETY: init_fn is a valid function; host_interface and plugin_ctx live for the call duration.
    let result: AbiError = unsafe {
        init_fn(
            &host_interface as *const HostApi,
            &plugin_ctx as *const polyplug_abi::BundleInitContext,
        )
    };

    assert!(result.is_ok(), "polyplug_init failed for {}", path);
    library
}

/// Retrieve the dispatch function for `fn_id` from the last registered interface (LAST_INTERFACE).
fn get_interface_fn(
    fn_id: usize,
) -> unsafe extern "C" fn(GuestContractInstance, *const (), *mut (), *mut AbiError) {
    let interface_ptr: *const GuestContractInterface = LAST_INTERFACE.with(|cell| cell.get());
    assert!(
        !interface_ptr.is_null(),
        "interface not captured — was load_and_init_plugin called?"
    );
    // SAFETY: interface_ptr was captured from the polyplug_init callback.
    // The library is kept alive via mem::forget in each benchmark function.
    let interface: &GuestContractInterface = unsafe { &*interface_ptr };
    assert!(
        interface.dispatch_type == DispatchType::Native,
        "expected Native dispatch type, got {:?}",
        interface.dispatch_type
    );
    // SAFETY: dispatch.native.functions is a static array; fn_id is within bounds.
    // We verified dispatch_type == Native above, so accessing .native is safe.
    let fn_ptr: *const () = unsafe { *interface.dispatch.native.functions.add(fn_id) };
    // SAFETY: transmuting to the canonical 4-arg native dispatch signature.
    // Arg/out types are enforced by each benchmark's setup code.
    unsafe { core::mem::transmute(fn_ptr) }
}

// ─── Benchmark 1 — noop dispatch ─────────────────────────────────────────────

/// Measures the cost of a single contract interface function call with trivial (zero) args.
/// Isolates the raw dispatch overhead with no meaningful computation.
fn bench_dispatch_noop(c: &mut Criterion) {
    // Reset registry for a clean slate.
    BENCH_REGISTRY.with(|cell: &core::cell::RefCell<Option<RuntimeStore>>| {
        *cell.borrow_mut() = Some(RuntimeStore::new());
    });

    let _library: libloading::Library = load_and_init_plugin(TEST_PLUGIN_SO);
    let dispatch_fn: unsafe extern "C" fn(
        GuestContractInstance,
        *const (),
        *mut (),
        *mut AbiError,
    ) = get_interface_fn(0);

    let mut group: criterion::BenchmarkGroup<'_, criterion::measurement::WallTime> =
        c.benchmark_group("dispatch");
    group.throughput(Throughput::Elements(1));

    let args: AddArgs = AddArgs { a: 0, b: 0 };
    let mut out: u32 = 0_u32;

    group.bench_function(BenchmarkId::new("noop", "add(0,0)"), |b| {
        b.iter(|| {
            let mut result: AbiError = AbiError::ok();
            // SAFETY: args points to AddArgs, out points to u32; result is writable.
            // test_plugin fn 0 (add) has signature: (a: u32, b: u32) -> u32.
            unsafe {
                dispatch_fn(
                    black_box(GuestContractInstance::null()),
                    black_box(&args as *const AddArgs as *const ()),
                    black_box(&mut out as *mut u32 as *mut ()),
                    &mut result,
                )
            };
            black_box(result);
        });
    });

    group.finish();
    // Keep library alive for the duration; never drop (never-drop invariant).
    core::mem::forget(_library);
}

// ─── Benchmark 2 — buffer arg dispatch ───────────────────────────────────────

/// Measures contract interface dispatch with a Buffer argument (pre-allocated 4096-byte buffer).
/// The buffer is allocated ONCE before the loop — only dispatch overhead is measured.
fn bench_dispatch_buffer_arg(c: &mut Criterion) {
    // Reset registry for a clean slate.
    BENCH_REGISTRY.with(|cell: &core::cell::RefCell<Option<RuntimeStore>>| {
        *cell.borrow_mut() = Some(RuntimeStore::new());
    });

    let _library: libloading::Library = load_and_init_plugin(MEMORY_PLUGIN_SO);
    // fn 0 = memory_fill_preallocated_buffer(args: FillArgs) -> u32
    let dispatch_fn: unsafe extern "C" fn(
        GuestContractInstance,
        *const (),
        *mut (),
        *mut AbiError,
    ) = get_interface_fn(0);

    // Allocate 4096 bytes ONCE outside the benchmark loop.
    let buf_ptr: *mut u8 = polyplug_host_alloc(4096, 1);
    assert!(!buf_ptr.is_null(), "bench alloc failed");

    let mut group: criterion::BenchmarkGroup<'_, criterion::measurement::WallTime> =
        c.benchmark_group("dispatch");
    group.throughput(Throughput::Elements(1));

    let args: FillArgs = FillArgs {
        buf: Buffer {
            ptr: buf_ptr,
            len: 0,
            cap: 4096,
        },
        fill_byte: 0xBB_u8,
    };
    let mut out: u32 = 0_u32;

    group.bench_function(BenchmarkId::new("buffer_arg", "fill_4096"), |b| {
        b.iter(|| {
            let mut result: AbiError = AbiError::ok();
            // SAFETY: args points to FillArgs; out points to u32; result is writable.
            // memory_plugin fn 0 (fill_preallocated_buffer): writes to buf.ptr[0..cap].
            // buf_ptr is a valid 4096-byte allocation from polyplug_host_alloc.
            unsafe {
                dispatch_fn(
                    black_box(GuestContractInstance::null()),
                    black_box(&args as *const FillArgs as *const ()),
                    black_box(&mut out as *mut u32 as *mut ()),
                    &mut result,
                )
            };
            black_box(result);
        });
    });

    group.finish();

    // SAFETY: buf_ptr was allocated with polyplug_host_alloc(4096, 1).
    // Freeing here is safe — the benchmark loop is complete.
    unsafe { polyplug_host_free(buf_ptr, 4096, 1) };

    core::mem::forget(_library);
}

// ─── Benchmark 3 — struct arg and return ─────────────────────────────────────

/// Measures contract interface dispatch with non-trivial AddArgs (a=42, b=57) and a u32 result.
/// Same dispatch path as bench 1 but with meaningful input values to prevent
/// dead-code elimination of the computation inside the plugin.
fn bench_dispatch_struct_arg_and_return(c: &mut Criterion) {
    // Reset registry for a clean slate.
    BENCH_REGISTRY.with(|cell: &core::cell::RefCell<Option<RuntimeStore>>| {
        *cell.borrow_mut() = Some(RuntimeStore::new());
    });

    let _library: libloading::Library = load_and_init_plugin(TEST_PLUGIN_SO);
    let dispatch_fn: unsafe extern "C" fn(
        GuestContractInstance,
        *const (),
        *mut (),
        *mut AbiError,
    ) = get_interface_fn(0);

    let mut group: criterion::BenchmarkGroup<'_, criterion::measurement::WallTime> =
        c.benchmark_group("dispatch");
    group.throughput(Throughput::Elements(1));

    let args: AddArgs = AddArgs {
        a: 42_u32,
        b: 57_u32,
    };
    let mut out: u32 = 0_u32;

    group.bench_function(
        BenchmarkId::new("struct_arg_and_return", "add(42,57)"),
        |b| {
            b.iter(|| {
                let mut result: AbiError = AbiError::ok();
                // SAFETY: args points to AddArgs, out points to u32; result is writable.
                // test_plugin fn 0 (add) expects (a: u32, b: u32) -> u32.
                unsafe {
                    dispatch_fn(
                        black_box(GuestContractInstance::null()),
                        black_box(&args as *const AddArgs as *const ()),
                        black_box(&mut out as *mut u32 as *mut ()),
                        &mut result,
                    )
                };
                black_box(out);
                black_box(result);
            });
        },
    );

    group.finish();
    core::mem::forget(_library);
}

// ─── Benchmark 4 — cross-plugin dispatch ─────────────────────────────────────

/// Measures the full cross-plugin dispatch path:
///   find_guest_contract (Registry lookup) + resolve_guest_contract (interface pointer) + direct dispatch.
/// Uses memory_plugin fn 2 (echo_string_view) as the target — no allocation.
fn bench_dispatch_cross_plugin(c: &mut Criterion) {
    // Reset registry for a clean slate.
    BENCH_REGISTRY.with(|cell: &core::cell::RefCell<Option<RuntimeStore>>| {
        *cell.borrow_mut() = Some(RuntimeStore::new());
    });

    // Load memory_plugin into BENCH_REGISTRY so find_guest_contract can locate it.
    let _memory_lib: libloading::Library = load_and_init_plugin(MEMORY_PLUGIN_SO);

    // Capture the memory.test contract_id (set by bench_register_callback above).
    let memory_contract_id: u64 = LAST_CONTRACT_ID.with(|cell| cell.get());
    assert_ne!(
        memory_contract_id, 0,
        "memory_plugin contract_id was not captured"
    );

    // Build a HostApi backed by the thread-local BENCH_REGISTRY.
    let host_interface: HostApi = bench_host_api();

    // Input StringView pointing to a static byte string — no allocation needed.
    let sv: StringView = StringView {
        ptr: b"hello".as_ptr(),
        len: 5,
    };
    let mut sv_out: StringView = StringView::null();

    let mut group: criterion::BenchmarkGroup<'_, criterion::measurement::WallTime> =
        c.benchmark_group("dispatch");
    group.throughput(Throughput::Elements(1));

    group.bench_function(BenchmarkId::new("cross_plugin", "find+call"), |b| {
        b.iter(|| {
            // SAFETY: bench_find_guest_contract is a valid extern C fn backed by BENCH_REGISTRY.
            let handle: GuestContractHandle = unsafe {
                black_box((host_interface.find_guest_contract)(
                    &host_interface as *const HostApi,
                    memory_contract_id,
                    0,
                ))
            };

            // SAFETY: bench_resolve_guest_contract returns a 'static GuestContractInterface pointer.
            let interface_ptr: *const GuestContractInterface = unsafe {
                black_box((host_interface.resolve_guest_contract)(
                    &host_interface as *const HostApi,
                    handle,
                ))
            };

            // SAFETY: interface_ptr is non-null (plugin is registered), fn 2 is in range.
            // fn 2 = memory_echo_string_view(args: *const StringView, out: *mut StringView).
            // sv is a valid StringView; sv_out is a valid StringView location.
            let result: AbiError = if interface_ptr.is_null() {
                AbiError {
                    code: AbiErrorCode::NotFound as u32,
                    message: StringView::null(),
                }
            } else {
                // SAFETY: interface_ptr is non-null (checked above) and 'static.
                let interface: &GuestContractInterface = unsafe { &*interface_ptr };
                // SAFETY: dispatch.native.functions is a valid static array; index 2 is within function_count.
                // We assume dispatch_type == Native for benchmark plugins.
                let fn_ptr: *const () = unsafe { *interface.dispatch.native.functions.add(2) };
                // SAFETY: fn_ptr is a valid extern C fn for the given function id.
                let dispatch_fn: unsafe extern "C" fn(
                    GuestContractInstance,
                    *const (),
                    *mut (),
                    *mut AbiError,
                ) = unsafe { core::mem::transmute(fn_ptr) };
                let mut err: AbiError = AbiError::ok();
                // SAFETY: sv and sv_out are valid locations matching the fn signature;
                // err is a valid writable out-param.
                unsafe {
                    dispatch_fn(
                        black_box(GuestContractInstance::null()),
                        black_box(&sv as *const StringView as *const ()),
                        black_box(&mut sv_out as *mut StringView as *mut ()),
                        &mut err,
                    )
                };
                err
            };
            black_box(result);
            black_box(sv_out);
        });
    });

    group.finish();
    core::mem::forget(_memory_lib);
}

// ─── Benchmark 5 — marshalling: borrowed view vs owned copy by size ───────────

/// Arguments to `memory_return_owned` (memory_plugin fn 5).
#[repr(C)]
struct CopyArgs {
    host: *const HostApi,
    sv: StringView,
}

/// Measures the cost of returning data across the ABI two ways, across payload
/// sizes:
///   - **borrowed** (fn 4): the plugin returns a `StringView` that aliases the
///     caller's bytes — zero allocation, zero copy, size-independent.
///   - **owned** (fn 5): the plugin host-allocates `len` bytes and copies the
///     input in — an allocation plus a `memcpy` that scales with the payload.
///
/// This is the cost behind borrowed-view returns (`&str` / `string_view` /
/// `ReadOnlySpan` / `memoryview`) versus owned native `String` / `bytes`.
fn bench_marshalling(c: &mut Criterion) {
    // Reset registry for a clean slate.
    BENCH_REGISTRY.with(|cell: &core::cell::RefCell<Option<RuntimeStore>>| {
        *cell.borrow_mut() = Some(RuntimeStore::new());
    });

    let _library: libloading::Library = load_and_init_plugin(MEMORY_PLUGIN_SO);
    // fn 4 = memory_return_borrowed(StringView) -> StringView
    // fn 5 = memory_return_owned(CopyArgs) -> Buffer
    let borrowed_fn: unsafe extern "C" fn(
        GuestContractInstance,
        *const (),
        *mut (),
        *mut AbiError,
    ) = get_interface_fn(4);
    let owned_fn: unsafe extern "C" fn(GuestContractInstance, *const (), *mut (), *mut AbiError) =
        get_interface_fn(5);

    // Host table so the owned path can allocate through the host allocator.
    let host_interface: HostApi = bench_host_api();

    // Backing payload of valid bytes; each size borrows a prefix of it. The sweep
    // runs 16 B → 1 MiB so the chart shows the borrowed line staying flat while the
    // owned line climbs across three orders of magnitude.
    let payload: Vec<u8> = vec![b'a'; 1_048_576];
    let sizes: [usize; 9] = [16, 64, 256, 1024, 4096, 16384, 65536, 262_144, 1_048_576];

    let mut group: criterion::BenchmarkGroup<'_, criterion::measurement::WallTime> =
        c.benchmark_group("marshalling");
    group.throughput(Throughput::Elements(1));

    for &size in &sizes {
        let sv: StringView = StringView {
            ptr: payload.as_ptr(),
            len: size,
        };

        group.bench_with_input(BenchmarkId::new("borrowed", size), &sv, |b, sv| {
            let mut out: StringView = StringView::null();
            b.iter(|| {
                let mut result: AbiError = AbiError::ok();
                // SAFETY: sv points to a valid StringView; out points to a valid StringView;
                // result is writable. memory_plugin fn 4 echoes the view back without touching the bytes.
                unsafe {
                    borrowed_fn(
                        black_box(GuestContractInstance::null()),
                        black_box(sv as *const StringView as *const ()),
                        black_box(&mut out as *mut StringView as *mut ()),
                        &mut result,
                    )
                };
                black_box(result);
                black_box(out);
            });
        });

        let copy_args: CopyArgs = CopyArgs {
            host: &host_interface as *const HostApi,
            sv,
        };
        group.bench_with_input(BenchmarkId::new("owned", size), &copy_args, |b, args| {
            let mut out: Buffer = Buffer {
                ptr: core::ptr::null_mut(),
                len: 0,
                cap: 0,
            };
            b.iter(|| {
                let mut result: AbiError = AbiError::ok();
                // SAFETY: args points to a valid CopyArgs (valid host + sv); out points to a
                // valid Buffer; result is writable. memory_plugin fn 5 host-allocs `sv.len` bytes and copies in.
                unsafe {
                    owned_fn(
                        black_box(GuestContractInstance::null()),
                        black_box(args as *const CopyArgs as *const ()),
                        black_box(&mut out as *mut Buffer as *mut ()),
                        &mut result,
                    )
                };
                black_box(result);
                // Free the owned buffer each iteration so the loop does not leak/grow.
                if !out.ptr.is_null() {
                    // SAFETY: out.ptr was allocated by the plugin via bench_alloc
                    // (polyplug_host_alloc) with align 1 and `out.len` bytes.
                    unsafe { polyplug_host_free(out.ptr, out.len, 1) };
                }
            });
        });
    }

    group.finish();
    core::mem::forget(_library);
}

// ─── criterion_group / criterion_main ────────────────────────────────────────

criterion_group!(
    benches,
    bench_dispatch_noop,
    bench_dispatch_buffer_arg,
    bench_dispatch_struct_arg_and_return,
    bench_dispatch_cross_plugin,
    bench_marshalling,
);
criterion_main!(benches);

/// `HostApi.log` stub for test hosts — drops the record.
unsafe extern "C" fn stub_host_log(
    _this: *const polyplug_abi::HostApi,
    _level: u32,
    _scope: polyplug_abi::StringView,
    _message: polyplug_abi::StringView,
) {
}

unsafe extern "C" fn stub_create_guest_instance(
    _this: *const polyplug_abi::HostApi,
    _interface: *const polyplug_abi::GuestContractInterface,
    _args: *const core::ffi::c_void,
    out_instance: *mut polyplug_abi::GuestContractInstance,
) {
    if !out_instance.is_null() {
        // SAFETY: out_instance is non-null (just checked) and writable per the ABI contract.
        unsafe { out_instance.write(polyplug_abi::GuestContractInstance::null()) };
    }
}

unsafe extern "C" fn stub_destroy_guest_instance(
    _this: *const polyplug_abi::HostApi,
    _interface: *const polyplug_abi::GuestContractInterface,
    _instance: polyplug_abi::GuestContractInstance,
) {
}

unsafe extern "C" fn stub_revision_counter(_this: *const polyplug_abi::HostApi) -> *const u64 {
    core::ptr::null()
}