Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C# enable the runtime numbers test #718

Merged
merged 5 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ jobs:
echo "WASI_SDK_PATH=`pwd`/wasi-sdk-16.0" >> $GITHUB_ENV
if : matrix.os == 'windows-latest'

- run: |
curl.exe -LO 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.exe -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
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,16 @@ default = [
'markdown',
'teavm-java',
'go',
'csharp',
'csharp-naot',
]
c = ['dep:wit-bindgen-c']
rust = ['dep:wit-bindgen-rust']
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 }
Expand Down
78 changes: 30 additions & 48 deletions crates/csharp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),

Expand Down Expand Up @@ -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::<Vec<String>>();

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(", ");
Expand All @@ -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::<Vec<&str>>()
.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::<Vec<&str>>()
.join(", ");
uwriteln!(self.src, "return ({cast})({results});")
}
}
},

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

Expand Down
33 changes: 23 additions & 10 deletions tests/runtime/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -115,15 +115,19 @@ fn tests(name: &str, dir_name: &str) -> Result<Vec<PathBuf>> {
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<PathBuf> = Vec::new();
for file in dir.read_dir()? {
let path = file?.path();
match path.extension().and_then(|s| s.to_str()) {
Some("c") => c.push(path),
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);
}
Comment on lines +127 to +129
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: When mono is added we will want to still add this for the linux case? Since we aren't running the tests on linux right now, is this needed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is the yml matrix in build.yml tries to run all the OSes and if it creates the c# files then it will fail to compile them. Agree we will have to revisit this for Mono, not sure about MacOS, but for Linux we will want it.

}
_ => {}
}
}
Expand All @@ -135,7 +139,6 @@ fn tests(name: &str, dir_name: &str) -> Result<Vec<PathBuf>> {
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));
Expand Down Expand Up @@ -474,9 +477,13 @@ fn tests(name: &str, dir_name: &str) -> Result<Vec<PathBuf>> {
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));
Expand Down Expand Up @@ -637,12 +644,20 @@ fn tests(name: &str, dir_name: &str) -> Result<Vec<PathBuf>> {
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")
Expand All @@ -653,14 +668,12 @@ fn tests(name: &str, dir_name: &str) -> Result<Vec<PathBuf>> {
.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);
Expand Down
133 changes: 133 additions & 0 deletions tests/runtime/numbers/wasm.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}