Skip to content

Commit 7544b80

Browse files
yowldicej
andauthored
C#: Add partial support for Futures and Streams (#1529)
* passes pendingimport test for c# * passes pendingimport test for c# * runtime async tests pass * fixes to make sync functions pass again. Some tidy * Remove debug WriteLines * some clean up * clippy * Make streams.wit compile * For streams use a StreamReader/StreamWriter pair so we can write something. Maybe we can go back to one pair of classes later. * add some stream support * clippy fmt * start clean up Fix stub params * enable another codegen test * format * some tidy/refactor * some tidy/refactor * refactor * resolve merge * clippy * merge conflict * remove partial * remove ws * remove unsafe * Apply suggestions from code review Co-authored-by: Joel Dice <joel.dice@akamai.com> * merge, enable test --------- Co-authored-by: Joel Dice <joel.dice@akamai.com>
1 parent 4a48c99 commit 7544b80

File tree

12 files changed

+1118
-279
lines changed

12 files changed

+1118
-279
lines changed

crates/csharp/src/AsyncSupport.cs

Lines changed: 590 additions & 110 deletions
Large diffs are not rendered by default.

crates/csharp/src/FutureCommonSupport.cs

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,56 @@
22
* Helpers for future support.
33
*/
44

5-
public readonly struct WaitableStatus (int status)
5+
public readonly struct WaitableStatus (uint status)
66
{
7-
public int State => status & 0xf;
8-
public int Count => (int)(status >> 4);
9-
public bool IsBlocked => status == -1;
7+
public uint State => status & 0xf;
8+
public uint Count => status >> 4;
9+
public bool IsBlocked => status == 0xffffffff;
1010
public bool IsCompleted => State == 0;
1111
public bool IsDropped => State == 1;
12+
public bool IsCancelled => State == 2;
1213
}
1314

14-
public enum EventCode
15+
public readonly struct SubtaskStatus (uint status)
1516
{
16-
None,
17-
Subtask,
18-
StreamRead,
19-
StreamWrite,
20-
FutureRead,
21-
FutureWrite,
22-
Cancel,
17+
public uint State => status & 0xf;
18+
public int Handle => (int)(status >> 4);
19+
public bool IsSubtaskStarting => State == 0;
20+
public bool IsSubtaskStarted => State == 1;
21+
public bool IsSubtaskReturned => State == 2;
22+
public bool IsSubtaskStartedCancelled => State == 3;
23+
public bool IsSubtaskReturnedCancelled => State == 4;
2324
}
2425

2526
public readonly struct EventWaitable
2627
{
27-
public EventWaitable(EventCode eventCode, int waitable, int code)
28+
public EventWaitable(EventCode eventCode, uint waitable, uint code)
2829
{
29-
Event = eventCode;
30-
Waitable = waitable;
31-
Status = new WaitableStatus(code);
30+
Console.WriteLine($"EventWaitable with code {code}");
31+
EventCode = eventCode;
32+
Waitable = (int)waitable;
33+
Code = code;
34+
35+
if(eventCode == EventCode.Subtask)
36+
{
37+
IsSubtask = true;
38+
SubtaskStatus = new SubtaskStatus(code);
39+
}
40+
else
41+
{
42+
WaitableStatus = new WaitableStatus(code);
43+
}
3244
}
33-
public readonly EventCode Event;
45+
46+
public readonly EventCode EventCode;
3447
public readonly int Waitable;
35-
public readonly int Code;
48+
public readonly uint Code;
3649

37-
public readonly WaitableStatus Status;
50+
public bool IsSubtask { get; }
51+
public readonly WaitableStatus WaitableStatus;
52+
public readonly SubtaskStatus SubtaskStatus;
53+
public readonly int WaitableCount => (int)Code >> 4;
54+
public bool IsDropped => !IsSubtask && WaitableStatus.IsDropped;
55+
public bool IsCompleted => IsSubtask && SubtaskStatus.IsSubtaskReturned || !IsSubtask && WaitableStatus.IsCompleted;
3856
}
3957

crates/csharp/src/function.rs

Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use heck::ToUpperCamelCase;
55
use std::fmt::Write;
66
use std::mem;
77
use std::ops::Deref;
8-
use wit_bindgen_core::abi::{self, Bindgen, Bitcast, Instruction};
8+
use wit_bindgen_core::abi::{Bindgen, Bitcast, Instruction};
99
use wit_bindgen_core::{Direction, Ns, uwrite, uwriteln};
1010
use wit_parser::abi::WasmType;
1111
use wit_parser::{
@@ -32,7 +32,7 @@ pub(crate) struct FunctionBindgen<'a, 'b> {
3232
is_block: bool,
3333
fixed_statments: Vec<Fixed>,
3434
parameter_type: ParameterType,
35-
result_type: Option<Type>,
35+
pub(crate) result_type: Option<Type>,
3636
pub(crate) resource_type_name: Option<String>,
3737
}
3838

@@ -384,7 +384,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
384384
ret
385385
}
386386

387-
fn emit_allocation_for_type(&mut self, results: &[WasmType]) -> String {
387+
pub fn emit_allocation_for_type(&mut self, results: &[WasmType]) -> String {
388388
let address = self.locals.tmp("address");
389389
let buffer_size = self.get_size_for_type(results);
390390
let align = self.get_align_for_type(results);
@@ -1030,40 +1030,23 @@ impl Bindgen for FunctionBindgen<'_, '_> {
10301030
}
10311031

10321032
Instruction::CallWasm { sig, .. } => {
1033-
let is_async = InterfaceGenerator::is_async(self.kind);
1034-
1035-
let requires_async_return_buffer_param = is_async && sig.results.len() >= 1;
1036-
let async_return_buffer = if requires_async_return_buffer_param {
1037-
let buffer = self.emit_allocation_for_type(&sig.results);
1038-
uwriteln!(self.src, "//TODO: store somewhere with the TaskCompletionSource, possibly in the state, using Task.AsyncState to retrieve it later.");
1039-
Some(buffer)
1040-
} else {
1041-
None
1042-
};
1043-
1033+
let mut result = String::new();
10441034
let assignment = match &sig.results[..] {
10451035
[_] => {
1046-
let result = self.locals.tmp("result");
1036+
result = self.locals.tmp("result");
10471037
let assignment = format!("var {result} = ");
1048-
if !requires_async_return_buffer_param {
1049-
results.push(result);
1050-
}
10511038
assignment
10521039
}
10531040

1054-
[] => String::new(),
1041+
[] => {
1042+
String::new()
1043+
}
10551044

10561045
_ => unreachable!(),
10571046
};
10581047

10591048
let func_name = self.func_name.to_upper_camel_case();
1060-
1061-
// Async functions that return a result need to pass a buffer where the result will later be written.
1062-
let operands = match async_return_buffer {
1063-
Some(ref buffer) if operands.is_empty() => buffer.clone(),
1064-
Some(ref buffer) => format!("{}, {}", operands.join(", "), buffer),
1065-
None => operands.join(", "),
1066-
};
1049+
let operands = operands.join(", ");
10671050

10681051
let (_namespace, interface_name) =
10691052
&CSharp::get_class_name_from_qualified_name(self.interface_gen.name);
@@ -1093,18 +1076,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
10931076
"{assignment} {interop_name}{resource_type_name}.{func_name}WasmInterop.wasmImport{func_name}({operands});"
10941077
);
10951078

1096-
if let Some(buffer) = async_return_buffer {
1097-
let result = abi::lift_from_memory(
1098-
self.interface_gen.resolve,
1099-
self,
1100-
buffer.clone(),
1101-
&self.result_type.unwrap(),
1102-
);
1103-
uwriteln!(
1104-
self.src,
1105-
"global::System.Runtime.InteropServices.NativeMemory.Free({});",
1106-
buffer
1107-
);
1079+
if sig.results.len() >= 1 {
11081080
results.push(result);
11091081
}
11101082
}
@@ -1177,9 +1149,21 @@ impl Bindgen for FunctionBindgen<'_, '_> {
11771149
{name}TaskReturn({ret_param});
11781150
return (uint)CallbackCode.Exit;
11791151
}}
1152+
1153+
ret.ContinueWith(t =>
1154+
{{
1155+
if (t.IsFaulted)
1156+
{{
1157+
// TODO
1158+
Console.Error.WriteLine("Async function {name} IsFaulted. This scenario is not yet implemented.");
1159+
throw new NotImplementedException("Async function {name} IsFaulted. This scenario is not yet implemented.");
1160+
}}
1161+
1162+
{name}TaskReturn({ret_param});
1163+
}});
11801164
11811165
// TODO: Defer dropping borrowed resources until a result is returned.
1182-
return (uint)CallbackCode.Yield;
1166+
return (uint)CallbackCode.Wait | (uint)(AsyncSupport.WaitableSet.Handle << 4);
11831167
"#);
11841168
}
11851169

@@ -1400,31 +1384,53 @@ impl Bindgen for FunctionBindgen<'_, '_> {
14001384
results.extend(operands.iter().take(*amt).cloned());
14011385
}
14021386

1403-
Instruction::FutureLower { payload, ty } => {
1387+
Instruction::FutureLower { payload, ty }
1388+
| Instruction::StreamLower { payload, ty }=> {
14041389
let op = &operands[0];
14051390
let generic_type_name = match payload {
14061391
Some(generic_type) => {
14071392
&self.interface_gen.type_name_with_qualifier(generic_type, false)
14081393
}
14091394
None => ""
14101395
};
1411-
self.interface_gen.add_future(self.func_name, &generic_type_name, ty);
1396+
1397+
match inst {
1398+
Instruction::FutureLower { .. } => {
1399+
self.interface_gen.add_future(self.func_name, &generic_type_name, ty);
1400+
}
1401+
_ => {
1402+
self.interface_gen.add_stream(self.func_name, &generic_type_name, ty);
1403+
}
1404+
}
14121405

14131406
results.push(format!("{op}.TakeHandle()"));
14141407
}
14151408

14161409
Instruction::AsyncTaskReturn { name: _, params: _ } => {
1417-
uwriteln!(self.src, "// TODO_task_cancel.forget();");
1410+
uwriteln!(self.src, "// TODO: task_cancel.forget();");
14181411
}
14191412

1420-
Instruction::FutureLift { payload, ty } => {
1413+
Instruction::FutureLift { payload, ty }
1414+
| Instruction:: StreamLift { payload, ty } => {
1415+
let generic_type_name_with_qualifier = match payload {
1416+
Some(generic_type) => {
1417+
&self.interface_gen.type_name_with_qualifier(generic_type, true)
1418+
}
1419+
None => ""
1420+
};
14211421
let generic_type_name = match payload {
14221422
Some(generic_type) => {
14231423
&self.interface_gen.type_name_with_qualifier(generic_type, false)
14241424
}
14251425
None => ""
14261426
};
14271427
let upper_camel = generic_type_name.to_upper_camel_case();
1428+
let bracketed_generic = match payload {
1429+
Some(_) => {
1430+
format!("<{generic_type_name_with_qualifier}>")
1431+
}
1432+
None => String::new()
1433+
};
14281434
// let sig_type_name = "Void";
14291435
let reader_var = self.locals.tmp("reader");
14301436
let module = self.interface_gen.name;
@@ -1435,16 +1441,29 @@ impl Bindgen for FunctionBindgen<'_, '_> {
14351441
.strip_prefix("I").unwrap()
14361442
.replace("Imports", "Exports"); // TODO: This is fragile and depends on the interface name.
14371443

1438-
uwriteln!(self.src, "var {reader_var} = new FutureReader({}, {export_name}.{base_interface_name}Interop.FutureVTable{});", operands[0], upper_camel);
1444+
let future_stream_name = match inst {
1445+
Instruction::FutureLift{payload: _, ty: _} => "Future",
1446+
Instruction::StreamLift{payload: _, ty: _} => "Stream",
1447+
_ => {
1448+
panic!("Unexpected instruction for lift");
1449+
}
1450+
};
1451+
uwriteln!(self.src, "var {reader_var} = new {future_stream_name}Reader{bracketed_generic}({}, {export_name}.{base_interface_name}Interop.{future_stream_name}VTable{});", operands[0], upper_camel);
14391452
results.push(reader_var);
14401453

1441-
self.interface_gen.add_future(self.func_name, &generic_type_name, ty);
1454+
match inst {
1455+
Instruction::FutureLift { .. } => {
1456+
self.interface_gen.add_future(self.func_name, &generic_type_name, ty);
1457+
}
1458+
_ => {
1459+
self.interface_gen.add_stream(self.func_name, &generic_type_name, ty);
1460+
}
1461+
}
1462+
14421463
self.interface_gen.csharp_gen.needs_async_support = true;
14431464
}
14441465

1445-
Instruction::StreamLower { .. }
1446-
| Instruction::StreamLift { .. }
1447-
| Instruction::ErrorContextLower { .. }
1466+
Instruction::ErrorContextLower { .. }
14481467
| Instruction::ErrorContextLift { .. }
14491468
| Instruction::DropHandle { .. }
14501469
| Instruction::FixedLengthListLift { .. }

0 commit comments

Comments
 (0)