diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 95751b16f..f070d9007 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -47,6 +47,22 @@ jobs: echo "WASI_SDK_PATH=`pwd`/wasi-sdk-16.0" >> $GITHUB_ENV if : matrix.os == 'windows-latest' + - run: | + curl -O https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1 + powershell -File dotnet-install.ps1 -Channel 8.0.1xx -Verbose + echo DOTNET_ROOT=$LOCALAPPDATA'\Microsoft\dotnet' >> $GITHUB_ENV + export DOTNET_ROOT=$LOCALAPPDATA\\Microsoft\\dotnet + echo $LOCALAPPDATA'\Microsoft\dotnet' >> $GITHUB_PATH + echo $LOCALAPPDATA'\Microsoft\dotnet\tools' >> $GITHUB_PATH + $LOCALAPPDATA/Microsoft/dotnet/dotnet --info + 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 + curl -OL https://github.com/emscripten-core/emsdk/archive/refs/heads/main.zip + unzip main.zip + cd emsdk-main + ./emsdk.bat install 3.1.23 + ./emsdk.bat activate 3.1.23 + if : matrix.os == 'windows-latest' + - run: ci/download-teavm.sh - uses: actions/setup-node@v2 @@ -62,7 +78,14 @@ jobs: - uses: acifani/setup-tinygo@v1 with: tinygo-version: 0.30.0 - - run: cargo test --workspace + - name: All but Windows, cargo test --workspace + if : matrix.os != 'windows-latest' + run: cargo test --workspace + - name: Windows, set EMSDK and run cargo test + if : matrix.os == 'windows-latest' + run: | + source ./emsdk-main/emsdk_env.sh + cargo test --workspace - run: cargo build - run: cargo build --no-default-features - run: cargo build --no-default-features --features rust diff --git a/Cargo.toml b/Cargo.toml index 92267c2e5..0d9186d11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ default = [ 'markdown', 'teavm-java', 'go', - 'csharp', + 'csharp-naot', ] c = ['dep:wit-bindgen-c'] rust = ['dep:wit-bindgen-rust'] @@ -76,6 +76,8 @@ markdown = ['dep:wit-bindgen-markdown'] teavm-java = ['dep:wit-bindgen-teavm-java'] go = ['dep:wit-bindgen-go'] csharp = ['dep:wit-bindgen-csharp'] +csharp-naot = ['csharp'] +csharp-mono = ['csharp'] [dev-dependencies] heck = { workspace = true } diff --git a/crates/csharp/src/lib.rs b/crates/csharp/src/lib.rs index 4ba150ce7..861390eb3 100644 --- a/crates/csharp/src/lib.rs +++ b/crates/csharp/src/lib.rs @@ -1341,28 +1341,26 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::F32Store { .. } => todo!("F32Store"), Instruction::F64Store { .. } => todo!("F64Store"), - Instruction::I64FromU64 => results.push(format!("(long)({})", operands[0].clone())), - - //This is handled in the C interface, so we just pass the value as is. - Instruction::I32FromChar - | Instruction::I64FromS64 - | Instruction::I32FromU32 - | Instruction::I32FromS32 + Instruction::I64FromU64 => results.push(format!("unchecked((long)({}))", operands[0])), + Instruction::I32FromChar => results.push(format!("((int){})", operands[0])), + Instruction::I32FromU32 => results.push(format!("unchecked((int)({}))", operands[0])), + Instruction::U8FromI32 => results.push(format!("((byte){})", operands[0])), + Instruction::S8FromI32 => results.push(format!("((sbyte){})", operands[0])), + Instruction::U16FromI32 => results.push(format!("((ushort){})", operands[0])), + Instruction::S16FromI32 => results.push(format!("((short){})", operands[0])), + Instruction::U32FromI32 => results.push(format!("unchecked((uint)({}))", operands[0])), + Instruction::U64FromI64 => results.push(format!("unchecked((ulong)({}))", operands[0])), + Instruction::CharFromI32 => results.push(format!("unchecked((uint)({}))", operands[0])), + Instruction::I64FromS64 | Instruction::I32FromU16 | Instruction::I32FromS16 | Instruction::I32FromU8 | Instruction::I32FromS8 + | Instruction::I32FromS32 | Instruction::F32FromFloat32 | Instruction::F64FromFloat64 - | Instruction::S8FromI32 - | Instruction::U8FromI32 - | Instruction::S16FromI32 - | Instruction::U16FromI32 | Instruction::S32FromI32 - | Instruction::U32FromI32 | Instruction::S64FromI64 - | Instruction::U64FromI64 - | Instruction::CharFromI32 | Instruction::Float32FromF32 | Instruction::Float64FromF64 => results.push(operands[0].clone()), @@ -1495,22 +1493,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { let module = self.gen.name.to_upper_camel_case(); let func_name = self.func_name.to_upper_camel_case(); let class_name = CSharp::get_class_name_from_qualified_name(module); - - let params_cast = func - .params - .iter() - .map(|(_, ty)| { - let ty = self.gen.type_name(ty); - - format!("{ty}") - }) - .collect::>(); - let mut oper = String::new(); for (i, param) in operands.iter().enumerate() { - let cast = params_cast.get(i).unwrap(); - oper.push_str(&format!("({cast}){param}")); + oper.push_str(&format!("({param})")); if i < operands.len() && operands.len() != i + 1 { oper.push_str(", "); @@ -1526,28 +1512,24 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } - Instruction::Return { amt, func } => { - let sig = self - .gen - .resolve() - .wasm_signature(AbiVariant::GuestExport, func); - - let cast = sig - .results - .into_iter() - .map(|ty| wasm_type(ty)) - .collect::>() - .join(", "); - - match *amt { - 0 => (), - 1 => uwriteln!(self.src, "return {};", operands[0]), - _ => { - let results = operands.join(", "); - uwriteln!(self.src, "return ({cast})({results});") - } + Instruction::Return { amt, func } => match func.results.len() { + 0 => (), + 1 => uwriteln!(self.src, "return {};", operands[0]), + _ => { + let results = operands.join(", "); + let sig = self + .gen + .resolve() + .wasm_signature(AbiVariant::GuestExport, func); + let cast = sig + .results + .into_iter() + .map(|ty| wasm_type(ty)) + .collect::>() + .join(", "); + uwriteln!(self.src, "return ({cast})({results});") } - } + }, Instruction::Malloc { .. } => unimplemented!(), diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 613a96eb3..5f49f3909 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -2,10 +2,10 @@ use anyhow::Result; use heck::ToUpperCamelCase; use std::borrow::Cow; -use std::fs; use std::io::Write; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::Command; +use std::{env, fs}; use wasm_encoder::{Encode, Section}; use wasmtime::component::{Component, Instance, Linker}; use wasmtime::{Config, Engine, Store}; @@ -115,7 +115,7 @@ fn tests(name: &str, dir_name: &str) -> Result> { let mut c = Vec::new(); let mut java = Vec::new(); let mut go = Vec::new(); - let mut c_sharp = Vec::new(); + let mut c_sharp: Vec = Vec::new(); for file in dir.read_dir()? { let path = file?.path(); match path.extension().and_then(|s| s.to_str()) { @@ -123,7 +123,11 @@ fn tests(name: &str, dir_name: &str) -> Result> { Some("java") => java.push(path), Some("rs") => rust.push(path), Some("go") => go.push(path), - Some("cs") => c_sharp.push(path), + Some("cs") => { + if cfg!(windows) { + c_sharp.push(path); + } + } _ => {} } } @@ -135,7 +139,6 @@ fn tests(name: &str, dir_name: &str) -> Result> { out_dir.push("runtime-tests"); out_dir.push(name); - println!("wasi adapter = {:?}", test_artifacts::ADAPTER); let wasi_adapter = std::fs::read(&test_artifacts::ADAPTER)?; drop(std::fs::remove_dir_all(&out_dir)); @@ -474,9 +477,13 @@ fn tests(name: &str, dir_name: &str) -> Result> { result.push(component_path); } - #[cfg(feature = "csharp")] + #[cfg(feature = "csharp-mono")] + for path in c_sharp.iter() { + todo!() + } + + #[cfg(feature = "csharp-naot")] for path in c_sharp.iter() { - println!("running for {}", path.display()); let world_name = &resolve.worlds[world].name; let out_dir = out_dir.join(format!("csharp-{}", world_name)); drop(fs::remove_dir_all(&out_dir)); @@ -637,12 +644,20 @@ fn tests(name: &str, dir_name: &str) -> Result> { let file_name = path.file_name().unwrap(); fs::copy(path, out_dir.join(file_name.to_str().unwrap()))?; - let mut cmd = Command::new("dotnet"); + let dotnet_root_env = "DOTNET_ROOT"; + let dotnet_cmd: PathBuf; + match env::var(dotnet_root_env) { + Ok(val) => dotnet_cmd = Path::new(&val).join("dotnet"), + Err(_e) => dotnet_cmd = "dotnet".into(), + } + + let mut cmd = Command::new(dotnet_cmd); let mut wasm_filename = out_wasm.join(assembly_name); wasm_filename.set_extension("wasm"); cmd.current_dir(&out_dir); + // add .arg("/bl") to diagnose dotnet build problems cmd.arg("publish") .arg(out_dir.join(format!("{camel}.csproj"))) .arg("-r") @@ -653,14 +668,12 @@ fn tests(name: &str, dir_name: &str) -> Result> { .arg("/p:MSBuildEnableWorkloadResolver=false") .arg("--self-contained") .arg("/p:UseAppHost=false") - .arg("/bl") .arg("-o") .arg(&out_wasm); let output = match cmd.output() { Ok(output) => output, Err(e) => panic!("failed to spawn compiler: {}", e), }; - println!("{:?} completed", cmd); if !output.status.success() { println!("status: {}", output.status); diff --git a/tests/runtime/numbers/wasm.cs b/tests/runtime/numbers/wasm.cs new file mode 100644 index 000000000..2dda14972 --- /dev/null +++ b/tests/runtime/numbers/wasm.cs @@ -0,0 +1,133 @@ +using System; +using System.Runtime.InteropServices; +using System.Diagnostics; +using wit_numbers.Wit.imports.test.numbers.Test; + +namespace wit_numbers; + +public class NumbersWorldImpl : NumbersWorld +{ + public static void TestImports() + { + Debug.Assert(TestInterop.RoundtripU8(1) == 1); + Debug.Assert(TestInterop.RoundtripU8(0) == 0); + Debug.Assert(TestInterop.RoundtripU8(Byte.MaxValue) == Byte.MaxValue); + + Debug.Assert(TestInterop.RoundtripS8(1) == 1); + Debug.Assert(TestInterop.RoundtripS8(SByte.MinValue) == SByte.MinValue); + Debug.Assert(TestInterop.RoundtripS8(SByte.MaxValue) == SByte.MaxValue); + + Debug.Assert(TestInterop.RoundtripU16(1) == 1); + Debug.Assert(TestInterop.RoundtripU16(0) == 0); + Debug.Assert(TestInterop.RoundtripU16(UInt16.MaxValue) == UInt16.MaxValue); + + Debug.Assert(TestInterop.RoundtripS16(1) == 1); + Debug.Assert(TestInterop.RoundtripS16(Int16.MinValue) == Int16.MinValue); + Debug.Assert(TestInterop.RoundtripS16(Int16.MaxValue) == Int16.MaxValue); + + Debug.Assert(TestInterop.RoundtripU32(1) == 1); + Debug.Assert(TestInterop.RoundtripU32(0) == 0); + Debug.Assert(TestInterop.RoundtripU32(UInt32.MaxValue) == UInt32.MaxValue); + + Debug.Assert(TestInterop.RoundtripS32(1) == 1); + Debug.Assert(TestInterop.RoundtripS32(Int32.MinValue) == Int32.MinValue); + Debug.Assert(TestInterop.RoundtripS32(Int32.MaxValue) == Int32.MaxValue); + + Debug.Assert(TestInterop.RoundtripU64(1) == 1); + Debug.Assert(TestInterop.RoundtripU64(0) == 0); + Debug.Assert(TestInterop.RoundtripU64(UInt64.MaxValue) == UInt64.MaxValue); + + Debug.Assert(TestInterop.RoundtripS64(1) == 1); + Debug.Assert(TestInterop.RoundtripS64(Int64.MinValue) == Int64.MinValue); + Debug.Assert(TestInterop.RoundtripS64(Int64.MaxValue) == Int64.MaxValue); + + Debug.Assert(TestInterop.RoundtripFloat32(1.0f) == 1.0f); + Debug.Assert(TestInterop.RoundtripFloat32(Single.PositiveInfinity) == Single.PositiveInfinity); + Debug.Assert(TestInterop.RoundtripFloat32(Single.NegativeInfinity) == Single.NegativeInfinity); + Debug.Assert(float.IsNaN(TestInterop.RoundtripFloat32(Single.NaN))); + + Debug.Assert(TestInterop.RoundtripFloat64(1.0) == 1.0); + Debug.Assert(TestInterop.RoundtripFloat64(Double.PositiveInfinity) == Double.PositiveInfinity); + Debug.Assert(TestInterop.RoundtripFloat64(Double.NegativeInfinity) == Double.NegativeInfinity); + Debug.Assert(double.IsNaN(TestInterop.RoundtripFloat64(Double.NaN))); + + Debug.Assert(TestInterop.RoundtripChar('a') == 'a'); + Debug.Assert(TestInterop.RoundtripChar(' ') == ' '); + Debug.Assert(Char.ConvertFromUtf32((int)TestInterop.RoundtripChar((uint)Char.ConvertToUtf32("🚩", 0))) == "🚩"); // This is 2 chars long as it contains a surrogate pair + + TestInterop.SetScalar(2); + Debug.Assert(TestInterop.GetScalar() == 2); + TestInterop.SetScalar(4); + Debug.Assert(TestInterop.GetScalar() == 4); + } +} + +public class TestImpl : wit_numbers.Wit.exports.test.numbers.Test.Test +{ + static uint SCALAR = 0; + + public static byte RoundtripU8(byte p0) + { + return p0; + } + + public static sbyte RoundtripS8(sbyte p0) + { + return p0; + } + + public static ushort RoundtripU16(ushort p0) + { + return p0; + } + + public static short RoundtripS16(short p0) + { + return p0; + } + + public static uint RoundtripU32(uint p0) + { + return p0; + } + + public static int RoundtripS32(int p0) + { + return p0; + } + + public static ulong RoundtripU64(ulong p0) + { + return p0; + } + + public static long RoundtripS64(long p0) + { + return p0; + } + + public static float RoundtripFloat32(float p0) + { + return p0; + } + + public static double RoundtripFloat64(double p0) + { + return p0; + } + + public static uint RoundtripChar(uint p0) + { + return p0; + } + + public static void SetScalar(uint p0) + { + SCALAR = p0; + } + + public static uint GetScalar() + { + return SCALAR; + } +} \ No newline at end of file