Skip to content

Commit a74b24a

Browse files
authored
Host values in BAML (#3571)
The problem: we want to be able to pass lambdas/closures/function pointers into BAML from a host language in a type-safe way and let BAML call it as if it were a normal BAML function. BAML->host handles already exist in the language, allowing the host language to own opaque BAML values. This PR implements the opposite: a way to have opaque handles to host values in BAML. ## Lifecycle 1. A code-genned BAML function is called by the host language, passing in an opaque/non-serializable value (such as a callable/function/closure) 2. The bridge (on the host side) saves a reference to the value and creates a new host value id. It ensures the host will not deallocate the value until BAML informs it that it no longer has any references. 3. The bridge passes the newly created host value id across the ffi boundary as an opaque handle. 4. The BAML side decodes the host value based on type. Currently this is only host callables (BAML function-typed). 5. (for callables) When a BAML function call occurs on a host callable, it will dispatch back across the host boundary with a new host call operation. 6. When a host value handle is dropped in BAML (there are no references to it and the GC runs on it), a message is sent to the bridge to notify it that it may release the (real) value. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Pass host-language functions as callable parameters and invoke them from BAML. * Structured host-callable errors surfaced as a catchable baml.errors.HostCallable with message/class/category/traceback. * Cross-platform runtime support (Python, Node.js, Go, WASM) for registering, dispatching and completing host callables. * Strict host-return validation against declared types; sync-path encoding fast-fails for host-callable args to avoid main-thread hangs. <!-- review_stack_entry_start --> [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/BoundaryML/baml/pull/3571?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent a9a1cae commit a74b24a

156 files changed

Lines changed: 11516 additions & 539 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

baml_language/Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

baml_language/crates/baml_builtins2/baml_std/baml/ns_errors/errors.baml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,11 @@ class DevOther {
5454
class HostPanic {
5555
message string
5656
}
57+
58+
class HostCallable {
59+
message string
60+
class_name string
61+
language string
62+
traceback string?
63+
category int
64+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// Internal: invoked only by compiler-synthesized host-callable wrapper
2+
/// closures. Not directly callable by user code.
3+
///
4+
/// `T` is the expected return type, delivered to the native handler at
5+
/// runtime via the type-arg channel. The handler validates both the
6+
/// inbound `args` and the host's returned value against the declared
7+
/// signature, raising `root.errors.HostCallable` on mismatch.
8+
function call_host_value<T>(handle: HostValue, args: unknown[])
9+
-> T throws root.errors.HostCallable {
10+
$rust_io_function
11+
}
12+
13+
/// Opaque builtin class. Construction is reserved to the bridge
14+
/// boundary; users cannot construct or destructure it.
15+
class HostValue {
16+
// intentionally empty; the value lives in the BexExternalValue
17+
// HostValue slot.
18+
}

baml_language/crates/baml_builtins2/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ pub const ALL: &[BuiltinFile] = &[
110110
builtin!("baml", "ns_llm/llm.baml"),
111111
builtin!("baml", "ns_stream/stream.baml"),
112112
builtin!("baml", "ns_future/future.baml"),
113+
builtin!("baml", "ns_host/host.baml"),
113114
// --- reflect package (standalone, accessible as `reflect.type_of(...)`) ---
114115
builtin!("reflect", "reflect.baml"),
115116
// --- testing package ---

baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_builtin_package_listing.snap

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class errors.NotImplemented <builtin>/baml/ns_errors/error
2626
class errors.LlmClient <builtin>/baml/ns_errors/errors.baml:44
2727
class errors.DevOther <builtin>/baml/ns_errors/errors.baml:49
2828
class errors.HostPanic <builtin>/baml/ns_errors/errors.baml:54
29+
class errors.HostCallable <builtin>/baml/ns_errors/errors.baml:58
2930
class errors.StackFrame <builtin>/baml/ns_errors/stack_trace.baml:2
3031
class errors.StackTrace <builtin>/baml/ns_errors/stack_trace.baml:9
3132
function events.send <builtin>/baml/ns_events/events.baml:3
@@ -46,6 +47,8 @@ class future.Future <builtin>/baml/ns_future/futur
4647
class glob.ScanOptions <builtin>/baml/ns_glob/glob.baml:2
4748
class glob.Glob <builtin>/baml/ns_glob/glob.baml:18
4849
function glob.new <builtin>/baml/ns_glob/glob.baml:34
50+
function host.call_host_value <builtin>/baml/ns_host/host.baml:8
51+
class host.HostValue <builtin>/baml/ns_host/host.baml:15
4952
class http.Request <builtin>/baml/ns_http/http.baml:2
5053
class http.Response <builtin>/baml/ns_http/http.baml:10
5154
class http.SseStream <builtin>/baml/ns_http/http.baml:33
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// snapshot the bytecode shape for a host-callable call site.
2+
//
3+
// A `Ty::Function` parameter is bound at the FFI boundary to an
4+
// `Object::HostClosure`; calling it via `f(x)` lowers to a plain
5+
// `CallIndirect` instruction at compile time. The HostClosure-specific
6+
// dispatch (yielding `VmExecState::SysOp { operation:
7+
// SysOp::BamlHostCallHostValue, args }`) is entirely a runtime concern
8+
// — the compiler emits no synthesized wrapper for this call.
9+
function call_with_callback(callback: (int) -> string, x: int) -> string {
10+
callback(x)
11+
}

baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____03_hir.snap

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snap

Lines changed: 184 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)