Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error linking to function that takes a GC array ref. #9848

Open
Tracked by #5032
viridia opened this issue Dec 18, 2024 · 7 comments
Open
Tracked by #5032

Error linking to function that takes a GC array ref. #9848

viridia opened this issue Dec 18, 2024 · 7 comments
Assignees
Labels
bug Incorrect behavior in the current implementation that needs fixing wasm-proposal:gc Issues with the implementation of the gc wasm proposal wasmtime:ref-types Issues related to reference types and GC in Wasmtime

Comments

@viridia
Copy link

viridia commented Dec 18, 2024

I'm getting this error:

types incompatible: expected type `(func (param (ref array (engine 5))))`, found type `(func (param (ref array)))`

The wasm looks like this:

(type $String (;3;) (array i8))
(type $debug.type (;4;) (func (param (ref $String))))
(import "host" "debug" (func $debug (;0;) (type $debug.type)))

And I'm registering the function like this:

let engine = wasmtime::Engine::new(&config).unwrap();
let store = wasmtime::Store::<StoreData>::new(&engine, ());
let mut linker = wasmtime::Linker::new(&engine);
linker
    .func_wrap(
        "host",
        "debug",
        |caller: Caller<'_, StoreData>, param: Rooted<ArrayRef>| {
            // ...
        },
    )
    .unwrap();

Test Case

(module $hello.saga
  (type $hello.type (;0;) (func (result i32)))
  (type $add.type (;1;) (func (param i32) (result i32)))
  (type $name.type (;2;) (func))
  (type $String (;3;) (array (mut i8)))
  (type $debug.type (;4;) (func (param (ref $String))))
  (import "host" "debug" (func $debug (;0;) (type $debug.type)))
  (export "hello" (func $hello))
  (func $hello (;1;) (type $hello.type) (result i32)
    i32.const 1
    call $add
    i32.const 2
    i32.add
    f32.const 0x1.8p+1 (;=3;)
    i32.trunc_f32_s
    i32.add
    return
  )
  (func $add (;2;) (type $add.type) (param i32) (result i32)
    (local i32)
    i32.const 1
    local.set 1
    local.get 0
    local.get 1
    i32.add
    return
  )
  (func $name (;3;) (type $name.type)
    i32.const 0
    i32.const 15
    array.new_data $String 0
    call $debug
  )
  (data (;0;) "Hello from SAGA")
)

Steps to Reproduce

Create an Engine and Linker.
Bind a function which accepts an ArrayRef as a parameter.
Attempt to load the script.

Expected Results

Script should load without error (it loads if I delete the code that calls the host func).

Actual Results

Error when I attempt to instantiate the script.

Versions and Environment

Wasmtime version or commit: { version = "27.0.0", features = ["gc", "gc-drc"] }

Operating system: OS X

Architecture: M2

@viridia viridia added the bug Incorrect behavior in the current implementation that needs fixing label Dec 18, 2024
@fitzgen
Copy link
Member

fitzgen commented Dec 18, 2024

Thanks for filing an issue. I won't have time to look into this for a while, unfortunately, but I appreciate getting it on the record for posterity.

@fitzgen fitzgen added wasmtime:ref-types Issues related to reference types and GC in Wasmtime wasm-proposal:gc Issues with the implementation of the gc wasm proposal labels Dec 19, 2024
Copy link

Subscribe to Label Action

cc @fitzgen

This issue or pull request has been labeled: "wasmtime:ref-types"

Thus the following users have been cc'd because of the following labels:

  • fitzgen: wasmtime:ref-types

To subscribe or unsubscribe from this label, edit the .github/subscribe-to-label.json configuration file.

Learn more.

@fitzgen
Copy link
Member

fitzgen commented Dec 19, 2024

FWIW, this is probably related to the WasmTy implementation for ArrayRef

@fitzgen-f5
Copy link

Hm so I converted this into a test in tests/all/arrays.rs:

#[test]
fn instantiate_with_func_that_takes_array() -> Result<()> {
    let mut store = gc_store()?;

    let module = Module::new(
        store.engine(),
        r#"
            (module
                (type $String (;3;) (array i8))
                (type $debug.type (;4;) (func (param (ref $String))))
                (import "host" "debug" (func $debug (;0;) (type $debug.type)))
            )
        "#,
    )?;

    let mut linker = wasmtime::Linker::new(store.engine());
    linker.func_wrap("host", "debug", |_: Caller<'_, ()>, _: Rooted<ArrayRef>| {})?;

    let _instance = linker.instantiate(&mut store, &module)?;
    Ok(())
}

And I am indeed reproducing the error:

Error: incompatible import type for `host::debug`

Caused by:
    types incompatible: expected type `(func (param (ref array (engine 0))))`, found type `(func (param (ref array)))`

However, I'm not convinced that this is actually incorrect behavior. The type of the wrapped function is (func (param (ref array))) which indeed is not a subtype of (func (param (ref array $String))) and so the MatchCx used to typecheck instance linking is correctly yielding a type error.

If we make $debug.type non-final (which it implicitly was originally) and switch from using linker.func_wrap to Func::new and explicitly provide a function whose type is a subtype of $debug.type, then the test does indeed pass:

#[test]
fn instantiate_with_func_that_takes_array() -> Result<()> {
    let mut store = gc_store()?;

    let module = Module::new(
        store.engine(),
        r#"
            (module
                (type $String (;3;) (array i8))
                (type $debug.type (;4;) (sub (func (param (ref $String)))))
                (import "host" "debug" (func $debug (;0;) (type $debug.type)))
            )
        "#,
    )?;

    let mut linker = wasmtime::Linker::new(store.engine());

    let string_ty = ArrayType::new(
        store.engine(),
        FieldType::new(Mutability::Const, StorageType::I8),
    );
    let sup_ty = FuncType::with_finality_and_supertype(
        store.engine(),
        Finality::NonFinal,
        None,
        [ValType::Ref(RefType::new(
            false,
            HeapType::ConcreteArray(string_ty),
        ))],
        None,
    )?;
    let sub_ty = FuncType::with_finality_and_supertype(
        store.engine(),
        Finality::NonFinal,
        Some(&sup_ty),
        [ValType::Ref(RefType::new(false, HeapType::Array))],
        None,
    )?;
    linker.func_new("host", "debug", sub_ty, |_caller, _args, _rets| Ok(()))?;

    let _instance = linker.instantiate(&mut store, &module)?;
    Ok(())
}
test arrays::instantiate_with_func_that_takes_array ... ok

That is all technically correct, but I'm not sure if it is what we ultimately want. It seems very reasonable to want to write code like in the OP, even if it technically doesn't match subtyping rules (but would if the types involved were non-final and declared to be subtypes; basically those that structurally match but are not actually declared subtypes).

One extra twist is that I think we actually used to allow this kind of thing, but I think I removed it when getting the GC wast tests passing, since (IIRC) there were some assert_unlinkable pragmas exercising this stuff. Makes sense to disallow in that context, but maybe not in the context of a wasmtime::Linker in the host? Not totally convinced either way.

@alexcrichton
Copy link
Member

I always forget the directionality of subtyping but I'm confused why this doesn't pass the subtyping check. If I ask for (ref array) and you give me (ref $specific_array) isn't that ok? Shouldn't the functions be subtypes here? (or is the "final" bit coming into play, I have less of a grasp on that)

Otherwise though I'd agree that we want something Func::wrap-related to work here. That's much more efficient, works better with bindings generators, and is generally more ergonomic. How best to thread that needle though I'm not sure either.

@fitzgen
Copy link
Member

fitzgen commented Jan 23, 2025

The issue is twofold:

First, that the wrapped function's type, when fully desugared, is

(sub final (func (param (ref array))))

which does not declare itself to be a subtype of $debug.type and therefore is not a subtype of $debug.type even if the underlying parameter and result types match.

And second, that

(type $debug.type (func (param (ref $String)))

desugars to a final type

(type $debug.type (sub final (func (param (ref $String)))))

so no other function type can be a subtype of $debug.type as written, even if it tried.

This all makes sense inside the world of pure Wasm, but from a host API and developer niceties point of view, it becomes pretty frustrating and leads to bad developer experience :-/

@alexcrichton
Copy link
Member

Ah I see, I was mistakenly thinking about the (sub ...) in the wrong place!

I thought we had something though which was testing that as long as the top type was the same then the host-level type-check passed? And then we optionally included a runtime type-check if the actual type was different from the top type? I'm probably misremembering here...

Another option perhaps is to do something different for wasm gc than we do for normal scalars. Some sort of argument passed to Func::wrap_new_name which is acquired via constructing the type or parsing it out somehow? Maybe that's just a slightly more ergonomic Func::new in that case ...

I suppose put another way one thing we could do is to change Func::new to take any closure, not just the signature today. The function type is then used to perform a runtime type-check against the closure's static type and for today's signature it'd be a noop but you could pass a more specialized signature like Func::wrap. That specialized signature would have a slightly different type-check than the subtyping checks we have today?

In any case I'm just musing here...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Incorrect behavior in the current implementation that needs fixing wasm-proposal:gc Issues with the implementation of the gc wasm proposal wasmtime:ref-types Issues related to reference types and GC in Wasmtime
Projects
None yet
Development

No branches or pull requests

4 participants