Latest Results
refactor: remove unnecessary deep cloning in native function glue (#3447)
[Ticket](https://cloud.codelayer.cloud/artifacts/019de23b-2ebc-7000-85b0-9585394ab28b)
|
[Artifacts](https://cloud.codelayer.cloud/tasks/019de23b-2d62-7fe8-b263-1a8075ca36a4/artifacts)
|
[Task](https://cloud.codelayer.cloud/deep/tasks/019de23b-2d62-7fe8-b263-1a8075ca36a4)
## What problems was I solving
The native function glue layer (generated by `baml_builtins2_codegen`)
was cloning every heap-type argument (arrays, maps, strings,
uint8arrays) when passing them to native Rust functions — even though
those functions accept references (`&[Value]`, `&str`, `&IndexMap`,
`&[u8]`). This caused **O(n²) behavior in array iteration**: `for (let x
in arr)` calls `Array.length` every iteration, and each call cloned the
entire array. For 100k elements, this meant ~10 billion `Value` copies
and 9.6 seconds of wall clock time.
The VM accessor methods (`vm.as_array()`, `vm.as_string()`, etc.)
already return references. The native implementations already accept
references. The clones in between were pure waste.
## What user-facing changes did I ship
No user-facing API changes. This is an internal performance optimization
that eliminates unnecessary memory allocation and copying in the VM's
builtin method dispatch path. Users should see significant speedups in
tight loops that call builtin methods, especially array iteration
patterns.
## How I implemented it
### Phase 1: Zero-copy extraction for non-mutating builtins (commit
`16458229d`)
-
[codegen.rs](https://github.com/BoundaryML/baml/pull/3447/files#diff-codegen)
— Threaded a `needs_owned` flag through the entire extraction pipeline.
Computed from `matches!(b.vm_usage, VmUsage::MutRef) || b.may_yield` in
`emit_glue_method`. When `needs_owned = false`, extraction emits
reference-based expressions (e.g., `vm.as_array(&args[0])?` instead of
`vm.as_array(&args[0])?.to_vec()`). Updated `call_arg_for_type` with an
`is_ref` flag so reference-extracted values aren't double-referenced.
### Phase 2: Reclassify read-only methods from MutRef to Ref (commit
`6f7519f65`)
-
[containers.baml](https://github.com/BoundaryML/baml/pull/3447/files#diff-containers)
— Changed `Array.join`, `Map.has`, `Map.get` from `//baml:mut_vm` to
`//baml:vm`
-
[unstable.baml](https://github.com/BoundaryML/baml/pull/3447/files#diff-unstable)
— Changed `unstable.string` from `//baml:mut_vm` to `//baml:vm`
-
[array.rs](https://github.com/BoundaryML/baml/pull/3447/files#diff-array),
[map.rs](https://github.com/BoundaryML/baml/pull/3447/files#diff-map),
[unstable.rs](https://github.com/BoundaryML/baml/pull/3447/files#diff-unstable-rs)
— Changed `vm: &mut BexVm` → `vm: &BexVm` in corresponding native
implementations
### Phase 3: Eliminate VM dependency from String.split (commit
`cb79c358b`)
-
[string.baml](https://github.com/BoundaryML/baml/pull/3447/files#diff-string-baml)
— Removed `//baml:mut_vm` annotation from `String.split`
-
[string.rs](https://github.com/BoundaryML/baml/pull/3447/files#diff-string-rs)
— Changed `split` to return `Vec<String>` instead of `Vec<Value>`,
removing the `vm` parameter entirely. Uses `str::to_string` instead of
`vm.alloc_string`.
-
[codegen.rs](https://github.com/BoundaryML/baml/pull/3447/files#diff-codegen)
— Added `Vec<String>` return type mapping in `baml_type_to_output` for
`List(String)`. Added two-step result conversion in
`emit_result_conversion_ok` that first collects `Vec<String>` into
`Vec<Value>`, then allocates the array — avoiding double-borrow of `vm`.
- [sys.rs](https://github.com/BoundaryML/baml/pull/3447/files#diff-sys)
— Extended the same `Vec<String>` return pattern to `sys.argv`
-
[extract.rs](https://github.com/BoundaryML/baml/pull/3447/files#diff-extract)
— Updated test assertion for `String.split` from `VmUsage::MutRef` to
`VmUsage::None`
## Deviations from the plan
### Implemented as planned
- Phase 1 `needs_owned` computation and threading through all
extraction/call-arg functions
- Phase 1 conditional extraction: reference-based for `!needs_owned`,
clone-based for `needs_owned`
- Phase 2 BAML annotation reclassifications and `&mut BexVm` → `&BexVm`
signature changes
- Phase 3 `String.split` return type change to `Vec<String>` and
`VmUsage::None` reclassification
- Phase 3 `baml_type_to_output` `List(String)` → `Vec<String>` mapping
### Deviations/surprises
- **`emit_result_conversion_ok` vs `result_conversion_expr`**: Plan
specified updating `result_conversion_expr` for the `List(String)`
two-step allocation. Implementation places this logic as an early-return
guard in `emit_result_conversion_ok` instead — same generated output,
different code organization
- **Method reference syntax**: Plan specified closure syntax `|s|
s.as_str()` / `|v| v.as_slice()` for Optional conversion; implementation
uses more idiomatic `String::as_str` / `Vec::as_slice` method references
- **`str::to_string` vs `.to_string()`**: `String.split` uses
`str::to_string` method reference instead of closure — functionally
identical
### Additions not in plan
- **`sys.argv` return type changed to `Vec<String>`**: Plan explicitly
said `sys.argv` should remain `MutRef` with cloning. Implementation
extended the `List(String)` optimization to `sys.argv` as well, which is
safe since the generated glue handles VM allocation after the function
returns
- **Unused import cleanup**: Removed `use bex_vm_types::types::Value`
and `use crate::BexVm` from `string.rs` and `sys.rs` as natural
consequence of the changes
### Items planned but not implemented
- None — all three phases were fully implemented
## How to verify it
### Setup
```bash
git fetch
scripts/create_worktree.sh hellovai/remove-unnecessary-deep-cloning-in-native-function-glue
cd ~/wt/baml/remove-unnecessary-deep-cloning-in-native-function-glue/baml_language
```
### Automated Tests
```bash
cargo build -p bex_vm # validates generated glue compiles
cargo test -p baml_builtins2_codegen # codegen + trait signature tests
cargo test -p baml_tests # integration tests for correctness
```
### Benchmarks
```bash
cargo bench --bench runtime_benchmark # measure improvement on vm_array_iter_10k
```
## Description for the changelog
Remove unnecessary deep cloning of heap-type arguments in VM builtin
method dispatch, fixing O(n²) performance in array iteration loops.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Builtin operations for arrays, maps, and strings now use less mutable
VM access and improved ownership handling, reducing unnecessary
borrowing and allocations.
* Codegen updated to emit ownership-aware glue so arguments and returns
avoid double-borrowing.
* **Tests**
* Adjusted test expectations to align with the refined VM-usage and
ownership semantics.
* **Chores**
* CI workflow trigger conditions expanded for benchmark jobs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Latest Branches
N/A
hellovai/remove-unnecessary-deep-cloning-in-native-function-glue N/A
dependabot/cargo/baml_language/cargo-8f85daf943 N/A
dependabot/github_actions/canary/all-dependencies-3f2e198334 © 2026 CodSpeed Technology