Skip to content

Native function continuations#3353

Merged
2kai2kai2 merged 3 commits intocanaryfrom
kai/call-from-native-fn
Apr 13, 2026
Merged

Native function continuations#3353
2kai2kai2 merged 3 commits intocanaryfrom
kai/call-from-native-fn

Conversation

@2kai2kai2
Copy link
Copy Markdown
Collaborator

@2kai2kai2 2kai2kai2 commented Apr 10, 2026

In many cases, we need to be able to call a BAML bytecode function from a BAML native function (implemented in Rust). This requires the ability to "pause" execution of the Rust function to yield control back to the VM execution loop. This is implemented as a coroutine.

This works by changing the return type of native functions from Result<Value, VmRustFnError> to an enum with Ok, Err, and YieldToCall variants. The YieldToCall variant contains a function pointer and arguments for the VM to call, along with a Continuation. The VM will add a native function frame to the frame stack which holds this Continuation. Once the called functions return (and their frames are popped), the VM will execute the Continuation, effectively 'resuming' the native function.
Note that to ensure the GC does not collect objects held by the native function (since GC events can take place if async operations happen while the called VM function is being executed), the Continuation has methods to get and update GC roots that will report to the GC that these objects are still live, even if not held in the VM stack itself.

Also:

  • Implements Array.map
  • Deleted Map.map, Map.map_keys, and Map.map_values since we do not have tuple types yet to properly implement them.

Summary by CodeRabbit

  • New Features
    • Array.map() is implemented in the VM with async-aware callbacks, proper exception propagation, and correct handling of captured variables.
  • Breaking Changes
    • Class-level Map helpers (map, map_keys, map_values) were removed.
  • Tests
    • Added extensive Array.map() tests covering empty/single arrays, captures, and exception behavior.
  • Refactor
    • VM call/continuation handling updated to support native yield/continuation semantics.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment Apr 13, 2026 5:30am
promptfiddle Ready Ready Preview, Comment Apr 13, 2026 5:30am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

The PR adds VM-yielding support for native calls and Array.map, introduces a CPS-style NativeCallResult/Continuation flow, updates codegen/extraction to propagate a new may_yield directive, and adjusts glue, VM frame handling, and tests accordingly.

Changes

Cohort / File(s) Summary
VM core & call results
baml_language/crates/bex_vm/src/package_baml/mod.rs, baml_language/crates/bex_vm/src/vm.rs
Introduce NativeCallResult enum and Continuation trait; change native function type to return NativeCallResult; replace Frame shape with tagged Bytecode/Native frames and update exec/unwind/GC to handle CPS-style yield/resume.
Array/Map runtime implementations
baml_language/crates/bex_vm/src/package_baml/array.rs, baml_language/crates/bex_vm/src/package_baml/map.rs
Implement element-by-element, yielding Array.map using continuations and GC rooting/forwarding; remove unimplemented Map.map/map_keys/map_values; adapt glue (__glue_set) to new NativeCallResult.
Codegen & extractor
baml_language/crates/baml_builtins2_codegen/src/codegen.rs, .../extract.rs, .../types.rs
Add may_yield flag to builtin metadata; emit trait methods and glue to return NativeCallResult when may_yield is set; split glue into yielding vs non-yielding paths, add indented emission helpers, and update extractor assertions.
BAML standard library surface
baml_language/crates/baml_builtins2/baml_std/baml/containers.baml
Mark Array.map with //baml:mut_vm and //baml:may_yield; remove Map methods map, map_keys, map_values from container declarations.
Tests
baml_language/crates/baml_tests/tests/arrays.rs, .../lambdas.rs, .../projects/lambda_advanced/lambda_advanced.baml
Add multiple async tests for Array.map (including error propagation and type checks); un-ignore a closure map test; remove several Map-related BAML tests that exercised removed Map APIs.
Helpers & plumbing
assorted small changes across codegen helpers and result-conversion helpers
Refactor codegen helpers to support indentation-aware emission and rename/adjust result conversion helpers to produce Ok(...) inside closures for the new glue shapes.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Caller as JS/BAML Code
    participant VM as BexVm
    participant Native as NativeFunction Glue
    participant Continuation as Continuation (Rust)
    Caller->>VM: call native function (args)
    VM->>Native: invoke __glue_{fn}(vm, args)
    alt non-yielding
        Native-->>VM: NativeCallResult.Done(value)
        VM-->>Caller: return value
    else yielding
        Native-->>VM: NativeCallResult::YieldToCall { callee, args, continuation }
        VM->>VM: push NativeFrame(callee, continuation)
        VM->>Native: execute callee (possibly bytecode/native)
        Note right of VM: callee eventually returns a Value (or Error/Yield)
        VM->>Continuation: continuation.call(vm, returned_value)
        Continuation-->>VM: NativeCallResult.Done(final_value) or Error or YieldToCall
        VM-->>Caller: final_value (or propagate Error / handle further Yield)
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰 I hopped through code, so nimble and spry,
Yanking map to yield, then watch it fly.
Continuations stitched with carrot-fueled cheer,
Arrays transformed — one element near.
Hooray for runtimes that let rabbits steer!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Native function continuations' directly and specifically summarizes the main change: introducing coroutine-style continuation support for native functions to enable yielding control back to the VM.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch kai/call-from-native-fn

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
baml_language/crates/baml_builtins2_codegen/src/extract.rs (1)

200-204: Error message should mention //baml:mut_self as an alternative.

The assertion correctly allows has_mut_vm || is_mut, but the error message only mentions //baml:mut_vm. For completeness, consider updating the message to reflect that //baml:mut_self also satisfies the requirement for methods.

📝 Suggested message improvement
         assert!(
             !may_yield || has_mut_vm || is_mut,
-            "baml codegen error: {path} has //baml:may_yield without //baml:mut_vm \
-             -- yielding methods require mutable VM access"
+            "baml codegen error: {path} has //baml:may_yield without //baml:mut_vm or //baml:mut_self \
+             -- yielding methods require mutable VM access"
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/baml_builtins2_codegen/src/extract.rs` around lines 200
- 204, The assert checking may_yield vs. has_mut_vm || is_mut should have its
error string updated to mention both accepted annotations; change the assertion
message in the assert(...) that references may_yield, has_mut_vm, and is_mut so
it lists both //baml:mut_vm and //baml:mut_self as valid ways to obtain mutable
access (e.g., "... requires mutable VM access via //baml:mut_vm or
//baml:mut_self"). Ensure the message still includes the {path} interpolation
and the existing explanatory text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@baml_language/crates/bex_vm/src/vm.rs`:
- Around line 1379-1426: The YieldToCall branch currently constructs Native and
Bytecode frames by hand (pushing Frame::Native, Frame::Bytecode, calling
allocate_real_locals_for_frame and load_function) which bypasses the invariants
enforced by execute_call_from_locals_offset(); fix by routing this path through
the same call-setup logic (either call execute_call_from_locals_offset() or
extract a helper that mirrors it) instead of manual pushes: validate
callback_args.len() against the callee arity, reject non-FunctionKind::Bytecode
callees, enforce the MAX_FRAMES extra-frame budget, perform the traced-call
bookkeeping and emit SpanNotify::FunctionEnter when appropriate, then set
frame_idx and call load_function/allocate_real_locals_for_frame via that helper
rather than duplicating the frame pushes in the YieldToCall branch.

---

Nitpick comments:
In `@baml_language/crates/baml_builtins2_codegen/src/extract.rs`:
- Around line 200-204: The assert checking may_yield vs. has_mut_vm || is_mut
should have its error string updated to mention both accepted annotations;
change the assertion message in the assert(...) that references may_yield,
has_mut_vm, and is_mut so it lists both //baml:mut_vm and //baml:mut_self as
valid ways to obtain mutable access (e.g., "... requires mutable VM access via
//baml:mut_vm or //baml:mut_self"). Ensure the message still includes the {path}
interpolation and the existing explanatory text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ea65727d-fc38-4fc2-a617-bca95a24c0f2

📥 Commits

Reviewing files that changed from the base of the PR and between d124833 and f8c4a82.

⛔ Files ignored due to path filters (24)
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/client_option_types/baml_tests__client_option_types__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/comment_after_string_in_config/baml_tests__comment_after_string_in_config__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/comment_in_type/baml_tests__comment_in_type__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/config_dictionary/baml_tests__config_dictionary__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/config_model_string/baml_tests__config_model_string__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/control_flow/baml_tests__control_flow__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/format_checks/baml_tests__format_checks__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/header_in_llm_function/baml_tests__header_in_llm_function__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__01_lexer__lambda_advanced.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__02_parser__lambda_advanced.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__10_formatter__lambda_advanced.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/o1_allowed_roles/baml_tests__o1_allowed_roles__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/retry_policy/baml_tests__retry_policy__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/src/compiler2_tir/snapshots/baml_tests__compiler2_tir__phase5__snapshot_baml_package_items.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snap is excluded by !**/*.snap
📒 Files selected for processing (11)
  • baml_language/crates/baml_builtins2/baml_std/baml/containers.baml
  • baml_language/crates/baml_builtins2_codegen/src/codegen.rs
  • baml_language/crates/baml_builtins2_codegen/src/extract.rs
  • baml_language/crates/baml_builtins2_codegen/src/types.rs
  • baml_language/crates/baml_tests/projects/lambda_advanced/lambda_advanced.baml
  • baml_language/crates/baml_tests/tests/arrays.rs
  • baml_language/crates/baml_tests/tests/lambdas.rs
  • baml_language/crates/bex_vm/src/package_baml/array.rs
  • baml_language/crates/bex_vm/src/package_baml/map.rs
  • baml_language/crates/bex_vm/src/package_baml/mod.rs
  • baml_language/crates/bex_vm/src/vm.rs
💤 Files with no reviewable changes (2)
  • baml_language/crates/baml_tests/tests/lambdas.rs
  • baml_language/crates/baml_tests/projects/lambda_advanced/lambda_advanced.baml

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 10, 2026

Binary size checks passed

7 passed

Artifact Platform Gzip Baseline Delta Status
bridge_cffi Linux 5.8 MB 5.7 MB +165.3 KB (+2.9%) OK
bridge_cffi-stripped Linux 5.8 MB 5.7 MB +146.5 KB (+2.6%) OK
bridge_cffi macOS 4.8 MB 4.6 MB +180.4 KB (+3.9%) OK
bridge_cffi-stripped macOS 4.8 MB 4.7 MB +121.8 KB (+2.6%) OK
bridge_cffi Windows 4.8 MB 4.6 MB +175.7 KB (+3.8%) OK
bridge_cffi-stripped Windows 4.8 MB 4.7 MB +127.2 KB (+2.7%) OK
bridge_wasm WASM 3.1 MB 3.0 MB +134.2 KB (+4.5%) OK

Generated by cargo size-gate · workflow run

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
baml_language/crates/baml_builtins2_codegen/src/codegen.rs (1)

843-935: Add a direct unit test for the yielding codegen path.

This branch changes both the emitted trait signature and the glue return plumbing, but the local test suite still only asserts non-yielding output shapes. A small assertion for a may_yield builtin (for example baml.Array.map) would make regressions here much easier to catch before they surface elsewhere. As per coding guidelines, **/*.rs: Prefer writing Rust unit tests over integration tests where possible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/baml_builtins2_codegen/src/codegen.rs` around lines 843
- 935, Add a unit test that covers the yielding codegen path: construct a
NativeBuiltin with may_yield = true (e.g., mimic baml.Array.map) and call the
codegen functions (emit_trait_method and emit_glue_method) to produce output,
then assert the emitted trait method includes "fn {method_name}(vm: &mut BexVm,"
and the glue contains the yielding-specific plumbing (closure returning
Result<NativeCallResult, VmRustFnError>, matching that returns NativeCallResult
directly, and the "Err(e) => NativeCallResult::Error(e)" arm). Place the test in
the same module as emit_glue_method / emit_trait_method so it can build the
NativeBuiltin and call clean_param_list / call_arg_list /
emit_arg_extractions_indented as needed; this ensures regressions in the
may_yield branch are caught by unit tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@baml_language/crates/baml_builtins2_codegen/src/extract.rs`:
- Around line 200-204: The current assert permits may_yield when is_mut
(//baml:mut_self) even if has_mut_vm is false, which creates an unsupported
builtin shape; tighten the invariant so that may_yield implies has_mut_vm by
changing the condition to require has_mut_vm whenever may_yield is set (i.e.
replace the `!may_yield || has_mut_vm || is_mut` check with `!may_yield ||
has_mut_vm`), and update the assertion message to reference that yielding
methods require //baml:mut_vm (use the existing symbols `may_yield`,
`has_mut_vm`, `is_mut`, and `path` to locate and adjust the assert and its
message).

In `@baml_language/crates/bex_vm/src/vm.rs`:
- Around line 3065-3092: The continuation path in NativeCallResult::YieldToCall
can return early after calling execute_call_from_locals_offset (in the block
handling cb_callee/cb_args/cb_cont) and thus skip emitting the previously
captured FunctionExit span; ensure the exit span is always emitted before any
early return by moving the span-exit emission to occur immediately after
execute_call_from_locals_offset returns (or by using a RAII/drop guard) so that
both branches that check self.frames.last() and return (including when
ecflo_result.is_some()) will first emit the FunctionExit; update the code paths
around execute_call_from_locals_offset, the
frames.push(Frame::Native(...))/cb_cont handling, and the branches that return
Ok(...) to call the span-exit emission (or install a guard) so spans remain
balanced when a continuation re-yields.
- Around line 1402-1415: The code pushes a NativeFrame (Frame::Native with
NativeFrame { function: callee_ptr, continuation: cb_cont }) onto self.frames
and extends self.stack with cb_args (using cb_locals =
StackIndex::from_raw(self.stack.len())) before calling
execute_call_from_locals_offset(cb_callee, cb_locals, arg_count, frame_idx,
function), which can return an error and leak that mutated state; update both
trampoline loops to atomically roll back those mutations on any early return by
removing the pushed frame from self.frames and truncating self.stack back to the
original length when execute_call_from_locals_offset returns Err (or otherwise
fails), ensuring you reference the same symbols (self.frames,
Frame::Native/NativeFrame, callee_ptr/cb_cont,
cb_args/cb_locals/StackIndex::from_raw, execute_call_from_locals_offset,
frame_idx, function) so the cleanup mirrors the setup.

---

Nitpick comments:
In `@baml_language/crates/baml_builtins2_codegen/src/codegen.rs`:
- Around line 843-935: Add a unit test that covers the yielding codegen path:
construct a NativeBuiltin with may_yield = true (e.g., mimic baml.Array.map) and
call the codegen functions (emit_trait_method and emit_glue_method) to produce
output, then assert the emitted trait method includes "fn {method_name}(vm: &mut
BexVm," and the glue contains the yielding-specific plumbing (closure returning
Result<NativeCallResult, VmRustFnError>, matching that returns NativeCallResult
directly, and the "Err(e) => NativeCallResult::Error(e)" arm). Place the test in
the same module as emit_glue_method / emit_trait_method so it can build the
NativeBuiltin and call clean_param_list / call_arg_list /
emit_arg_extractions_indented as needed; this ensures regressions in the
may_yield branch are caught by unit tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ac35c1e6-1ffb-47d1-be9b-fd444214fd44

📥 Commits

Reviewing files that changed from the base of the PR and between f8c4a82 and 8986ed6.

⛔ Files ignored due to path filters (24)
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/client_option_types/baml_tests__client_option_types__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/comment_after_string_in_config/baml_tests__comment_after_string_in_config__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/comment_in_type/baml_tests__comment_in_type__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/config_dictionary/baml_tests__config_dictionary__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/config_model_string/baml_tests__config_model_string__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/control_flow/baml_tests__control_flow__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/format_checks/baml_tests__format_checks__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/header_in_llm_function/baml_tests__header_in_llm_function__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__01_lexer__lambda_advanced.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__02_parser__lambda_advanced.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__10_formatter__lambda_advanced.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/o1_allowed_roles/baml_tests__o1_allowed_roles__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/retry_policy/baml_tests__retry_policy__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/src/compiler2_tir/snapshots/baml_tests__compiler2_tir__phase5__snapshot_baml_package_items.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snap is excluded by !**/*.snap
📒 Files selected for processing (11)
  • baml_language/crates/baml_builtins2/baml_std/baml/containers.baml
  • baml_language/crates/baml_builtins2_codegen/src/codegen.rs
  • baml_language/crates/baml_builtins2_codegen/src/extract.rs
  • baml_language/crates/baml_builtins2_codegen/src/types.rs
  • baml_language/crates/baml_tests/projects/lambda_advanced/lambda_advanced.baml
  • baml_language/crates/baml_tests/tests/arrays.rs
  • baml_language/crates/baml_tests/tests/lambdas.rs
  • baml_language/crates/bex_vm/src/package_baml/array.rs
  • baml_language/crates/bex_vm/src/package_baml/map.rs
  • baml_language/crates/bex_vm/src/package_baml/mod.rs
  • baml_language/crates/bex_vm/src/vm.rs
💤 Files with no reviewable changes (2)
  • baml_language/crates/baml_tests/tests/lambdas.rs
  • baml_language/crates/baml_tests/projects/lambda_advanced/lambda_advanced.baml
🚧 Files skipped from review as they are similar to previous changes (3)
  • baml_language/crates/baml_builtins2/baml_std/baml/containers.baml
  • baml_language/crates/bex_vm/src/package_baml/map.rs
  • baml_language/crates/baml_tests/tests/arrays.rs

Native functions can now yield back to the VM to call more VM functions. This works by returning a function pointer + args to call, as well as a `Continuation` to resume execution of the native function once the VM is done evaluating its callees.
This uses the new ability to call VM functions from native functions.
Also deleted `Map.map`
and other improvements
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
baml_language/crates/bex_vm/src/vm.rs (1)

1423-1434: ⚠️ Potential issue | 🔴 Critical

Rollback the pushed native frame and callback args if callback dispatch setup fails.

execute_call_from_locals_offset() can still reject the yielded callee before a callback frame exists here (bad callee kind, arity mismatch, MAX_FRAMES, etc.). By then both branches have already pushed Frame::Native and extended self.stack, so the error path leaks state; try_unwind_exception() only pops the native frame and never drains those callback args because native frames own no stack slice. Snapshot frames.len() / stack.len() before mutating and truncate them on every Err before unwinding or returning.

Also applies to: 1713-1738

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bex_vm/src/vm.rs` around lines 1423 - 1434, Before
pushing Frame::Native and extending self.stack with callback_args, snapshot the
current frames.len() and stack.len(); then call execute_call_from_locals_offset
as before but on any Err ensure you truncate self.frames and self.stack back to
those saved lengths (removing the pushed NativeFrame and the appended callback
args) before propagating/unwinding. Apply this same snapshot-and-truncate
pattern around the other analogous block that constructs NativeFrame/cb_locals
and calls execute_call_from_locals_offset (the duplicate mentioned) so no state
is leaked when execute_call_from_locals_offset rejects the callee.
🧹 Nitpick comments (1)
baml_language/crates/baml_builtins2_codegen/src/codegen.rs (1)

842-936: Add a focused unit test for the may_yield generator path.

This branch changes the generated ABI, but the nearby tests still only assert non-yielding shapes. A direct assertion for Array.map returning NativeCallResult and emitting the Result<NativeCallResult, VmRustFnError> wrapper would make this much harder to regress. Please also run cargo test --lib locally before merge.

As per coding guidelines, "Prefer writing Rust unit tests over integration tests where possible" and "Always run cargo test --lib if you changed any Rust code".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/baml_builtins2_codegen/src/codegen.rs` around lines 842
- 936, Add a focused unit test that exercises the may_yield branch of
emit_glue_method: construct a NativeBuiltin with may_yield = true (e.g., similar
to Array.map), call emit_glue_method and assert the generated string contains
the expected ABI changes — the glue fn signature returning NativeCallResult (fn
__glue_{method_name}(vm: &mut BexVm, args: &[Value]) -> NativeCallResult), the
inner closure typed as Result<NativeCallResult, VmRustFnError>, the call to
Self::{method_name}(vm, {call_args}) inside that closure, and the match arms
Ok(r) => r and Err(e) => NativeCallResult::Error(e); use existing helpers like
call_arg_list and emit_arg_extractions_indented to build the input and keep the
test as a Rust unit test (not integration), then run cargo test --lib locally
before merging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@baml_language/crates/bex_vm/src/vm.rs`:
- Around line 1423-1434: Before pushing Frame::Native and extending self.stack
with callback_args, snapshot the current frames.len() and stack.len(); then call
execute_call_from_locals_offset as before but on any Err ensure you truncate
self.frames and self.stack back to those saved lengths (removing the pushed
NativeFrame and the appended callback args) before propagating/unwinding. Apply
this same snapshot-and-truncate pattern around the other analogous block that
constructs NativeFrame/cb_locals and calls execute_call_from_locals_offset (the
duplicate mentioned) so no state is leaked when execute_call_from_locals_offset
rejects the callee.

---

Nitpick comments:
In `@baml_language/crates/baml_builtins2_codegen/src/codegen.rs`:
- Around line 842-936: Add a focused unit test that exercises the may_yield
branch of emit_glue_method: construct a NativeBuiltin with may_yield = true
(e.g., similar to Array.map), call emit_glue_method and assert the generated
string contains the expected ABI changes — the glue fn signature returning
NativeCallResult (fn __glue_{method_name}(vm: &mut BexVm, args: &[Value]) ->
NativeCallResult), the inner closure typed as Result<NativeCallResult,
VmRustFnError>, the call to Self::{method_name}(vm, {call_args}) inside that
closure, and the match arms Ok(r) => r and Err(e) => NativeCallResult::Error(e);
use existing helpers like call_arg_list and emit_arg_extractions_indented to
build the input and keep the test as a Rust unit test (not integration), then
run cargo test --lib locally before merging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 85ce37ba-1a16-4dd5-84fa-34619465afc2

📥 Commits

Reviewing files that changed from the base of the PR and between 8986ed6 and f0405d8.

⛔ Files ignored due to path filters (14)
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__01_lexer__lambda_advanced.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__02_parser__lambda_advanced.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__10_formatter__lambda_advanced.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/src/compiler2_tir/snapshots/baml_tests__compiler2_tir__phase5__snapshot_baml_package_items.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snap is excluded by !**/*.snap
📒 Files selected for processing (11)
  • baml_language/crates/baml_builtins2/baml_std/baml/containers.baml
  • baml_language/crates/baml_builtins2_codegen/src/codegen.rs
  • baml_language/crates/baml_builtins2_codegen/src/extract.rs
  • baml_language/crates/baml_builtins2_codegen/src/types.rs
  • baml_language/crates/baml_tests/projects/lambda_advanced/lambda_advanced.baml
  • baml_language/crates/baml_tests/tests/arrays.rs
  • baml_language/crates/baml_tests/tests/lambdas.rs
  • baml_language/crates/bex_vm/src/package_baml/array.rs
  • baml_language/crates/bex_vm/src/package_baml/map.rs
  • baml_language/crates/bex_vm/src/package_baml/mod.rs
  • baml_language/crates/bex_vm/src/vm.rs
💤 Files with no reviewable changes (2)
  • baml_language/crates/baml_tests/tests/lambdas.rs
  • baml_language/crates/baml_tests/projects/lambda_advanced/lambda_advanced.baml
🚧 Files skipped from review as they are similar to previous changes (5)
  • baml_language/crates/baml_builtins2_codegen/src/types.rs
  • baml_language/crates/baml_tests/tests/arrays.rs
  • baml_language/crates/baml_builtins2/baml_std/baml/containers.baml
  • baml_language/crates/baml_builtins2_codegen/src/extract.rs
  • baml_language/crates/bex_vm/src/package_baml/map.rs

Merged via the queue into canary with commit 138a702 Apr 13, 2026
41 checks passed
@2kai2kai2 2kai2kai2 deleted the kai/call-from-native-fn branch April 13, 2026 05:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant