Skip to content

Referencing a project that exposes WIT bindings gives linker error #92

Description

@lukechu10

Background: I am interested in compiling F# to a WASM component. So far, I've managed to get a simple WASI "Hello, world!" program compiling and running using componentize-dotnet. For the next step, I wanted to access host bindings exposed via a WIT file. Since wit-bindgen generates C#, and not F# source files, it does not seem like I can do this directly from F#.

What I have experimented so far is creating a new C# Project which imports the wit file, and then reference this C# project from F#. With this setup, the auto-generated host bindings seem to be accessible to F#. However, I get some kind of link error when trying to build the project:

error: failed to encode component

Caused by:
    0: failed to decode world from module
    1: module was not valid
    2: no top-level imported function `foobar` specified
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Here, foobar is a function that I am exposing from my wit file:

package example:host;
world host {
    import foobar: func ();
}

This error only occurs when actually calling HostWorld.exports.HostWorld.Foobar () somewhere from the F# project or from the C# project.

Is there some combination of configurations that could make this setup work? If there is, it would be nice for this to be documented somewhere since I could not find any relevant information.

Environment:

  • BytecodeAlliance.Componentize.DotNet.Wasm.SDK: 0.7.0-preview00010
  • runtime.linux-x64.microsoft.dotnet.ilcompiler.llvm: 10.0.0-alpha.1.25162.1
  • OS: Linux x64
  • .NET: 10.0.100-preview.6

Activity

  1. lukechu10 commented on Aug 19, 2025

    @lukechu10
    Author

    After some digging around, it seems like the error is because this flag is not being set for the linker:

    <CustomLinkerArg Include="@(WitComponentImports->Replace('\', '/')->'-Wl,--component-type,&quot;%(Identity)&quot;')" />

  2. jsturtevant commented on Aug 20, 2025

    @jsturtevant
    Contributor

    Is there some combination of configurations that could make this setup work? If there is, it would be nice for this to be documented somewhere since I could not find any relevant information.

    There aren't any docs or tests currently. We could probably add a test in https://github.com/bytecodealliance/componentize-dotnet/tree/main/test that demonstrates this pattern.

  3. lukechu10 commented on Aug 20, 2025

    @lukechu10
    Author

    OK I think I've managed to find a somewhat hacky solution.

    Basically create two C# projects. The first project is kept empty and the second project references the first project. In the second project, we can then use componentize-dotnet to generate the wit bindings, but we set the generated files root to be in the first project. So something like this:

    <PropertyGroup>
      ...
      <WitGeneratedFilesRoot>../Library/generated/wit</WitGeneratedFilesRoot>
    </PropertyGroup>
    
    <ItemGroup>
      <PackageReference Include="BytecodeAlliance.Componentize.DotNet.Wasm.SDK" Version="0.7.0-preview*" />
      <PackageReference Include="runtime.linux-x64.microsoft.dotnet.ilcompiler.llvm" Version="10.0.0-alpha.1.25162.1" />
    
      <ProjectReference Include="../Library/Library.csproj" />
    </ItemGroup>

    This way, the bindings are in the first project, but since we are building the second project, the proper linker arguments are set.


    This works with two C# projects but if we want to create a WASM component with F#, this doesn't work because the F# project refuses to call wit-bindgen. So to get F# working, what we can do is create 3 projects:

    • Library is a C# project that is empty except for the automatically generated WIT bindings.
    • Component is a F# project that has a project reference to Library and can import the WIT bindings.
    • Driver is a C# project that has a project reference to Component, and generates the bindings in Library so that they are available to F# in Component, and also so that the proper linker arguments are set. The only code in Driver is to call some "start" entry-point function in the F# project.

    This approach seems to work pretty well if all we want to do is import WIT bindings, but I don't think it's possible to use this to export bindings directly from F#.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions