Skip to content

Commit a5e5ae8

Browse files
authored
C#: Add implementation for lowering and lifiting flags (#800)
* implement flags for c# as enums * remove dead code, fix lowering for >32 bit flags * add documentation for testing single wit fiel * change enum type to be shortest to match flags length
1 parent eb03a20 commit a5e5ae8

File tree

3 files changed

+75
-33
lines changed

3 files changed

+75
-33
lines changed

crates/csharp/src/lib.rs

Lines changed: 68 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -363,14 +363,14 @@ impl WorldGenerator for CSharp {
363363
364364
private int GetS32(int offset)
365365
{{
366-
ReadOnlySpan<byte> span = this;
366+
ReadOnlySpan<byte> span = MemoryMarshal.CreateSpan(ref buffer, {});
367367
368368
return BitConverter.ToInt32(span.Slice(offset, 4));
369369
}}
370370
371371
public void SetS32(int offset, int value)
372372
{{
373-
Span<byte> span = this;
373+
Span<byte> span = MemoryMarshal.CreateSpan(ref buffer, {});
374374
375375
BitConverter.TryWriteBytes(span.Slice(offset), value);
376376
}}
@@ -394,6 +394,8 @@ impl WorldGenerator for CSharp {
394394
",
395395
self.return_area_size,
396396
self.return_area_align,
397+
self.return_area_size,
398+
self.return_area_size
397399
);
398400

399401
src.push_str(&ret_area_str);
@@ -582,8 +584,12 @@ impl InterfaceGenerator<'_> {
582584
fn qualifier(&self, when: bool, ty: &TypeDef) -> String {
583585
if let TypeOwner::Interface(id) = &ty.owner {
584586
if let Some(name) = self.gen.interface_names.get(id) {
585-
if name != self.name {
586-
return format!("{}.", name);
587+
let name_with_interface = format!(
588+
"{name}.I{}",
589+
CSharp::get_class_name_from_qualified_name(name.clone())
590+
);
591+
if name_with_interface != self.name {
592+
return format!("{}.", name_with_interface);
587593
}
588594
}
589595
}
@@ -617,7 +623,7 @@ impl InterfaceGenerator<'_> {
617623
r#"
618624
private unsafe struct ReturnArea
619625
{{
620-
private int GetS32(IntPtr ptr, int offset)
626+
public int GetS32(IntPtr ptr, int offset)
621627
{{
622628
var span = new Span<byte>((void*)ptr, {});
623629
@@ -665,14 +671,14 @@ impl InterfaceGenerator<'_> {
665671
666672
private int GetS32(int offset)
667673
{{
668-
ReadOnlySpan<byte> span = this;
674+
ReadOnlySpan<byte> span = MemoryMarshal.CreateSpan(ref buffer, {});
669675
670676
return BitConverter.ToInt32(span.Slice(offset, 4));
671677
}}
672678
673679
public void SetS32(int offset, int value)
674680
{{
675-
Span<byte> span = this;
681+
Span<byte> span = MemoryMarshal.CreateSpan(ref buffer, {});
676682
677683
BitConverter.TryWriteBytes(span.Slice(offset), value);
678684
}}
@@ -696,6 +702,8 @@ impl InterfaceGenerator<'_> {
696702
",
697703
self.gen.return_area_size,
698704
self.gen.return_area_align,
705+
self.gen.return_area_size,
706+
self.gen.return_area_size
699707
);
700708

701709
self.csharp_interop_src.push_str(&ret_area_str);
@@ -727,7 +735,7 @@ impl InterfaceGenerator<'_> {
727735
0 => "void".to_string(),
728736
1 => {
729737
let ty = func.results.iter_types().next().unwrap();
730-
self.type_name(ty)
738+
self.type_name_with_qualifier(ty, true)
731739
}
732740
_ => unreachable!(), //TODO
733741
};
@@ -1156,43 +1164,34 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> {
11561164

11571165
let name = name.to_upper_camel_case();
11581166

1159-
let ty = match flags.repr() {
1160-
FlagsRepr::U8 => "byte",
1161-
FlagsRepr::U16 => "ushort",
1162-
FlagsRepr::U32(1) => "uint",
1163-
FlagsRepr::U32(2) => "ulong",
1164-
repr => todo!("flags {repr:?}"),
1165-
};
1166-
1167-
let flags = flags
1167+
let enum_elements = flags
11681168
.flags
11691169
.iter()
11701170
.enumerate()
11711171
.map(|(i, flag)| {
11721172
let flag_name = flag.name.to_shouty_snake_case();
11731173
let suffix = if matches!(flags.repr(), FlagsRepr::U32(2)) {
1174-
"L"
1174+
"UL"
11751175
} else {
11761176
""
11771177
};
1178-
format!(
1179-
"public static readonly {name} {flag_name} = new {name}(({ty}) (1{suffix} << {i}));"
1180-
)
1178+
format!("{flag_name} = 1{suffix} << {i},")
11811179
})
11821180
.collect::<Vec<_>>()
11831181
.join("\n");
11841182

1183+
let enum_type = match flags.repr() {
1184+
FlagsRepr::U32(2) => ": ulong",
1185+
FlagsRepr::U16 => ": ushort",
1186+
FlagsRepr::U8 => ": byte",
1187+
_ => "",
1188+
};
1189+
11851190
uwrite!(
11861191
self.src,
11871192
"
1188-
public static class {name} {{
1189-
public readonly {ty} value;
1190-
1191-
public {name}({ty} value) {{
1192-
this.value = value;
1193-
}}
1194-
1195-
{flags}
1193+
public enum {name} {enum_type} {{
1194+
{enum_elements}
11961195
}}
11971196
"
11981197
);
@@ -1426,7 +1425,13 @@ impl Bindgen for FunctionBindgen<'_, '_> {
14261425
.to_owned()
14271426
})),
14281427

1429-
Instruction::I32Load { offset } => results.push(format!("returnArea.GetS32({offset})")),
1428+
Instruction::I32Load { offset } => {
1429+
if self.gen.in_import {
1430+
results.push(format!("returnArea.GetS32(ptr, {offset})"))
1431+
} else {
1432+
results.push(format!("returnArea.GetS32({offset})"))
1433+
}
1434+
}
14301435
Instruction::I32Load8U { offset } => {
14311436
results.push(format!("returnArea.GetU8({offset})"))
14321437
}
@@ -1482,9 +1487,40 @@ impl Bindgen for FunctionBindgen<'_, '_> {
14821487
}
14831488
Instruction::BoolFromI32 => results.push(format!("({} != 0)", operands[0])),
14841489

1485-
Instruction::FlagsLower { .. } => todo!("FlagsLower"),
1490+
Instruction::FlagsLower {
1491+
flags,
1492+
name: _,
1493+
ty: _,
1494+
} => {
1495+
if flags.flags.len() > 32 {
1496+
results.push(format!(
1497+
"(int)(((long){}) & uint.MaxValue)",
1498+
operands[0].to_string()
1499+
));
1500+
results.push(format!("((int)({})) >> 32", operands[0].to_string()));
1501+
} else {
1502+
results.push(format!("(int){}", operands[0].to_string()));
1503+
}
1504+
}
14861505

1487-
Instruction::FlagsLift { .. } => todo!("FlagsLift"),
1506+
Instruction::FlagsLift { flags, name, ty } => {
1507+
let id_type = &self.gen.resolve.types[*ty];
1508+
let qualified_type_name = format!(
1509+
"{}{}",
1510+
self.gen.qualifier(true, id_type),
1511+
name.to_string().to_upper_camel_case()
1512+
);
1513+
if flags.flags.len() > 32 {
1514+
results.push(format!(
1515+
"({})((long)({}) << 32 | (uint)({}))",
1516+
qualified_type_name,
1517+
operands[0].to_string(),
1518+
operands[1].to_string()
1519+
));
1520+
} else {
1521+
results.push(format!("({})({})", qualified_type_name, operands[0]))
1522+
}
1523+
}
14881524

14891525
Instruction::RecordLower { .. } => todo!("RecordLower"),
14901526
Instruction::RecordLift { .. } => todo!("RecordLift"),

crates/csharp/tests/codegen.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ macro_rules! codegen_test {
1717
|resolve, world, files| {
1818
if [
1919
"conventions",
20-
"flags",
2120
"guest-name",
2221
"import-and-export-resource",
2322
"import-and-export-resource-alias",

tests/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ To run just `codegen` tests for a single language (replace rust with language of
2525
cargo test -p wit-bindgen-rust
2626
```
2727

28+
To run just `codegen` tests for a single language (replace rust with language of choice: `go`, `c`, `csharp`, etc.) and a single wit file (replace `flags` with whatever wit file should be tested):
29+
30+
```
31+
cargo test -p wit-bindgen-rust -- flags
32+
```
33+
2834
To run just `runtime` tests for a single language (replace rust with language of choice: `go`, `c`, `csharp`, etc.):
2935

3036
```bash
@@ -57,6 +63,7 @@ wasm and executed on hosts. The code compiled-to-wasm can be one of:
5763
* `wasm.rs` - compiled with Rust to WebAssembly
5864
* `wasm.c` - compiled with Clang
5965
* `wasm.java` - compiled with TeaVM-WASI
66+
* `wasm.cs` - compiled with NativeAOT and Mono
6067

6168
Existence of these files indicates that the language should be supported for the
6269
test, and if a file is missing then it's skipped when running other tests. Each

0 commit comments

Comments
 (0)