Skip to content

feat(rust) add native link option - #1685

Open
BjornTheProgrammer wants to merge 6 commits into
bytecodealliance:mainfrom
BjornTheProgrammer:wit-native
Open

BjornTheProgrammer wants to merge 6 commits into
bytecodealliance:mainfrom
BjornTheProgrammer:wit-native

Conversation

@BjornTheProgrammer

Copy link
Copy Markdown

Addresses bytecodealliance/wit-bindgen#1062. Currently the Rust bindings stub with unreachable!() when compiling to wasm. This PR makes bindings usable on native targets, so people can test and run component code in ordinary native environments.

This is a new approach to what was initially done in #1565.

The reason this PR needs to exist is described by a comment in the original PR

This PR despite making the possible stubs very modular, suffers from a lot of issues, I'm going to close this in favor of another PR that I've been making. The idea is a bit different, and slightly more complex then just calling a host function, since that would require the host binary to be compiled with "-rdynamic" on linux, and it wouldn't even work on Windows and MacOS.

To solve this issue I've added a register function which can add a pointer to the host function. This fixes the above issue.

Testing

  • Codegen tests have been added.
  • End-to-end validation via native-wit hosting native plugins in Pumpkin.

The Pumpkin PR is interesting because the approach shows how one can make very minimal changes and get wasmtime and native runtimes working at the same time.

@alexcrichton

Copy link
Copy Markdown
Member

Could you address the question I had in the other PR? This seems like a lot to take on in terms of maintainership here because only codegen is tested, not any runtime parts, and since that comment the component model has continued to add features like async which I'm not sure how would map to native counterparts.

@BjornTheProgrammer

Copy link
Copy Markdown
Author

Sure! I'll respond to the previous comment.

Thanks for the PR, and I'm trying to page this all back in and understand the motivations/intentions here. Correct me if I'm wrong, but the high level idea is to be able to satisfy, on non-wasm platforms, wit-bindgen-generated bindings with a native implementation. Is that correct?

Yes. This has been demonstrated in the Pumpkin PR I shared. It allows for native and wasm targets to be compiled interchangeably with no code changes from the plugin developer.

If so, I'm second-guessing this a bit in the sense of why go through ABI-marshalling at all? For example if you're calling a WIT function it seems like it'd be best to just turn around and immediately call a native function rather than serializing to some ABI-based representation just to deserialize and call a host function. Does that make sense to fit in here at all? Or am I perhaps missing something? (I could very well be)

The reason a direct native call doesn't work here is that the guest is compiled separately from the host and loaded with dlopen. Rust has no stable ABI, so passing native types like String or Vec across that boundary, potentially across different rustc versions, or from a non-Rust host won't work. The canonical ABI is already implemented by wit-bindgen and tapping into that is easy. We wouldn't really have to "serialize/deserialize." Nothing is encoded to a buffer. Scalars pass as-is and strings cross as pointer+length, so simple calls are effectively direct calls already.

Reusing the existing ABI also keeps the change small. The existing codegen already emits the full ABI representation on native targets (the unreachable!() stubs sit after all of that), so this PR just swaps the stub for a dispatch through a registered function pointer and hex-encodes the symbol names. Only around +130 lines of actual generator code. Most of that is the register hooks, which exist so the host doesn't have to re-export every import symbol into the dlopen'd library (the -rdynamic problem) and can implement only the subset of imports it needs. And because it's the same underlying C ABI emission, async, resources, and every other WIT construct should work without a parallel code path. Which is why I didn't really see the need to test the runtime part, since whatever ABI works on wasm should just work with native.

A direct-call mode (traits with idiomatic signatures) is imaginable, but I don't think it fits wit-bindgen's shape. There's no language-neutral contract for it to target, so it would be a separate parallel bindings mode per generator, none of which could interoperate across the dlopen or language boundary. The canonical ABI is the one representation every generator here already shares. Which is what lets, e.g., a C++ host load a Rust plugin under this scheme. Theoretically this could be added to the other generators as well.

@tschneidereit

Copy link
Copy Markdown
Member

@cpetig I know you've done similar work, so it'd be great if you could weigh in here as well.

@cpetig

cpetig commented Aug 21, 2026 •

Copy link
Copy Markdown
Collaborator

Wow, good to see growing interest in applying the component model to native binaries.

I have a long living fork of wit-bindgen at https://github.com/cpetig/wit-bindgen/ which adds

  • hex encoding of the colorful wasm-link names (as native symbols are more constrained on ":/[]" characters) - should become standardized in something akin to Add BuildTargets.md WebAssembly/component-model#378
  • removing the unreachable!
  • introducing a symmetric ABI mode which enables directly connecting exports to imports (needs one renamed when importing and exporting the same interface from the same module).
    • a matching test runner for native with symmetric ABI covering a large fraction of the runtime tests
    • this also covers streams and futures with a matching runtime library for POSIX.
    • a dll import linking stub generator which removes the need to link component dlls in dependency graph order
  • some more unrelated niceties (including a now unmaintained mode to connect native canonical components by a "host" mesh, wamr host code)

The symmetric ABI is what makes the real difference in usability as the (also mental) overhead for (maintaining) a host mode (mesh) code generator is significant.

I guess we should have a discussion about standardizing native names in a BuildTarget.md, potentially about symmetric ABI and then merge our efforts. I simply dispersed myself in too many parallel activities over the past year to get it approved and merged upstream until now. Honestly the pain was too low to make this high priority.

@cpetig cpetig left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I feel that the amount of code changes can be reduced by making the native symbol name generation and target-conditional linker attributes the default behavior. This is how I would solve this.

Comment thread crates/guest-rust/src/lib.rs Outdated
/// // still does; use `type_section_suffix` to tell them apart. See
/// // `wit_bindgen_rust::Opts::link_native_symbols` for the full list of
/// // symbols a host can expect.
/// link_native_symbols: true,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(written before I saw that your code is likely the same when the native flag is turned on)

I think my approach of using conditional compilation to choose between both naming schemes at compilation time of identical generated code is preferable. See e.g. https://github.com/cpetig/wit-bindgen/blob/work-in-progress/crates/cpp/tests/symmetric_future/future/src/future_world.rs#L17-L24 for an example.

Comment thread crates/rust/src/interface.rs Outdated
/// `#`, `[` and `]` characters that canonical names contain. Names that
/// survive encoding unchanged (`$root` exports, for instance) are emitted
/// once with no `cfg` rather than twice.
fn core_export_symbols(&self, export_name: &str) -> Vec<(&'static str, String)> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ok, I see, this is additive, so the generated code is unchanged in the default case.

I will leave this decision to Alex, but for my branch I decided that it should become the default, simplifying the generation logic and making the flag unnecessary. (It only adds visual clutter to the generated code, no runtime effects)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I pushed some changes that now make it the default as well.

@BjornTheProgrammer

Copy link
Copy Markdown
Author

@cpetig thank you for the review!

I hadn't realized you had a fork tackling this issue as well, and I would love to combine our work. We're already aligned on naming (the hex encoding this PR moves into wit-bindgen-core is the same scheme your C++ generator uses).

I've also taken your suggestion. Native symbol name generation and target-conditional linker attributes are now the default behavior, and the link_native_symbols option is gone entirely. Exports are emitted once with paired cfg_attr(target_arch = "wasm32", ...) export names. The one place I deliberately differ from your fork is imports. Instead of undefined externs, the old unreachable!() slot is filled with a dispatch through a _wit_bindgen_register(func: unsafe extern "C" fn(...)) hook. That keeps the default free for existing users (bindings crates still build and link everywhere on host targets. No import stub libraries needed, and calling an unregistered import aborts with a message, same as the stub did), and it's what makes runtime-discovered plugins work: a dlopen'd cdylib can't resolve import symbols against the host executable without -rdynamic on Linux, and can't on Windows at all, which is why I abandoned #1565. The hooks are convention-agnostic. They just carry a core-signature function pointer, so a symmetric mode could register through the same mechanism later.

On the symmetric ABI, I had considered something similar for this PR, but I was worried about whether something requiring maintenance at that scale would be accepted, which is why I settled on the existing wasm32 ABI. It also helped that wasmtime's bindgen existed as a reference for making a fully working native host runtime (native-wit). A lot of our changes are shared regardless. Would it make sense to land this PR as the common substrate (standardized names, no stubs, a portable dispatch mechanism), and then adopt the symmetric ABI as an opt-in mode once the BuildTargets.md discussion stabilizes? Happy to help draft the naming section there. The encoding and hook naming in this PR could serve as input.

@BjornTheProgrammer

Copy link
Copy Markdown
Author

Hmm seems like a single unit test fails due to a naming conflict after my changes. I just had an idea how to fix this that might make naming resolution never a problem and also simplify the rest of the code. I'll probably push some changes tomorrow, but for now I would love to hear your input!

Comment thread crates/guest-rust/src/rt/mod.rs
@BjornTheProgrammer

Copy link
Copy Markdown
Author

I've made some changes to how I register exports. Additionally I've added support for async, futures, and streams in native. I also made an end-to-end test to validate that nothing breaks between versions using libloading. Please let me know what you think about it.

@yunuservices

Copy link
Copy Markdown

Um, is there any progress in this PR? I noticed the last commit was a week ago, I'm just wondering.

@BjornTheProgrammer

Copy link
Copy Markdown
Author

Um, is there any progress in this PR? I noticed the last commit was a week ago, I'm just wondering.

I've made all of the changes that I felt were needed, and the CI just failed a single test because I think it timed out. Should be ready for review.

@QuantumSegfault

Copy link
Copy Markdown
Contributor

@cpetig @alexcrichton

What's the verdict on this? Still interested in having this?

@alexcrichton

Copy link
Copy Markdown
Member

Apologies I've definitely let this languish and haven't been attentive.

Overall on this I feel like I'm missing something pretty fundamental or not understanding something, and by missing that I'm wary to merge this in and try to maintain it. Having tests is definitely good, and I agree this isn't the biggest change ever, but despite these parts I'm still not entirely sure what to do about this. One thing I'm particulary wary of is what this test is doing internally which I'm assuming is roughly what external consumers would also be doing -- the resolve-style function is seemingly quite unsafe as there's no type-checks that the ABI requested is the one of the function that's given out. The test here has a handwritten log and add function for example and it seems like you just have to be super careful to get that correct. I suppose what I sort of expected was everything being 99% safe with some sort of unsafe thing in the middle where you'd assert "yes both halves are using the same WIT" or something like that, but this seems to not be shaped towards that.

Another piece I haven't thought too carefully about is all the memory management/ownership/lifetimes w.r.t the canonical ABI and these functions. For example wasm exports receiving a string inherently expect an owned allocation to be received, but when calling a wasm import that takes a string no allocation is made. This is part of the implicit assumption that something else is what's translating between addresss spaces, but by putting everything in the same process that's breaking a pretty fundamental assumption. At a skim of this PR I'm not seeing where this is handled for example and where the export either no longer assumes it owns the memory or the caller allocates separate memory.

I guess put a bit more broadly everything about WIT and wit-bindgen and such has so far been developed without much heed to a use case like native plugins or native dynamic libraries. I'm wary of breaking this foundational assumption which can have rippling effects far beyond just some tests in this repo or similar. Now there's no really any way to quantify this one way or another, so I'm mostly just trying to explain my rationale of being wary -- I don't think it's possible for me to not be wary of a change like this but that doesn't mean it can't happen.


That's not really an answer for a verdict per-se, but I'm also not really equipped to say "definitely yes" or "definitely no" to a change like this. I can try to help out and explain some of the concerns/wariness I have and see how this takes shape over time, but I definitely don't have a picture in my head of "if you did this it'd be landable" for example.

@BjornTheProgrammer

Copy link
Copy Markdown
Author

@alexcrichton No worries, and thanks for writing all this out!

the resolve-style function is seemingly quite unsafe as there's no type-checks that the ABI requested is the one of the function that's given out

I think I explained the split badly in the PR. This PR is only the guest side. The test hand-writes log and add because it's utilizing the raw ABI directly, kind of like testing the guest's wasm output without pulling in wasmtime. Anything touching the canonical ABI directly is going to be unsafe, and I don't expect anyone to actually write host code like that test.

The safe part is supposed to live in a separate host crate, the same way wasmtime does for wasm. That's what native-wit is. It has a bindgen! macro that generates the host side from the same WIT, so nobody writes ABI functions by hand. I think it ends up pretty close to what you described, with one unsafe call and everything else safe.

Note that native-wit is still relatively unpolished, and more of a proof of concept then what the actual final product would look like.

// the one unsafe call: "this is a trusted plugin for this world"
let plugin = unsafe { native::Plugin::new(&path)? };

// safe from here on, same API as wasmtime
plugin.call_init_plugin(&mut store).await?;

After loading the library, Plugin::new checks for the world marker symbol that this PR emits on the guest side, before it installs the import resolver or calls anything. If the library was built for a different world you get an error instead of memory corruption. It also looks up every export, post-return function, and the allocator right away, so missing symbols show up at load time. The import resolver is generated from the same WIT as the host bindings, so if both sides were built from the same WIT the signatures line up. If the host doesn't provide an import, the guest aborts with a message saying which one.

There's one hole I know about: the marker only covers the package name, version, and world name, not the actual WIT contents. So if someone changes an interface without bumping the version, an old plugin still passes the check. I can switch the marker to include a hash of the world in this PR, which would make that a load-time error too.

At a skim of this PR I'm not seeing where this is handled for example and where the export either no longer assumes it owns the memory or the caller allocates separate memory.

This PR doesn't change anything about ownership on the guest side. The generated code assumes exactly what it does on wasm. The "something else translating between address spaces" still exists, it's just the native host now instead of wasmtime. In native-wit:

  • When the host calls an export, it allocates the arguments with the plugin's own cabi_realloc and copies them in, so the export gets an owned allocation like it expects. After lifting the result it calls the cabi_post_* function so the plugin frees its own return value.
  • When the plugin calls an import, it passes borrowed pointers like on wasm, and the host copies the data out. Anything the host returns gets allocated with the plugin's cabi_realloc.

So nothing ever gets freed by a different allocator than the one that allocated it, which also matters because every cdylib has its own copy of std and its own global allocator. Everything being in one process doesn't really break the model, since the host still treats the plugin's memory as the plugin's, same as wasmtime does with linear memory.

You're right that none of this is written down in the PR. I wrote it up in the native-wit README and can add a shorter version here as the contract a native host needs to follow.

I'm wary of breaking this foundational assumption which can have rippling effects far beyond just some tests in this repo or similar.

Yeah, that makes sense. What I'd say is that this PR doesn't change the guest-side model at all. The canonical ABI and ownership rules stay the same. What's new is the symbol naming (hex encoding, same scheme as @cpetig's fork), the world marker, exporting the allocator, and the resolver hook replacing the unreachable!() stubs. Anything more native-specific than that lives in native-wit, not here.

For a real-world check, Pumpkin has a pretty big wasmtime + wit-bindgen plugin API, and these are all the changes it took to load native plugins next to wasm ones. The existing Host impls are reused as-is, and plugins build for either target from the same code.

I definitely don't have a picture in my head of "if you did this it'd be landable"

Somethings that could be done to make this more landable is make the world marker include a hash of the WIT contents, put the native output behind an experimental off-by-default option if you'd rather it not be the default, and/or keep all the host-side generation in native-wit so the maintenance cost here is just the guest changes.

Would that be enough for you to feel ok maintaining it? If you'd rather wait until native naming gets standardized (e.g. WebAssembly/component-model#378), I'm also happy to help push that along with @cpetig.

@alexcrichton alexcrichton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok thanks for writing all that out! That seems to me like a reasonable middle-ground here. I do think it'd be good to have some long-form documentation about how native is intended to work and what's there, but that's fine to live either in this repo or in your repo externally since that's where the primary development point would be.

Otherwise though I've got some comments below, but the main one is that I'm wondering if the generator changes can be simplified by using the helper macro that the rt module itself uses in the generated code since that'd encapsulate the implementation details and such.

Comment on lines +87 to +98

/// Native counterpart of the C-defined `wasip3_task_set` above. Uses
/// thread local so there is no possible panic on multi thread when polling
/// two async exports at the same time.
#[cfg(all(not(target_family = "wasm"), feature = "std"))]
pub unsafe fn wasip3_task_set(ptr: *mut wasip3_task) -> *mut wasip3_task {
use core::cell::Cell;
std::thread_local!(
static CURRENT: Cell<*mut wasip3_task> = const { Cell::new(core::ptr::null_mut()) }
);
CURRENT.with(|current| current.replace(ptr))
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

FWIW the purpose of this symbol on wasm was to specifically have a weak definition to handle cases where imports are using one version of wit-bindgen and exports are using another. I suspect that'll come up less rarely for your native use case, but perhaps something to consider.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

i believe it can also happen with set_import_resolver and cabi_realloc which cause a duplicate symbol error

Comment on lines +100 to 108
/// Without `std` there are no thread-locals on stable Rust, so this falls
/// back to one global slot, which requires the host to not poll two async
/// exports at the same time.
#[cfg(all(not(target_family = "wasm"), not(feature = "std")))]
pub unsafe fn wasip3_task_set(ptr: *mut wasip3_task) -> *mut wasip3_task {
use core::sync::atomic::{AtomicPtr, Ordering};
static CURRENT: AtomicPtr<wasip3_task> = AtomicPtr::new(core::ptr::null_mut());
CURRENT.swap(ptr, Ordering::AcqRel)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm kind of hesitant to do this sort of fallback because sometimes it's appropriate and sometimes not. Would it be possible to somehow hook this up into the resolver for native?

Comment on lines +50 to +62
static CACHE: ::core::sync::atomic::AtomicPtr<()> =
::core::sync::atomic::AtomicPtr::new(::core::ptr::null_mut());
// Named so as not to shadow any parameter.
let mut __impl = CACHE.load(::core::sync::atomic::Ordering::Acquire);
if __impl.is_null() {
__impl = crate::rt::resolve_import(
crate::rt::native_imports::cstr(concat!($module, "\0")),
crate::rt::native_imports::cstr(concat!($name, "\0")),
);
CACHE.store(__impl, ::core::sync::atomic::Ordering::Release);
}
let __func: unsafe extern "C" fn($($ty),*) $(-> $ret)? =
unsafe { ::core::mem::transmute(__impl) };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To reduce the size of the macro expansion here a bit, could this be restructured as:

fn foo() {
    static CACHE: ... = ...;
    let ptr = $crate::rt::resolve_import(module, name, &CACHE);
    unsafe { transmute::<A, B>(ptr)(...) }
}

basically moving the if and load/store to the rt module where resolve_import could have a fast/cold path similar to what the macro has today to basically generate the same code

return ptr;
}

#[cfg(not(target_arch = "wasm32"))]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Mind switching to target_family = "wasm"? I've been meaning to do that for the whole crate for some time and just haven't gotten around to it.

Comment on lines +38 to +49
#[unsafe(no_mangle)]
pub unsafe extern "C" fn __wit_bindgen_set_import_resolver(
resolver: Option<ImportResolver>,
ctx: *mut (),
) {
let resolver = match resolver {
Some(resolver) => resolver as *mut (),
None => core::ptr::null_mut(),
};
RESOLVER_CTX.store(ctx, Ordering::Relaxed);
RESOLVER.store(resolver, Ordering::Release);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it be possible to not worry about no_mangle here and let external callers do that? I'm otherwise a bit worried about mixing multiple versions of wit-bindgen and having the symbols clash

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Otherwise though the ownership/uninstallation/synchronization/etc story here is a bit suspect -- could this move to being under a lock?

For example some possible issues with this current protocol are:

  • Concurrent invocations of setting the resolve can corrupt the resolver/ctx static
  • Uninstalling a resolver doesn't mean it's not in use by other threads

Comment on lines +64 to +65
let module_display = module.to_string_lossy();
let name_display = name.to_string_lossy();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems like some care is taken for efficiency to move the module/name around as a CStr as opposed to a &str which is relatively unidiomatic, but here ther're immediately turned into Rust strings which is a pretty heavyweight operation and additionally seems to sort of defeat the purpose of optimizing things here. Is the intentiona that this doesn't happen in typical execution?

/// `component-type` custom section on wasm: binding the same world twice in
/// one binary needs a `type_section_suffix` (and `export_prefix` for the
/// export names).
///

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm a bit wary to document too much here because this crate is sort of only half the story. Could the section here be generally reworded to say "things compile, but won't work by default. If you want things to work go to this URL over here for more information" where that URL is either one you host or some *.md in this repo

Comment on lines +728 to +737
// Natively the intrinsics are resolved through the host's import
// resolver, exactly like every other import.
let rt = self.r#gen.runtime_path();
let handle = || ("handle".to_string(), "u32");
let mut extra: Vec<(String, &str)> = Vec::new();
if let PayloadFor::Stream = payload_for {
extra.push(("amt".to_string(), "usize"));
}
let mut native_intrinsics = String::new();
for (rust_name, core_name, params, result) in [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead of hand-writing this, could this maybe be redone by exporting the helper macro in the wit-bindgen crate (probably as #[doc(hidden)]) and then that's used here?

Comment thread crates/rust/src/lib.rs
Comment on lines +553 to +578
/// Emits the world's native marker symbol, `__wit_bindgen_world_<world>`,
/// which hosts look up before calling anything else to check that the
/// library they opened implements the world they expect. It's keyed on
/// the world name and `type_section_suffix`, like the `component-type`
/// section on wasm.
fn finish_native_world_marker(&mut self, resolve: &Resolve, world: WorldId) {
let world = &resolve.worlds[world];
let pkg = world
.package
.map(|p| resolve.packages[p].name.to_string())
.unwrap_or_default();
let name = format!("{pkg}/{}", world.name);
let suffix = self.opts.type_section_suffix.as_deref().unwrap_or("");
let symbol = symbol_name::make_external_component(&format!("{name}{suffix}"));
uwriteln!(
self.src,
r#"
#[cfg(not(target_arch = "wasm32"))]
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub extern "C" fn __wit_bindgen_world_{symbol}() -> *const ::core::ffi::c_char {{
c"{name}".as_ptr()
}}
"#
);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So for this, ideally what this would do would be to return the wasm-world-encoded-as-WIT, much like the custom section present for wasm today. Whether that's a function or a section in the object I don't mind either way, but in the long run you'll probably want the full WIT type information as opposed to just the world name.

Comment on lines +13 to +15
# Deliberately not a member of the wit-bindgen workspace: `tests/native_e2e.rs`
# builds this crate with a nested `cargo build` and `dlopen`s the result.
[workspace]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maintaining multiple Cargo.locks and such, or not having one here, is something that I'm a bit worried about in terms of flaky CI failures. I'm also a bit worried from earlier about the degree of unsafety w.r.t. how the host side of the test is written (which is autogenerated mostly you say on your side).

Given that, what would you think about either removing this test or reducing its scope? For example asserting it can be compiled would be fine, but actually running the output seems like it sort of best needs the other half of running things which lives elsewhere. If that becomes problematic over time we can probably try to figure out some other scheme, but I'm hopeful that things aren't changing too rapidly such that if you've tested this PR then future PRs have a low chance of breakage.

This branch has not been deployed

No deployments
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.

7 participants