Skip to content

[C#] Add support for the ping pong test, bring support for futures that return strings, i.e. types returned via pointers.#1606

Open
yowl wants to merge 3 commits intobytecodealliance:mainfrom
yowl:csharp-ping-pong
Open

[C#] Add support for the ping pong test, bring support for futures that return strings, i.e. types returned via pointers.#1606
yowl wants to merge 3 commits intobytecodealliance:mainfrom
yowl:csharp-ping-pong

Conversation

@yowl
Copy link
Copy Markdown
Collaborator

@yowl yowl commented Apr 28, 2026

As per the title, this PR adds more support to the C# wit-bindgen to get the ping-pong test to pass. As part of this I've:

  • Added more things to the VTable: size, alignment, lower and lift.
  • Added support for lifting and lowering, tested for strings only.
  • Removed ICancelableRead and ICancelableWrite and replaced with IVTable
  • Removed the FreeBuffer method and replaced with cleanup actions

Requires supporting futures that return strings.
@yowl yowl force-pushed the csharp-ping-pong branch from f95033f to 7f6a5b1 Compare April 29, 2026 00:21
@yowl yowl changed the title [C#] WIP Csharp ping pong [C#] Add support for the ping pong test, bring support for futures that return strings, i.e. types returned via pointers. Apr 29, 2026
@yowl yowl requested review from dicej and silesmo April 29, 2026 01:27
@yowl yowl marked this pull request as ready for review April 29, 2026 01:27
Copy link
Copy Markdown
Collaborator

@dicej dicej left a comment

Choose a reason for hiding this comment

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

Thanks for continuing to push this forward!

Comment thread crates/csharp/src/AsyncSupport.cs Outdated
public static unsafe uint Callback(EventWaitable e, ContextTask* contextPtr)
public static unsafe int Callback(EventWaitable e, ContextTask* contextPtr)
{
Console.WriteLine("Callback");
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.

Looks like a stray debug print statement.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

removed

Comment thread crates/csharp/src/AsyncSupport.cs Outdated
if (e.SubtaskStatus.IsStarted || e.SubtaskStatus.IsReturned)
{
waitableInfoState.SetResult(e.WaitableCount);
Interop.SubtaskDrop(e.Waitable);
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'm surprised to see a SubtaskDrop call here for e.SubtaskStatus.IsStarted. Don't we want to wait until it reaches the e.SubtaskStatus.IsReturned state?

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.

Also, the line above seems like it should only apply to e.SubtaskStatus.IsReturned as well.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ah, yes, I misread the go equivalent, not realising it doesn't use a break statement between cases.

Comment thread crates/csharp/src/AsyncSupport.cs Outdated
}

// unsafe because we are using pointers.
public static unsafe int Callback<T>(EventWaitable e, Func<T> liftFunc)
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.

There's a lot of code duplicated between the two overloads of Callback. Would it be practical to combine them, e.g. by having the non-generic one defer to the generic one using a dummy type of some sort?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Turns out that we don't need the generic version at all as the lift func parameter is not used here. Was an idea that later got dropped.

}

// TODO: this is not great, we want the underlying parameter, but it is passed in operands already lifted.
pub fn strip_lift(lifted_param: &String) -> 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.

I'll admit I don't understand why this is needed or what it is doing. Could you elaborate?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

ah yes, I meant to talk about this in the description. This stems from the chat we had and the idea to use an async function for the exported function. In C# UnmanagedCallersOnly functions cannot be marked async so what I did was from the UCO function, we call to an async function so we can do the cleanups easily. Without this strip_lift function, that pattern ends up like this

[global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(EntryPoint = "[async-lift]a:b/i#one-argument")]
public static unsafe int wasmExportOneArgument(int p0) {

    var task = OneArgumentAsync((unchecked((uint)(p0))));
    if (task.IsCompletedSuccessfully)
    {
        return (int)CallbackCode.Exit;
    }

    // TODO: Defer dropping borrowed resources until a result is returned.
    ContextTask* contextTaskPtr = AsyncSupport.ContextGet();

    return (int)CallbackCode.Wait | (int)(contextTaskPtr->WaitableSetHandle << 4);
}

public static async Task OneArgumentAsync(uint unchecked((uint)(p0)))
{
    var cleanups = new global::System.Collections.Generic.List<global::System.Action>();

    await IExportsImpl.OneArgument((unchecked((uint)(p0))));
    // TODO: task_cancel.forget();
    OneArgumentTaskReturn();

    foreach (var cleanup in cleanups)
    {
        cleanup();
    }

}

The signature to OneArgumentAsync has the lifted parameter expression and is not valid.

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 see, thanks for explaining. Perhaps you could expand the comment to include an example of what the input and output of the regex would be, e.g. unchecked((uint)(p0))->p0.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

done.

Comment thread crates/csharp/src/interface.rs Outdated
[global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(EntryPoint = "[callback]{export_name}")]
public static unsafe uint {camel_name}Callback(int eventRaw, uint waitable, uint code)
{{
Console.WriteLine("Callback for {export_name} creating EventWaitable with code " + eventRaw + " for waitable " + waitable + " with code " + code);
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.

Did you mean to leave this in here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Removed this and other debugs

| Type::Char
| Type::F32
| Type::F64 => false,
_ => 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.

FYI, this will probably need to be refined; e.g. resource handles and other types may not need pointers either.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, added a TODO for now.

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.

2 participants