Skip to content

Commit b6537dc

Browse files
yowlalexcrichton
andauthored
[C#] Simple Async support (#1346)
* wip * pass simple-import-params test * tidy for PR * fmt * Rremove extra test * remove sln * exclude failing tests * remove vscode files * format * Add some todos, split the interop code up properly * revert code output location change * revert format change * Pin Rust in CI --------- Co-authored-by: Alex Crichton <alex@alexcrichton.com>
1 parent 1eb33b9 commit b6537dc

File tree

8 files changed

+495
-143
lines changed

8 files changed

+495
-143
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ jobs:
6969
- uses: actions/checkout@v4
7070
with:
7171
submodules: true
72+
# FIXME(rust-lang/rust#148347) current rust (1.91) is broken
7273
- name: Install Rust
73-
run: rustup update stable --no-self-update && rustup default stable
74+
run: rustup update 1.90.0 --no-self-update && rustup default 1.90.0
7475
- run: rustup target add wasm32-wasip1 wasm32-wasip2
7576

7677
- run: rustup target add wasm32-unknown-unknown

crates/csharp/src/AsyncSupport.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Helpers for the async support.
3+
*/
4+
5+
public enum CallbackCode
6+
{
7+
Exit = 0,
8+
Yield = 1,
9+
}

crates/csharp/src/function.rs

Lines changed: 138 additions & 22 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::{Bindgen, Bitcast, Instruction};
8+
use wit_bindgen_core::abi::{self, Bindgen, Bitcast, Instruction};
99
use wit_bindgen_core::{uwrite, uwriteln, Direction, Ns};
1010
use wit_parser::abi::WasmType;
1111
use wit_parser::{
@@ -32,6 +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>,
3536
}
3637

3738
impl<'a, 'b> FunctionBindgen<'a, 'b> {
@@ -42,6 +43,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
4243
params: Box<[String]>,
4344
results: Vec<TypeId>,
4445
parameter_type: ParameterType,
46+
result_type: Option<Type>,
4547
) -> FunctionBindgen<'a, 'b> {
4648
let mut locals = Ns::default();
4749
// Ensure temporary variable names don't clash with parameter names:
@@ -67,6 +69,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
6769
is_block: false,
6870
fixed_statments: Vec::new(),
6971
parameter_type: parameter_type,
72+
result_type: result_type,
7073
}
7174
}
7275

@@ -304,7 +307,13 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
304307
let ty = self
305308
.interface_gen
306309
.type_name_with_qualifier(&func.result.unwrap(), true);
307-
uwriteln!(self.src, "{ty} {ret};");
310+
let is_async = InterfaceGenerator::is_async(&func.kind);
311+
312+
if is_async {
313+
uwriteln!(self.src, "Task<{ty}> {ret};");
314+
} else {
315+
uwriteln!(self.src, "{ty} {ret};");
316+
}
308317
let mut cases = Vec::with_capacity(self.results.len());
309318
let mut oks = Vec::with_capacity(self.results.len());
310319
let mut payload_is_void = false;
@@ -371,6 +380,44 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
371380
}
372381
ret
373382
}
383+
384+
fn emit_allocation_for_type(&mut self, results: &[WasmType]) -> String {
385+
let address = self.locals.tmp("address");
386+
let buffer_size = self.get_size_for_type(results);
387+
let align = self.get_align_for_type(results);
388+
uwriteln!(self.src, "void* {address} = global::System.Runtime.InteropServices.NativeMemory.AlignedAlloc({buffer_size}, {align});");
389+
390+
// TODO: Store the address somewhere so we can free it when the task completes.
391+
address
392+
}
393+
394+
fn get_size_for_type(&self, results: &[WasmType]) -> usize {
395+
match results {
396+
[WasmType::I32] => 4,
397+
[WasmType::I64] => 8,
398+
[WasmType::F32] => 4,
399+
[WasmType::F64] => 8,
400+
[WasmType::Pointer, WasmType::Length] => 4, // TODO: Wasm64
401+
[WasmType::PointerOrI64, WasmType::Length] => 8,
402+
_ => {
403+
todo!("other types not yet supported");
404+
}
405+
}
406+
}
407+
408+
fn get_align_for_type(&self, results: &[WasmType]) -> usize {
409+
match results {
410+
[WasmType::I32] => 4,
411+
[WasmType::I64] => 8,
412+
[WasmType::F32] => 4,
413+
[WasmType::F64] => 8,
414+
[WasmType::Pointer, WasmType::Length] => 4, // TODO: Wasm64
415+
[WasmType::PointerOrI64, WasmType::Length] => 8,
416+
_ => {
417+
todo!("other types not yet supported");
418+
}
419+
}
420+
}
374421
}
375422

376423
impl Bindgen for FunctionBindgen<'_, '_> {
@@ -400,22 +447,22 @@ impl Bindgen for FunctionBindgen<'_, '_> {
400447
})),
401448
Instruction::I32Load { offset }
402449
| Instruction::PointerLoad { offset }
403-
| Instruction::LengthLoad { offset } => results.push(format!("global::System.BitConverter.ToInt32(new global::System.Span<byte>((void*)({} + {offset}), 4))",operands[0],offset = offset.size_wasm32())),
404-
Instruction::I32Load8U { offset } => results.push(format!("new global::System.Span<byte>((void*)({} + {offset}), 1)[0]",operands[0],offset = offset.size_wasm32())),
405-
Instruction::I32Load8S { offset } => results.push(format!("(sbyte)new global::System.Span<byte>((void*)({} + {offset}), 1)[0]",operands[0],offset = offset.size_wasm32())),
406-
Instruction::I32Load16U { offset } => results.push(format!("global::System.BitConverter.ToUInt16(new global::System.Span<byte>((void*)({} + {offset}), 2))",operands[0],offset = offset.size_wasm32())),
407-
Instruction::I32Load16S { offset } => results.push(format!("global::System.BitConverter.ToInt16(new global::System.Span<byte>((void*)({} + {offset}), 2))",operands[0],offset = offset.size_wasm32())),
408-
Instruction::I64Load { offset } => results.push(format!("global::System.BitConverter.ToInt64(new global::System.Span<byte>((void*)({} + {offset}), 8))",operands[0],offset = offset.size_wasm32())),
409-
Instruction::F32Load { offset } => results.push(format!("global::System.BitConverter.ToSingle(new global::System.Span<byte>((void*)({} + {offset}), 4))",operands[0],offset = offset.size_wasm32())),
410-
Instruction::F64Load { offset } => results.push(format!("global::System.BitConverter.ToDouble(new global::System.Span<byte>((void*)({} + {offset}), 8))",operands[0],offset = offset.size_wasm32())),
450+
| Instruction::LengthLoad { offset } => results.push(format!("global::System.BitConverter.ToInt32(new global::System.Span<byte>((byte*){} + {offset}, 4))",operands[0],offset = offset.size_wasm32())),
451+
Instruction::I32Load8U { offset } => results.push(format!("new global::System.Span<byte>((byte*){} + {offset}, 1)[0]",operands[0],offset = offset.size_wasm32())),
452+
Instruction::I32Load8S { offset } => results.push(format!("(sbyte)new global::System.Span<byte>((byte*){} + {offset}, 1)[0]",operands[0],offset = offset.size_wasm32())),
453+
Instruction::I32Load16U { offset } => results.push(format!("global::System.BitConverter.ToUInt16(new global::System.Span<byte>((byte*){} + {offset}, 2))",operands[0],offset = offset.size_wasm32())),
454+
Instruction::I32Load16S { offset } => results.push(format!("global::System.BitConverter.ToInt16(new global::System.Span<byte>((byte*){} + {offset}, 2))",operands[0],offset = offset.size_wasm32())),
455+
Instruction::I64Load { offset } => results.push(format!("global::System.BitConverter.ToInt64(new global::System.Span<byte>((byte*){} + {offset}, 8))",operands[0],offset = offset.size_wasm32())),
456+
Instruction::F32Load { offset } => results.push(format!("global::System.BitConverter.ToSingle(new global::System.Span<byte>((byte*){} + {offset}, 4))",operands[0],offset = offset.size_wasm32())),
457+
Instruction::F64Load { offset } => results.push(format!("global::System.BitConverter.ToDouble(new global::System.Span<byte>((byte*){} + {offset}, 8))",operands[0],offset = offset.size_wasm32())),
411458
Instruction::I32Store { offset }
412459
| Instruction::PointerStore { offset }
413-
| Instruction::LengthStore { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span<byte>((void*)({} + {offset}), 4), {});", operands[1], operands[0],offset = offset.size_wasm32()),
460+
| Instruction::LengthStore { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span<byte>((byte*){} + {offset}, 4), {});", operands[1], operands[0],offset = offset.size_wasm32()),
414461
Instruction::I32Store8 { offset } => uwriteln!(self.src, "*(byte*)({} + {offset}) = (byte){};", operands[1], operands[0],offset = offset.size_wasm32()),
415-
Instruction::I32Store16 { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span<byte>((void*)({} + {offset}), 2), (short){});", operands[1], operands[0],offset = offset.size_wasm32()),
416-
Instruction::I64Store { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span<byte>((void*)({} + {offset}), 8), unchecked((long){}));", operands[1], operands[0],offset = offset.size_wasm32()),
417-
Instruction::F32Store { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span<byte>((void*)({} + {offset}), 4), unchecked((float){}));", operands[1], operands[0],offset = offset.size_wasm32()),
418-
Instruction::F64Store { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span<byte>((void*)({} + {offset}), 8), unchecked((double){}));", operands[1], operands[0],offset = offset.size_wasm32()),
462+
Instruction::I32Store16 { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span<byte>((byte*){} + {offset}, 2), (short){});", operands[1], operands[0],offset = offset.size_wasm32()),
463+
Instruction::I64Store { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span<byte>((byte*){} + {offset}, 8), unchecked((long){}));", operands[1], operands[0],offset = offset.size_wasm32()),
464+
Instruction::F32Store { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span<byte>((byte*){} + {offset}, 4), unchecked((float){}));", operands[1], operands[0],offset = offset.size_wasm32()),
465+
Instruction::F64Store { offset } => uwriteln!(self.src, "global::System.BitConverter.TryWriteBytes(new global::System.Span<byte>((byte*){} + {offset}, 8), unchecked((double){}));", operands[1], operands[0],offset = offset.size_wasm32()),
419466

420467
Instruction::I64FromU64 => results.push(format!("unchecked((long)({}))", operands[0])),
421468
Instruction::I32FromChar => results.push(format!("((int){})", operands[0])),
@@ -980,11 +1027,24 @@ impl Bindgen for FunctionBindgen<'_, '_> {
9801027
}
9811028

9821029
Instruction::CallWasm { sig, .. } => {
1030+
let is_async = InterfaceGenerator::is_async(self.kind);
1031+
1032+
let requires_async_return_buffer_param = is_async && sig.results.len() >= 1;
1033+
let async_return_buffer = if requires_async_return_buffer_param {
1034+
let buffer = self.emit_allocation_for_type(&sig.results);
1035+
uwriteln!(self.src, "//TODO: store somewhere with the TaskCompletionSource, possibly in the state, using Task.AsyncState to retrieve it later.");
1036+
Some(buffer)
1037+
} else {
1038+
None
1039+
};
1040+
9831041
let assignment = match &sig.results[..] {
9841042
[_] => {
9851043
let result = self.locals.tmp("result");
9861044
let assignment = format!("var {result} = ");
987-
results.push(result);
1045+
if !requires_async_return_buffer_param {
1046+
results.push(result);
1047+
}
9881048
assignment
9891049
}
9901050

@@ -995,12 +1055,32 @@ impl Bindgen for FunctionBindgen<'_, '_> {
9951055

9961056
let func_name = self.func_name.to_upper_camel_case();
9971057

998-
let operands = operands.join(", ");
1058+
// Async functions that return a result need to pass a buffer where the result will later be written.
1059+
let operands = match async_return_buffer {
1060+
Some(ref buffer) if operands.is_empty() => buffer.clone(),
1061+
Some(ref buffer) => format!("{}, {}", operands.join(", "), buffer),
1062+
None => operands.join(", "),
1063+
};
9991064

10001065
uwriteln!(
10011066
self.src,
10021067
"{assignment} {func_name}WasmInterop.wasmImport{func_name}({operands});"
10031068
);
1069+
1070+
if let Some(buffer) = async_return_buffer {
1071+
let result = abi::lift_from_memory(
1072+
self.interface_gen.resolve,
1073+
self,
1074+
buffer.clone(),
1075+
&self.result_type.unwrap(),
1076+
);
1077+
uwriteln!(
1078+
self.src,
1079+
"global::System.Runtime.InteropServices.NativeMemory.Free({});",
1080+
buffer
1081+
);
1082+
results.push(result);
1083+
}
10041084
}
10051085

10061086
Instruction::CallInterface { func, .. } => {
@@ -1027,6 +1107,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
10271107
}
10281108
}
10291109

1110+
let is_async = InterfaceGenerator::is_async(self.kind);
10301111
match self.kind {
10311112
FunctionKind::Constructor(id) => {
10321113
let target = self.interface_gen.csharp_gen.all_resources[id].export_impl_name();
@@ -1042,7 +1123,13 @@ impl Bindgen for FunctionBindgen<'_, '_> {
10421123
};
10431124

10441125
match func.result {
1045-
None => uwriteln!(self.src, "{target}.{func_name}({oper});"),
1126+
None => {
1127+
if is_async{
1128+
uwriteln!(self.src, "var ret = {target}.{func_name}({oper});");
1129+
} else {
1130+
uwriteln!(self.src, "{target}.{func_name}({oper});");
1131+
}
1132+
}
10461133
Some(_ty) => {
10471134
let ret = self.handle_result_call(func, target, func_name, oper);
10481135
results.push(ret);
@@ -1051,6 +1138,25 @@ impl Bindgen for FunctionBindgen<'_, '_> {
10511138
}
10521139
}
10531140

1141+
if is_async {
1142+
self.interface_gen.csharp_gen.needs_async_support = true;
1143+
let name = self.func_name.to_upper_camel_case();
1144+
let ret_param = match func.result {
1145+
None => "",
1146+
Some(_ty) => "ret.Result"
1147+
};
1148+
1149+
uwriteln!(self.src, r#"if (ret.IsCompletedSuccessfully)
1150+
{{
1151+
{name}TaskReturn({ret_param});
1152+
return (uint)CallbackCode.Exit;
1153+
}}
1154+
1155+
// TODO: Defer dropping borrowed resources until a result is returned.
1156+
return (uint)CallbackCode.Yield;
1157+
"#);
1158+
}
1159+
10541160
for (_, drop) in &self.resource_drops {
10551161
uwriteln!(self.src, "{drop}?.Dispose();");
10561162
}
@@ -1267,15 +1373,25 @@ impl Bindgen for FunctionBindgen<'_, '_> {
12671373
results.extend(operands.iter().take(*amt).map(|v| v.clone()));
12681374
}
12691375

1270-
Instruction::AsyncTaskReturn { .. }
1271-
| Instruction::FutureLower { .. }
1272-
| Instruction::FutureLift { .. }
1376+
Instruction::FutureLower { .. } => {
1377+
let op = &operands[0];
1378+
results.push(format!("{op}.Handle"));
1379+
}
1380+
1381+
Instruction::AsyncTaskReturn { name: _, params: _ } => {
1382+
uwriteln!(self.src, "// TODO_task_cancel.forget();");
1383+
}
1384+
1385+
Instruction::FutureLift { .. }
12731386
| Instruction::StreamLower { .. }
12741387
| Instruction::StreamLift { .. }
12751388
| Instruction::ErrorContextLower { .. }
12761389
| Instruction::ErrorContextLift { .. }
12771390
| Instruction::DropHandle { .. }
1278-
=> todo!(),
1391+
=> {
1392+
dbg!(inst);
1393+
todo!()
1394+
}
12791395
}
12801396
}
12811397

0 commit comments

Comments
 (0)