Skip to content

Commit f30e6ac

Browse files
yowljsturtevant
andauthored
C# enable the runtime numbers test (#718)
* add c# runtime test for numbers add features for the 2 dotnet runtime flavours, naot and mono * Update .github/workflows/main.yml Co-authored-by: James Sturtevant <[email protected]> * Update .github/workflows/main.yml Co-authored-by: James Sturtevant <[email protected]> * newline --------- Co-authored-by: James Sturtevant <[email protected]>
1 parent 5354db0 commit f30e6ac

File tree

5 files changed

+213
-60
lines changed

5 files changed

+213
-60
lines changed

.github/workflows/main.yml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,22 @@ jobs:
4545
echo "WASI_SDK_PATH=`pwd`/wasi-sdk-20.0+m" >> $GITHUB_ENV
4646
if : matrix.os == 'windows-latest'
4747
48+
- run: |
49+
curl.exe -LO https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1
50+
powershell -File dotnet-install.ps1 -Channel 8.0.1xx -Verbose
51+
echo DOTNET_ROOT=$LOCALAPPDATA'\Microsoft\dotnet' >> $GITHUB_ENV
52+
export DOTNET_ROOT=$LOCALAPPDATA\\Microsoft\\dotnet
53+
echo $LOCALAPPDATA'\Microsoft\dotnet' >> $GITHUB_PATH
54+
echo $LOCALAPPDATA'\Microsoft\dotnet\tools' >> $GITHUB_PATH
55+
$LOCALAPPDATA/Microsoft/dotnet/dotnet --info
56+
echo nativeaot-llvm requires emscripten for its version of clang as wasi-sdk 20 does not work see https://github.com/WebAssembly/wasi-sdk/issues/326
57+
curl.exe -OL https://github.com/emscripten-core/emsdk/archive/refs/heads/main.zip
58+
unzip main.zip
59+
cd emsdk-main
60+
./emsdk.bat install 3.1.23
61+
./emsdk.bat activate 3.1.23
62+
if : matrix.os == 'windows-latest'
63+
4864
- run: ci/download-teavm.sh
4965

5066
- uses: actions/setup-node@v2
@@ -60,7 +76,14 @@ jobs:
6076
- uses: acifani/setup-tinygo@v1
6177
with:
6278
tinygo-version: 0.30.0
63-
- run: cargo test --workspace
79+
- name: All but Windows, cargo test --workspace
80+
if : matrix.os != 'windows-latest'
81+
run: cargo test --workspace
82+
- name: Windows, set EMSDK and run cargo test
83+
if : matrix.os == 'windows-latest'
84+
run: |
85+
source ./emsdk-main/emsdk_env.sh
86+
cargo test --workspace
6487
- run: cargo build
6588
- run: cargo build --no-default-features
6689
- run: cargo build --no-default-features --features rust

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,16 @@ default = [
6666
'markdown',
6767
'teavm-java',
6868
'go',
69-
'csharp',
69+
'csharp-naot',
7070
]
7171
c = ['dep:wit-bindgen-c']
7272
rust = ['dep:wit-bindgen-rust']
7373
markdown = ['dep:wit-bindgen-markdown']
7474
teavm-java = ['dep:wit-bindgen-teavm-java']
7575
go = ['dep:wit-bindgen-go']
7676
csharp = ['dep:wit-bindgen-csharp']
77+
csharp-naot = ['csharp']
78+
csharp-mono = ['csharp']
7779

7880
[dev-dependencies]
7981
heck = { workspace = true }

crates/csharp/src/lib.rs

Lines changed: 30 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,28 +1339,26 @@ impl Bindgen for FunctionBindgen<'_, '_> {
13391339
Instruction::F32Store { .. } => todo!("F32Store"),
13401340
Instruction::F64Store { .. } => todo!("F64Store"),
13411341

1342-
Instruction::I64FromU64 => results.push(format!("(long)({})", operands[0].clone())),
1343-
1344-
//This is handled in the C interface, so we just pass the value as is.
1345-
Instruction::I32FromChar
1346-
| Instruction::I64FromS64
1347-
| Instruction::I32FromU32
1348-
| Instruction::I32FromS32
1342+
Instruction::I64FromU64 => results.push(format!("unchecked((long)({}))", operands[0])),
1343+
Instruction::I32FromChar => results.push(format!("((int){})", operands[0])),
1344+
Instruction::I32FromU32 => results.push(format!("unchecked((int)({}))", operands[0])),
1345+
Instruction::U8FromI32 => results.push(format!("((byte){})", operands[0])),
1346+
Instruction::S8FromI32 => results.push(format!("((sbyte){})", operands[0])),
1347+
Instruction::U16FromI32 => results.push(format!("((ushort){})", operands[0])),
1348+
Instruction::S16FromI32 => results.push(format!("((short){})", operands[0])),
1349+
Instruction::U32FromI32 => results.push(format!("unchecked((uint)({}))", operands[0])),
1350+
Instruction::U64FromI64 => results.push(format!("unchecked((ulong)({}))", operands[0])),
1351+
Instruction::CharFromI32 => results.push(format!("unchecked((uint)({}))", operands[0])),
1352+
Instruction::I64FromS64
13491353
| Instruction::I32FromU16
13501354
| Instruction::I32FromS16
13511355
| Instruction::I32FromU8
13521356
| Instruction::I32FromS8
1357+
| Instruction::I32FromS32
13531358
| Instruction::F32FromFloat32
13541359
| Instruction::F64FromFloat64
1355-
| Instruction::S8FromI32
1356-
| Instruction::U8FromI32
1357-
| Instruction::S16FromI32
1358-
| Instruction::U16FromI32
13591360
| Instruction::S32FromI32
1360-
| Instruction::U32FromI32
13611361
| Instruction::S64FromI64
1362-
| Instruction::U64FromI64
1363-
| Instruction::CharFromI32
13641362
| Instruction::Float32FromF32
13651363
| Instruction::Float64FromF64 => results.push(operands[0].clone()),
13661364

@@ -1493,22 +1491,10 @@ impl Bindgen for FunctionBindgen<'_, '_> {
14931491
let module = self.gen.name.to_upper_camel_case();
14941492
let func_name = self.func_name.to_upper_camel_case();
14951493
let class_name = CSharp::get_class_name_from_qualified_name(module);
1496-
1497-
let params_cast = func
1498-
.params
1499-
.iter()
1500-
.map(|(_, ty)| {
1501-
let ty = self.gen.type_name(ty);
1502-
1503-
format!("{ty}")
1504-
})
1505-
.collect::<Vec<String>>();
1506-
15071494
let mut oper = String::new();
15081495

15091496
for (i, param) in operands.iter().enumerate() {
1510-
let cast = params_cast.get(i).unwrap();
1511-
oper.push_str(&format!("({cast}){param}"));
1497+
oper.push_str(&format!("({param})"));
15121498

15131499
if i < operands.len() && operands.len() != i + 1 {
15141500
oper.push_str(", ");
@@ -1524,28 +1510,24 @@ impl Bindgen for FunctionBindgen<'_, '_> {
15241510
}
15251511
}
15261512

1527-
Instruction::Return { amt, func } => {
1528-
let sig = self
1529-
.gen
1530-
.resolve()
1531-
.wasm_signature(AbiVariant::GuestExport, func);
1532-
1533-
let cast = sig
1534-
.results
1535-
.into_iter()
1536-
.map(|ty| wasm_type(ty))
1537-
.collect::<Vec<&str>>()
1538-
.join(", ");
1539-
1540-
match *amt {
1541-
0 => (),
1542-
1 => uwriteln!(self.src, "return {};", operands[0]),
1543-
_ => {
1544-
let results = operands.join(", ");
1545-
uwriteln!(self.src, "return ({cast})({results});")
1546-
}
1513+
Instruction::Return { amt, func } => match func.results.len() {
1514+
0 => (),
1515+
1 => uwriteln!(self.src, "return {};", operands[0]),
1516+
_ => {
1517+
let results = operands.join(", ");
1518+
let sig = self
1519+
.gen
1520+
.resolve()
1521+
.wasm_signature(AbiVariant::GuestExport, func);
1522+
let cast = sig
1523+
.results
1524+
.into_iter()
1525+
.map(|ty| wasm_type(ty))
1526+
.collect::<Vec<&str>>()
1527+
.join(", ");
1528+
uwriteln!(self.src, "return ({cast})({results});")
15471529
}
1548-
}
1530+
},
15491531

15501532
Instruction::Malloc { .. } => unimplemented!(),
15511533

tests/runtime/main.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use anyhow::Result;
22
use heck::ToUpperCamelCase;
33

44
use std::borrow::Cow;
5-
use std::fs;
65
use std::io::Write;
7-
use std::path::PathBuf;
6+
use std::path::{Path, PathBuf};
87
use std::process::Command;
8+
use std::{env, fs};
99
use wasm_encoder::{Encode, Section};
1010
use wasmtime::component::{Component, Instance, Linker};
1111
use wasmtime::{Config, Engine, Store};
@@ -124,15 +124,19 @@ fn tests(name: &str, dir_name: &str) -> Result<Vec<PathBuf>> {
124124
let mut c = Vec::new();
125125
let mut java = Vec::new();
126126
let mut go = Vec::new();
127-
let mut c_sharp = Vec::new();
127+
let mut c_sharp: Vec<PathBuf> = Vec::new();
128128
for file in dir.read_dir()? {
129129
let path = file?.path();
130130
match path.extension().and_then(|s| s.to_str()) {
131131
Some("c") => c.push(path),
132132
Some("java") => java.push(path),
133133
Some("rs") => rust.push(path),
134134
Some("go") => go.push(path),
135-
Some("cs") => c_sharp.push(path),
135+
Some("cs") => {
136+
if cfg!(windows) {
137+
c_sharp.push(path);
138+
}
139+
}
136140
_ => {}
137141
}
138142
}
@@ -144,7 +148,6 @@ fn tests(name: &str, dir_name: &str) -> Result<Vec<PathBuf>> {
144148
out_dir.push("runtime-tests");
145149
out_dir.push(name);
146150

147-
println!("wasi adapter = {:?}", test_artifacts::ADAPTER);
148151
let wasi_adapter = std::fs::read(&test_artifacts::ADAPTER)?;
149152

150153
drop(std::fs::remove_dir_all(&out_dir));
@@ -484,9 +487,13 @@ fn tests(name: &str, dir_name: &str) -> Result<Vec<PathBuf>> {
484487
result.push(component_path);
485488
}
486489

487-
#[cfg(feature = "csharp")]
490+
#[cfg(feature = "csharp-mono")]
491+
for path in c_sharp.iter() {
492+
todo!()
493+
}
494+
495+
#[cfg(feature = "csharp-naot")]
488496
for path in c_sharp.iter() {
489-
println!("running for {}", path.display());
490497
let world_name = &resolve.worlds[world].name;
491498
let out_dir = out_dir.join(format!("csharp-{}", world_name));
492499
drop(fs::remove_dir_all(&out_dir));
@@ -647,12 +654,20 @@ fn tests(name: &str, dir_name: &str) -> Result<Vec<PathBuf>> {
647654
let file_name = path.file_name().unwrap();
648655
fs::copy(path, out_dir.join(file_name.to_str().unwrap()))?;
649656

650-
let mut cmd = Command::new("dotnet");
657+
let dotnet_root_env = "DOTNET_ROOT";
658+
let dotnet_cmd: PathBuf;
659+
match env::var(dotnet_root_env) {
660+
Ok(val) => dotnet_cmd = Path::new(&val).join("dotnet"),
661+
Err(_e) => dotnet_cmd = "dotnet".into(),
662+
}
663+
664+
let mut cmd = Command::new(dotnet_cmd);
651665
let mut wasm_filename = out_wasm.join(assembly_name);
652666
wasm_filename.set_extension("wasm");
653667

654668
cmd.current_dir(&out_dir);
655669

670+
// add .arg("/bl") to diagnose dotnet build problems
656671
cmd.arg("publish")
657672
.arg(out_dir.join(format!("{camel}.csproj")))
658673
.arg("-r")
@@ -663,14 +678,12 @@ fn tests(name: &str, dir_name: &str) -> Result<Vec<PathBuf>> {
663678
.arg("/p:MSBuildEnableWorkloadResolver=false")
664679
.arg("--self-contained")
665680
.arg("/p:UseAppHost=false")
666-
.arg("/bl")
667681
.arg("-o")
668682
.arg(&out_wasm);
669683
let output = match cmd.output() {
670684
Ok(output) => output,
671685
Err(e) => panic!("failed to spawn compiler: {}", e),
672686
};
673-
println!("{:?} completed", cmd);
674687

675688
if !output.status.success() {
676689
println!("status: {}", output.status);

tests/runtime/numbers/wasm.cs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
using System.Diagnostics;
4+
using wit_numbers.Wit.imports.test.numbers.Test;
5+
6+
namespace wit_numbers;
7+
8+
public class NumbersWorldImpl : NumbersWorld
9+
{
10+
public static void TestImports()
11+
{
12+
Debug.Assert(TestInterop.RoundtripU8(1) == 1);
13+
Debug.Assert(TestInterop.RoundtripU8(0) == 0);
14+
Debug.Assert(TestInterop.RoundtripU8(Byte.MaxValue) == Byte.MaxValue);
15+
16+
Debug.Assert(TestInterop.RoundtripS8(1) == 1);
17+
Debug.Assert(TestInterop.RoundtripS8(SByte.MinValue) == SByte.MinValue);
18+
Debug.Assert(TestInterop.RoundtripS8(SByte.MaxValue) == SByte.MaxValue);
19+
20+
Debug.Assert(TestInterop.RoundtripU16(1) == 1);
21+
Debug.Assert(TestInterop.RoundtripU16(0) == 0);
22+
Debug.Assert(TestInterop.RoundtripU16(UInt16.MaxValue) == UInt16.MaxValue);
23+
24+
Debug.Assert(TestInterop.RoundtripS16(1) == 1);
25+
Debug.Assert(TestInterop.RoundtripS16(Int16.MinValue) == Int16.MinValue);
26+
Debug.Assert(TestInterop.RoundtripS16(Int16.MaxValue) == Int16.MaxValue);
27+
28+
Debug.Assert(TestInterop.RoundtripU32(1) == 1);
29+
Debug.Assert(TestInterop.RoundtripU32(0) == 0);
30+
Debug.Assert(TestInterop.RoundtripU32(UInt32.MaxValue) == UInt32.MaxValue);
31+
32+
Debug.Assert(TestInterop.RoundtripS32(1) == 1);
33+
Debug.Assert(TestInterop.RoundtripS32(Int32.MinValue) == Int32.MinValue);
34+
Debug.Assert(TestInterop.RoundtripS32(Int32.MaxValue) == Int32.MaxValue);
35+
36+
Debug.Assert(TestInterop.RoundtripU64(1) == 1);
37+
Debug.Assert(TestInterop.RoundtripU64(0) == 0);
38+
Debug.Assert(TestInterop.RoundtripU64(UInt64.MaxValue) == UInt64.MaxValue);
39+
40+
Debug.Assert(TestInterop.RoundtripS64(1) == 1);
41+
Debug.Assert(TestInterop.RoundtripS64(Int64.MinValue) == Int64.MinValue);
42+
Debug.Assert(TestInterop.RoundtripS64(Int64.MaxValue) == Int64.MaxValue);
43+
44+
Debug.Assert(TestInterop.RoundtripFloat32(1.0f) == 1.0f);
45+
Debug.Assert(TestInterop.RoundtripFloat32(Single.PositiveInfinity) == Single.PositiveInfinity);
46+
Debug.Assert(TestInterop.RoundtripFloat32(Single.NegativeInfinity) == Single.NegativeInfinity);
47+
Debug.Assert(float.IsNaN(TestInterop.RoundtripFloat32(Single.NaN)));
48+
49+
Debug.Assert(TestInterop.RoundtripFloat64(1.0) == 1.0);
50+
Debug.Assert(TestInterop.RoundtripFloat64(Double.PositiveInfinity) == Double.PositiveInfinity);
51+
Debug.Assert(TestInterop.RoundtripFloat64(Double.NegativeInfinity) == Double.NegativeInfinity);
52+
Debug.Assert(double.IsNaN(TestInterop.RoundtripFloat64(Double.NaN)));
53+
54+
Debug.Assert(TestInterop.RoundtripChar('a') == 'a');
55+
Debug.Assert(TestInterop.RoundtripChar(' ') == ' ');
56+
Debug.Assert(Char.ConvertFromUtf32((int)TestInterop.RoundtripChar((uint)Char.ConvertToUtf32("🚩", 0))) == "🚩"); // This is 2 chars long as it contains a surrogate pair
57+
58+
TestInterop.SetScalar(2);
59+
Debug.Assert(TestInterop.GetScalar() == 2);
60+
TestInterop.SetScalar(4);
61+
Debug.Assert(TestInterop.GetScalar() == 4);
62+
}
63+
}
64+
65+
public class TestImpl : wit_numbers.Wit.exports.test.numbers.Test.Test
66+
{
67+
static uint SCALAR = 0;
68+
69+
public static byte RoundtripU8(byte p0)
70+
{
71+
return p0;
72+
}
73+
74+
public static sbyte RoundtripS8(sbyte p0)
75+
{
76+
return p0;
77+
}
78+
79+
public static ushort RoundtripU16(ushort p0)
80+
{
81+
return p0;
82+
}
83+
84+
public static short RoundtripS16(short p0)
85+
{
86+
return p0;
87+
}
88+
89+
public static uint RoundtripU32(uint p0)
90+
{
91+
return p0;
92+
}
93+
94+
public static int RoundtripS32(int p0)
95+
{
96+
return p0;
97+
}
98+
99+
public static ulong RoundtripU64(ulong p0)
100+
{
101+
return p0;
102+
}
103+
104+
public static long RoundtripS64(long p0)
105+
{
106+
return p0;
107+
}
108+
109+
public static float RoundtripFloat32(float p0)
110+
{
111+
return p0;
112+
}
113+
114+
public static double RoundtripFloat64(double p0)
115+
{
116+
return p0;
117+
}
118+
119+
public static uint RoundtripChar(uint p0)
120+
{
121+
return p0;
122+
}
123+
124+
public static void SetScalar(uint p0)
125+
{
126+
SCALAR = p0;
127+
}
128+
129+
public static uint GetScalar()
130+
{
131+
return SCALAR;
132+
}
133+
}

0 commit comments

Comments
 (0)