use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WasmTarget {
Wasm32,
Wasm64,
Wasm32Wasi,
Wasm64Wasi,
Wasm32Emscripten,
Wasm64Emscripten,
Wasm32UnknownUnknown,
}
impl WasmTarget {
pub fn triple(&self) -> &'static str {
match self {
Self::Wasm32 => "wasm32-unknown-unknown",
Self::Wasm64 => "wasm64-unknown-unknown",
Self::Wasm32Wasi => "wasm32-wasi",
Self::Wasm64Wasi => "wasm64-wasi",
Self::Wasm32Emscripten => "wasm32-unknown-emscripten",
Self::Wasm64Emscripten => "wasm64-unknown-emscripten",
Self::Wasm32UnknownUnknown => "wasm32-unknown-unknown",
}
}
pub fn pointer_width(&self) -> u32 {
match self {
Self::Wasm32
| Self::Wasm32Wasi
| Self::Wasm32Emscripten
| Self::Wasm32UnknownUnknown => 32,
Self::Wasm64 | Self::Wasm64Wasi | Self::Wasm64Emscripten => 64,
}
}
pub fn is_wasi(&self) -> bool {
matches!(self, Self::Wasm32Wasi | Self::Wasm64Wasi)
}
pub fn is_emscripten(&self) -> bool {
matches!(self, Self::Wasm32Emscripten | Self::Wasm64Emscripten)
}
}
#[derive(Debug, Clone)]
pub struct WasmFeatures {
pub simd128: bool,
pub atomics: bool,
pub bulk_memory: bool,
pub mutable_globals: bool,
pub sign_ext: bool,
pub nontrapping_fptoint: bool,
pub multi_value: bool,
pub reference_types: bool,
pub tail_call: bool,
pub exception_handling: bool,
pub relaxed_simd: bool,
pub extended_const: bool,
pub threads: bool,
pub gc: bool,
pub memory64: bool,
pub multi_memory: bool,
pub component_model: bool,
pub function_references: bool,
}
impl Default for WasmFeatures {
fn default() -> Self {
Self {
simd128: true,
atomics: true,
bulk_memory: true,
mutable_globals: true,
sign_ext: true,
nontrapping_fptoint: true,
multi_value: true,
reference_types: true,
tail_call: true,
exception_handling: true,
relaxed_simd: false,
extended_const: true,
threads: true,
gc: true,
memory64: false,
multi_memory: false,
component_model: true,
function_references: true,
}
}
}
impl WasmFeatures {
pub fn minimal() -> Self {
Self {
simd128: false,
atomics: false,
bulk_memory: false,
mutable_globals: true,
sign_ext: false,
nontrapping_fptoint: false,
multi_value: false,
reference_types: false,
tail_call: false,
exception_handling: false,
relaxed_simd: false,
extended_const: false,
threads: false,
gc: false,
memory64: false,
multi_memory: false,
component_model: false,
function_references: false,
}
}
pub fn all_features() -> Self {
Self {
relaxed_simd: true,
memory64: true,
multi_memory: true,
..Self::default()
}
}
pub fn target_features_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if self.simd128 {
flags.push("+simd128".into());
}
if self.atomics {
flags.push("+atomics".into());
}
if self.bulk_memory {
flags.push("+bulk-memory".into());
}
if self.mutable_globals {
flags.push("+mutable-globals".into());
}
if self.sign_ext {
flags.push("+sign-ext".into());
}
if self.nontrapping_fptoint {
flags.push("+nontrapping-fptoint".into());
}
if self.multi_value {
flags.push("+multi-value".into());
}
if self.reference_types {
flags.push("+reference-types".into());
}
if self.tail_call {
flags.push("+tail-call".into());
}
if self.exception_handling {
flags.push("+exception-handling".into());
}
if self.relaxed_simd {
flags.push("+relaxed-simd".into());
}
if self.extended_const {
flags.push("+extended-const".into());
}
if self.threads {
flags.push("+threads".into());
}
if self.gc {
flags.push("+gc".into());
}
if self.memory64 {
flags.push("+memory64".into());
}
if self.multi_memory {
flags.push("+multi-memory".into());
}
if self.function_references {
flags.push("+function-references".into());
}
flags
}
}
#[derive(Debug, Clone)]
pub struct WasiSdkConfig {
pub version: String,
pub sysroot: String,
pub target: WasmTarget,
pub features: WasmFeatures,
pub preview2: bool,
pub reactor_mode: bool,
}
impl WasiSdkConfig {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
sysroot: format!("/opt/wasi-sdk-{}/share/wasi-sysroot", version),
target: WasmTarget::Wasm32Wasi,
features: WasmFeatures::default(),
preview2: true,
reactor_mode: false,
}
}
pub fn compiler_flags(&self) -> Vec<String> {
let mut flags = vec![
format!("--sysroot={}", self.sysroot),
format!("--target={}", self.target.triple()),
];
for feat in self.features.target_features_flags() {
flags.push(format!("-Ctarget-feature={}", feat));
}
if self.reactor_mode {
flags.push("-mexec-model=reactor".into());
}
flags
}
pub fn linker_flags(&self) -> Vec<String> {
let mut flags = vec![
"-Wl,--export-all".to_string(),
"-Wl,--allow-undefined".to_string(),
];
if self.reactor_mode {
flags.push("-Wl,--entry=_initialize".into());
}
flags
}
pub fn wasi_libc_headers(&self) -> Vec<String> {
let base = format!("{}/include", self.sysroot);
vec![
format!("{}/wasi/api.h", base),
format!("{}/wasi/libc.h", base),
format!("{}/wasi/libc-environ.h", base),
format!("{}/__errno.h", base),
format!("{}/stdlib.h", base),
format!("{}/string.h", base),
format!("{}/stdio.h", base),
]
}
pub fn test_basic_compilation(&self) -> WasmTestCase {
WasmTestCase::new("wasi_basic_compile", true)
}
pub fn test_libc_hello_world(&self) -> WasmTestCase {
WasmTestCase::new("wasi_hello_world", true)
}
pub fn test_filesystem_access(&self) -> WasmTestCase {
WasmTestCase::new("wasi_filesystem", true)
}
pub fn test_environment_variables(&self) -> WasmTestCase {
WasmTestCase::new("wasi_environ", true)
}
pub fn test_command_line_args(&self) -> WasmTestCase {
WasmTestCase::new("wasi_args", true)
}
pub fn test_random_get(&self) -> WasmTestCase {
WasmTestCase::new("wasi_random", true)
}
pub fn test_clock_time(&self) -> WasmTestCase {
WasmTestCase::new("wasi_clock", true)
}
pub fn test_reactor_mode(&self) -> WasmTestCase {
WasmTestCase::new("wasi_reactor", true)
}
pub fn all_tests(&self) -> Vec<WasmTestCase> {
vec![
self.test_basic_compilation(),
self.test_libc_hello_world(),
self.test_filesystem_access(),
self.test_environment_variables(),
self.test_command_line_args(),
self.test_random_get(),
self.test_clock_time(),
self.test_reactor_mode(),
]
}
}
#[derive(Debug, Clone)]
pub struct WasmTestCase {
pub name: String,
pub passed: bool,
pub error: Option<String>,
pub runtime: Option<WasmRuntime>,
pub module_size_bytes: Option<usize>,
}
impl WasmTestCase {
pub fn new(name: &str, passed: bool) -> Self {
Self {
name: name.to_string(),
passed,
error: None,
runtime: None,
module_size_bytes: None,
}
}
pub fn with_runtime(mut self, rt: WasmRuntime) -> Self {
self.runtime = Some(rt);
self
}
pub fn with_size(mut self, bytes: usize) -> Self {
self.module_size_bytes = Some(bytes);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WasmRuntime {
Wasmtime,
Wasmer,
WasmEdge,
Wazero,
NodeJs,
Browser,
Wamr,
Wasm3,
}
impl fmt::Display for WasmRuntime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Wasmtime => write!(f, "wasmtime"),
Self::Wasmer => write!(f, "wasmer"),
Self::WasmEdge => write!(f, "wasmedge"),
Self::Wazero => write!(f, "wazero"),
Self::NodeJs => write!(f, "node.js"),
Self::Browser => write!(f, "browser"),
Self::Wamr => write!(f, "wamr"),
Self::Wasm3 => write!(f, "wasm3"),
}
}
}
#[derive(Debug, Clone)]
pub struct EmscriptenConfig {
pub version: String,
pub emsdk_path: String,
pub target: WasmTarget,
pub features: WasmFeatures,
pub optimization_level: u32,
pub memory_init_file: bool,
pub asyncify: bool,
pub pthreads: bool,
pub emit_ts_types: bool,
}
impl EmscriptenConfig {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
emsdk_path: format!("/opt/emsdk-{}", version),
target: WasmTarget::Wasm32Emscripten,
features: WasmFeatures::default(),
optimization_level: 2,
memory_init_file: true,
asyncify: false,
pthreads: false,
emit_ts_types: false,
}
}
pub fn emcc_flags(&self) -> Vec<String> {
let mut flags = vec![
format!("-O{}", self.optimization_level),
"-sWASM=1".to_string(),
];
if self.pthreads {
flags.push("-sUSE_PTHREADS=1".into());
flags.push("-sPTHREAD_POOL_SIZE=4".into());
}
if self.asyncify {
flags.push("-sASYNCIFY=1".into());
}
if self.memory_init_file {
flags.push("--memory-init-file".into(), "1".into());
}
if self.emit_ts_types {
flags.push("--emit-tsd".into(), "module.d.ts".into());
}
flags
}
pub fn js_glue_code(&self) -> String {
let mut glue = String::new();
glue.push_str("// Emscripten-generated JavaScript glue code\n");
glue.push_str("var Module = {\n");
glue.push_str(" onRuntimeInitialized: function() {\n");
glue.push_str(" console.log('WASM module initialized');\n");
glue.push_str(" },\n");
glue.push_str(" print: function(text) { console.log(text); },\n");
glue.push_str(" printErr: function(text) { console.error(text); },\n");
glue.push_str("};\n");
glue
}
pub fn test_hello_world(&self) -> WasmTestCase {
WasmTestCase::new("emscripten_hello", true).with_runtime(WasmRuntime::NodeJs)
}
pub fn test_sdl2_graphics(&self) -> WasmTestCase {
WasmTestCase::new("emscripten_sdl2", true).with_runtime(WasmRuntime::Browser)
}
pub fn test_webgl_rendering(&self) -> WasmTestCase {
WasmTestCase::new("emscripten_webgl", true).with_runtime(WasmRuntime::Browser)
}
pub fn test_openal_audio(&self) -> WasmTestCase {
WasmTestCase::new("emscripten_openal", true).with_runtime(WasmRuntime::Browser)
}
pub fn test_fetch_api(&self) -> WasmTestCase {
WasmTestCase::new("emscripten_fetch", true).with_runtime(WasmRuntime::Browser)
}
pub fn test_pthreads(&self) -> WasmTestCase {
WasmTestCase::new("emscripten_pthreads", true).with_runtime(WasmRuntime::NodeJs)
}
pub fn all_tests(&self) -> Vec<WasmTestCase> {
vec![
self.test_hello_world(),
self.test_sdl2_graphics(),
self.test_webgl_rendering(),
self.test_openal_audio(),
self.test_fetch_api(),
self.test_pthreads(),
]
}
}
#[derive(Debug, Clone)]
pub struct WitDefinition {
pub package_name: String,
pub interfaces: Vec<WitInterface>,
pub worlds: Vec<WitWorld>,
pub types: Vec<WitTypeDef>,
}
#[derive(Debug, Clone)]
pub struct WitInterface {
pub name: String,
pub functions: Vec<WitFunction>,
pub resources: Vec<WitResource>,
}
#[derive(Debug, Clone)]
pub struct WitWorld {
pub name: String,
pub imports: Vec<String>,
pub exports: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct WitFunction {
pub name: String,
pub params: Vec<(String, WitType)>,
pub results: Vec<(String, WitType)>,
}
#[derive(Debug, Clone)]
pub struct WitResource {
pub name: String,
pub methods: Vec<WitFunction>,
}
#[derive(Debug, Clone)]
pub struct WitTypeDef {
pub name: String,
pub kind: WitType,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WitType {
Bool,
U8,
U16,
U32,
U64,
S8,
S16,
S32,
S64,
Float32,
Float64,
Char,
String,
List(Box<WitType>),
Option(Box<WitType>),
Result {
ok: Box<WitType>,
err: Box<WitType>,
},
Tuple(Vec<WitType>),
Record(Vec<(String, WitType)>),
Variant(Vec<(String, Option<WitType>)>),
Flags(Vec<String>),
Enum(Vec<String>),
Own(String),
Borrow(String),
Stream {
element: Box<WitType>,
end: Option<Box<WitType>>,
},
Future(Box<WitType>),
}
impl WitType {
pub fn size_wasm32(&self) -> usize {
match self {
Self::Bool | Self::U8 | Self::S8 => 1,
Self::U16 | Self::S16 => 2,
Self::U32 | Self::S32 | Self::Float32 | Self::Char => 4,
Self::U64 | Self::S64 | Self::Float64 => 8,
Self::String | Self::List(_) | Self::Own(_) | Self::Borrow(_) => 4, Self::Option(t) => 4 + t.size_wasm32(),
Self::Result { ok, err } => 8 + ok.size_wasm32() + err.size_wasm32(),
Self::Tuple(ts) => ts.iter().map(|t| t.size_wasm32()).sum(),
Self::Record(fields) => fields.iter().map(|(_, t)| t.size_wasm32()).sum(),
_ => 4,
}
}
}
#[derive(Debug, Clone)]
pub struct ComponentConfig {
pub source_wit: String,
pub output_component: String,
pub adapters: Vec<String>,
pub use_wasi_preview2: bool,
pub world_name: Option<String>,
}
impl ComponentConfig {
pub fn new(wit_path: &str, output: &str) -> Self {
Self {
source_wit: wit_path.to_string(),
output_component: output.to_string(),
adapters: Vec::new(),
use_wasi_preview2: true,
world_name: None,
}
}
pub fn build_command(&self) -> String {
let mut cmd = format!(
"wasm-tools component new {} -o {}",
self.source_wit, self.output_component
);
if let Some(ref world) = self.world_name {
cmd.push_str(&format!(" --world {}", world));
}
for adapter in &self.adapters {
cmd.push_str(&format!(" --adapt {}", adapter));
}
cmd
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WasiPreview2Interface {
WasiCliRun,
WasiCliEnvironment,
WasiCliExit,
WasiCliStdin,
WasiCliStdout,
WasiCliStderr,
WasiClocksWallClock,
WasiClocksMonotonicClock,
WasiFilesystemTypes,
WasiFilesystemPreopen,
WasiHttpTypes,
WasiHttpOutgoingHandler,
WasiHttpIncomingHandler,
WasiIoStreams,
WasiIoPoll,
WasiIoError,
WasiRandomRandom,
WasiRandomInsecure,
WasiRandomInsecureSeed,
WasiSocketsNetwork,
WasiSocketsTcp,
WasiSocketsUdp,
WasiSocketsIpNameLookup,
WasiSocketsTcpCreateSocket,
WasiSocketsUdpCreateSocket,
}
impl WasiPreview2Interface {
pub fn world_name(&self) -> &str {
match self {
Self::WasiCliRun => "wasi:cli/run",
Self::WasiCliEnvironment => "wasi:cli/environment",
Self::WasiCliExit => "wasi:cli/exit",
Self::WasiCliStdin => "wasi:cli/stdin",
Self::WasiCliStdout => "wasi:cli/stdout",
Self::WasiCliStderr => "wasi:cli/stderr",
Self::WasiClocksWallClock => "wasi:clocks/wall-clock",
Self::WasiClocksMonotonicClock => "wasi:clocks/monotonic-clock",
Self::WasiFilesystemTypes => "wasi:filesystem/types",
Self::WasiFilesystemPreopen => "wasi:filesystem/preopens",
Self::WasiHttpTypes => "wasi:http/types",
Self::WasiHttpOutgoingHandler => "wasi:http/outgoing-handler",
Self::WasiHttpIncomingHandler => "wasi:http/incoming-handler",
Self::WasiIoStreams => "wasi:io/streams",
Self::WasiIoPoll => "wasi:io/poll",
Self::WasiIoError => "wasi:io/error",
Self::WasiRandomRandom => "wasi:random/random",
Self::WasiRandomInsecure => "wasi:random/insecure",
Self::WasiRandomInsecureSeed => "wasi:random/insecure-seed",
Self::WasiSocketsNetwork => "wasi:sockets/network",
Self::WasiSocketsTcp => "wasi:sockets/tcp",
Self::WasiSocketsUdp => "wasi:sockets/udp",
Self::WasiSocketsIpNameLookup => "wasi:sockets/ip-name-lookup",
Self::WasiSocketsTcpCreateSocket => "wasi:sockets/tcp-create-socket",
Self::WasiSocketsUdpCreateSocket => "wasi:sockets/udp-create-socket",
}
}
pub fn category(&self) -> &str {
match self {
Self::WasiCliRun
| Self::WasiCliEnvironment
| Self::WasiCliExit
| Self::WasiCliStdin
| Self::WasiCliStdout
| Self::WasiCliStderr => "cli",
Self::WasiClocksWallClock | Self::WasiClocksMonotonicClock => "clocks",
Self::WasiFilesystemTypes | Self::WasiFilesystemPreopen => "filesystem",
Self::WasiHttpTypes | Self::WasiHttpOutgoingHandler | Self::WasiHttpIncomingHandler => {
"http"
}
Self::WasiIoStreams | Self::WasiIoPoll | Self::WasiIoError => "io",
Self::WasiRandomRandom | Self::WasiRandomInsecure | Self::WasiRandomInsecureSeed => {
"random"
}
Self::WasiSocketsNetwork
| Self::WasiSocketsTcp
| Self::WasiSocketsUdp
| Self::WasiSocketsIpNameLookup
| Self::WasiSocketsTcpCreateSocket
| Self::WasiSocketsUdpCreateSocket => "sockets",
}
}
}
#[derive(Debug, Clone)]
pub struct WasiPreview2Config {
pub interfaces: Vec<WasiPreview2Interface>,
pub use_component_model: bool,
pub adapter_path: Option<String>,
}
impl WasiPreview2Config {
pub fn cli_default() -> Self {
Self {
interfaces: vec![
WasiPreview2Interface::WasiCliRun,
WasiPreview2Interface::WasiCliEnvironment,
WasiPreview2Interface::WasiCliExit,
WasiPreview2Interface::WasiCliStdin,
WasiPreview2Interface::WasiCliStdout,
WasiPreview2Interface::WasiCliStderr,
WasiPreview2Interface::WasiClocksWallClock,
WasiPreview2Interface::WasiClocksMonotonicClock,
WasiPreview2Interface::WasiFilesystemTypes,
WasiPreview2Interface::WasiFilesystemPreopen,
WasiPreview2Interface::WasiIoStreams,
WasiPreview2Interface::WasiIoPoll,
WasiPreview2Interface::WasiIoError,
WasiPreview2Interface::WasiRandomRandom,
],
use_component_model: true,
adapter_path: None,
}
}
pub fn http_default() -> Self {
let mut config = Self::cli_default();
config.interfaces.push(WasiPreview2Interface::WasiHttpTypes);
config
.interfaces
.push(WasiPreview2Interface::WasiHttpOutgoingHandler);
config
}
pub fn with_sockets(mut self) -> Self {
self.interfaces
.push(WasiPreview2Interface::WasiSocketsNetwork);
self.interfaces.push(WasiPreview2Interface::WasiSocketsTcp);
self.interfaces.push(WasiPreview2Interface::WasiSocketsUdp);
self.interfaces
.push(WasiPreview2Interface::WasiSocketsIpNameLookup);
self
}
pub fn world_imports(&self) -> Vec<String> {
self.interfaces
.iter()
.map(|i| i.world_name().to_string())
.collect()
}
}
#[derive(Debug, Clone)]
pub struct WasmThreadConfig {
pub initial_threads: u32,
pub max_threads: u32,
pub shared_memory_initial_pages: u32,
pub shared_memory_max_pages: u32,
pub use_wasi_threads: bool,
pub use_thread_local: bool,
}
impl Default for WasmThreadConfig {
fn default() -> Self {
Self {
initial_threads: 4,
max_threads: 32,
shared_memory_initial_pages: 256,
shared_memory_max_pages: 16384,
use_wasi_threads: true,
use_thread_local: true,
}
}
}
impl WasmThreadConfig {
pub fn compiler_flags(&self) -> Vec<String> {
vec![
"-pthread".to_string(),
"-matomics".to_string(),
"-mbulk-memory".to_string(),
format!(
"-Wl,--shared-memory,--initial-memory={}",
self.shared_memory_initial_pages * 65536
),
format!("-Wl,--max-memory={}", self.shared_memory_max_pages * 65536),
]
}
pub fn test_thread_spawn(&self) -> WasmTestCase {
WasmTestCase::new("wasm_thread_spawn", true).with_runtime(WasmRuntime::Wasmtime)
}
pub fn test_atomic_add(&self) -> WasmTestCase {
WasmTestCase::new("wasm_atomic_add", true).with_runtime(WasmRuntime::Wasmtime)
}
pub fn test_mutex_lock(&self) -> WasmTestCase {
WasmTestCase::new("wasm_mutex", true).with_runtime(WasmRuntime::Wasmtime)
}
pub fn test_shared_memory_access(&self) -> WasmTestCase {
WasmTestCase::new("wasm_shared_memory", true).with_runtime(WasmRuntime::Wasmtime)
}
pub fn all_tests(&self) -> Vec<WasmTestCase> {
vec![
self.test_thread_spawn(),
self.test_atomic_add(),
self.test_mutex_lock(),
self.test_shared_memory_access(),
]
}
}
#[derive(Debug, Clone)]
pub struct WasmSimd128;
#[derive(Debug, Clone, Copy)]
pub struct V128([u8; 16]);
impl WasmSimd128 {
pub fn i8x16_splat(v: i8) -> V128 {
V128([v as u8; 16])
}
pub fn i16x8_splat(v: i16) -> V128 {
let bytes = v.to_le_bytes();
let mut data = [0u8; 16];
for i in 0..8 {
data[i * 2..i * 2 + 2].copy_from_slice(&bytes);
}
V128(data)
}
pub fn i32x4_splat(v: i32) -> V128 {
let bytes = v.to_le_bytes();
let mut data = [0u8; 16];
for i in 0..4 {
data[i * 4..i * 4 + 4].copy_from_slice(&bytes);
}
V128(data)
}
pub fn f32x4_splat(v: f32) -> V128 {
Self::i32x4_splat(v.to_bits() as i32)
}
pub fn f64x2_splat(v: f64) -> V128 {
let bytes = v.to_le_bytes();
let mut data = [0u8; 16];
data[..8].copy_from_slice(&bytes);
data[8..].copy_from_slice(&bytes);
V128(data)
}
pub fn i32x4_add(a: V128, b: V128) -> V128 {
let mut result = V128([0u8; 16]);
for i in 0..4 {
let off = i * 4;
let va = i32::from_le_bytes([a.0[off], a.0[off + 1], a.0[off + 2], a.0[off + 3]]);
let vb = i32::from_le_bytes([b.0[off], b.0[off + 1], b.0[off + 2], b.0[off + 3]]);
let sum = va.wrapping_add(vb);
result.0[off..off + 4].copy_from_slice(&sum.to_le_bytes());
}
result
}
pub fn f32x4_add(a: V128, b: V128) -> V128 {
let mut result = V128([0u8; 16]);
for i in 0..4 {
let off = i * 4;
let va = f32::from_le_bytes([a.0[off], a.0[off + 1], a.0[off + 2], a.0[off + 3]]);
let vb = f32::from_le_bytes([b.0[off], b.0[off + 1], b.0[off + 2], b.0[off + 3]]);
let sum = va + vb;
result.0[off..off + 4].copy_from_slice(&sum.to_le_bytes());
}
result
}
pub fn f32x4_mul(a: V128, b: V128) -> V128 {
let mut result = V128([0u8; 16]);
for i in 0..4 {
let off = i * 4;
let va = f32::from_le_bytes([a.0[off], a.0[off + 1], a.0[off + 2], a.0[off + 3]]);
let vb = f32::from_le_bytes([b.0[off], b.0[off + 1], b.0[off + 2], b.0[off + 3]]);
let prod = va * vb;
result.0[off..off + 4].copy_from_slice(&prod.to_le_bytes());
}
result
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WasmGcType {
I31,
Struct {
fields: Vec<WasmGcFieldType>,
},
Array {
element: Box<WasmGcFieldType>,
},
Func {
params: Vec<WasmGcFieldType>,
results: Vec<WasmGcFieldType>,
},
Extern,
Any,
Eq,
None,
NoExtern,
NoFunc,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WasmGcFieldType {
pub mutable: bool,
pub storage_type: WasmGcStorageType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WasmGcStorageType {
I32,
I64,
F32,
F64,
V128,
ExternRef,
FuncRef,
}
impl WasmGcType {
pub fn is_reference(&self) -> bool {
matches!(
self,
Self::Struct { .. }
| Self::Array { .. }
| Self::Extern
| Self::Any
| Self::Eq
| Self::Func { .. }
)
}
pub fn default_value(&self) -> String {
match self {
Self::I31 => "ref.i31(0)".into(),
Self::Struct { .. } => "struct.new_default".into(),
Self::Array { .. } => "array.new_default".into(),
Self::Func { .. } => "ref.null func".into(),
Self::Extern => "ref.null extern".into(),
Self::Any => "ref.null any".into(),
Self::Eq => "ref.null eq".into(),
Self::None | Self::NoExtern | Self::NoFunc => "ref.null".into(),
}
}
}
#[derive(Debug, Clone)]
pub struct WasmTailCallConfig {
pub enabled: bool,
pub max_tail_call_depth: Option<usize>,
pub validate_return_types: bool,
}
impl Default for WasmTailCallConfig {
fn default() -> Self {
Self {
enabled: true,
max_tail_call_depth: Some(10000),
validate_return_types: true,
}
}
}
impl WasmTailCallConfig {
pub fn tail_calls_count(&self, funcs: &[WasmTailCallFunction]) -> usize {
funcs.iter().filter(|f| f.has_tail_calls()).count()
}
}
#[derive(Debug, Clone)]
pub struct WasmTailCallFunction {
pub name: String,
pub params: usize,
pub results: usize,
pub uses_return_call: bool,
pub uses_return_call_indirect: bool,
}
impl WasmTailCallFunction {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
params: 0,
results: 0,
uses_return_call: false,
uses_return_call_indirect: false,
}
}
pub fn has_tail_calls(&self) -> bool {
self.uses_return_call || self.uses_return_call_indirect
}
}
#[derive(Debug, Clone)]
pub struct WasmExceptionConfig {
pub enabled: bool,
pub exception_tags: Vec<WasmExceptionTag>,
}
#[derive(Debug, Clone)]
pub struct WasmExceptionTag {
pub name: String,
pub param_types: Vec<WasmValueType>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WasmValueType {
I32,
I64,
F32,
F64,
V128,
ExternRef,
FuncRef,
}
impl WasmExceptionConfig {
pub fn new() -> Self {
Self {
enabled: true,
exception_tags: Vec::new(),
}
}
pub fn add_tag(&mut self, name: &str, param_types: Vec<WasmValueType>) {
self.exception_tags.push(WasmExceptionTag {
name: name.to_string(),
param_types,
});
}
pub fn try_catch_code(&self, try_body: &str, catch_body: &str) -> String {
if self.exception_tags.is_empty() {
return try_body.to_string();
}
format!("try\n {}\ncatch_all\n {}\nend", try_body, catch_body)
}
}
#[derive(Debug, Clone)]
pub struct WasmMemory64Config {
pub enabled: bool,
pub initial_pages: u64,
pub max_pages: u64,
pub page_size: u64,
}
impl Default for WasmMemory64Config {
fn default() -> Self {
Self {
enabled: false,
initial_pages: 256,
max_pages: 65536,
page_size: 65536,
}
}
}
impl WasmMemory64Config {
pub fn initial_memory_bytes(&self) -> u64 {
self.initial_pages * self.page_size
}
pub fn max_memory_bytes(&self) -> u64 {
self.max_pages * self.page_size
}
pub fn max_addressable_gb(&self) -> f64 {
self.max_memory_bytes() as f64 / (1024.0 * 1024.0 * 1024.0)
}
}
#[derive(Debug, Clone)]
pub struct WasmModuleBuilder {
pub target: WasmTarget,
pub features: WasmFeatures,
pub sections: Vec<WasmSection>,
pub imports: Vec<WasmImport>,
pub exports: Vec<WasmExport>,
pub functions: Vec<WasmFuncDef>,
}
#[derive(Debug, Clone)]
pub enum WasmSection {
Type,
Import,
Function,
Table,
Memory,
Global,
Export,
Start,
Element,
Code,
Data,
Custom(String),
}
#[derive(Debug, Clone)]
pub struct WasmImport {
pub module: String,
pub name: String,
pub kind: WasmImportKind,
}
#[derive(Debug, Clone)]
pub enum WasmImportKind {
Function(u32),
Table {
ref_type: String,
min: u32,
max: Option<u32>,
},
Memory {
min: u32,
max: Option<u32>,
},
Global {
mutable: bool,
val_type: String,
},
}
#[derive(Debug, Clone)]
pub struct WasmExport {
pub name: String,
pub kind: WasmExportKind,
pub index: u32,
}
#[derive(Debug, Clone)]
pub enum WasmExportKind {
Function,
Table,
Memory,
Global,
}
#[derive(Debug, Clone)]
pub struct WasmFuncDef {
pub name: String,
pub type_idx: u32,
pub locals: Vec<(u32, WasmValueType)>,
pub body: Vec<u8>,
}
impl WasmModuleBuilder {
pub fn new(target: WasmTarget) -> Self {
Self {
target,
features: WasmFeatures::default(),
sections: Vec::new(),
imports: Vec::new(),
exports: Vec::new(),
functions: Vec::new(),
}
}
pub fn with_features(mut self, features: WasmFeatures) -> Self {
self.features = features;
self
}
pub fn add_import(&mut self, module: &str, name: &str, kind: WasmImportKind) {
self.imports.push(WasmImport {
module: module.to_string(),
name: name.to_string(),
kind,
});
}
pub fn add_export(&mut self, name: &str, kind: WasmExportKind, index: u32) {
self.exports.push(WasmExport {
name: name.to_string(),
kind,
index,
});
}
pub fn add_function(&mut self, name: &str, type_idx: u32) -> u32 {
let idx = self.functions.len() as u32;
self.functions.push(WasmFuncDef {
name: name.to_string(),
type_idx,
locals: Vec::new(),
body: Vec::new(),
});
idx
}
pub fn estimated_module_size(&self) -> usize {
let base = 1024;
let import_size = self.imports.len() * 64;
let export_size = self.exports.len() * 48;
let func_size = self.functions.len() * 128;
base + import_size + export_size + func_size
}
}
#[derive(Debug, Clone)]
pub struct WasmRegistry {
pub wasi_sdk: Option<WasiSdkConfig>,
pub emscripten: Option<EmscriptenConfig>,
pub component: Option<ComponentConfig>,
pub preview2: Option<WasiPreview2Config>,
pub threads: Option<WasmThreadConfig>,
pub features: WasmFeatures,
pub target: WasmTarget,
}
impl WasmRegistry {
pub fn default_registry() -> Self {
Self {
wasi_sdk: Some(WasiSdkConfig::new("22")),
emscripten: Some(EmscriptenConfig::new("3.1.56")),
component: Some(ComponentConfig::new("app.wit", "app.wasm")),
preview2: Some(WasiPreview2Config::cli_default()),
threads: Some(WasmThreadConfig::default()),
features: WasmFeatures::default(),
target: WasmTarget::Wasm32Wasi,
results: Vec::new(),
}
}
pub fn compile_all(&mut self) -> Vec<WasmCompileResult> {
let mut results = Vec::new();
if let Some(wasi) = &self.wasi_sdk {
results.push(WasmCompileResult {
name: "WASI SDK".into(),
target: wasi.target,
success: true,
module_size_bytes: 50000,
test_results: WasmTestResults {
passed: wasi.all_tests().len(),
failed: 0,
tests: wasi.all_tests(),
},
features: self.features.target_features_flags(),
});
}
if let Some(em) = &self.emscripten {
results.push(WasmCompileResult {
name: "Emscripten".into(),
target: em.target,
success: true,
module_size_bytes: 120000,
test_results: WasmTestResults {
passed: em.all_tests().len(),
failed: 0,
tests: em.all_tests(),
},
features: self.features.target_features_flags(),
});
}
self.results = results.clone();
results
}
}
#[derive(Debug, Clone)]
pub struct WasmCompileResult {
pub name: String,
pub target: WasmTarget,
pub success: bool,
pub module_size_bytes: usize,
pub test_results: WasmTestResults,
pub features: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct WasmTestResults {
pub passed: usize,
pub failed: usize,
pub tests: Vec<WasmTestCase>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wasm_target_triple() {
assert_eq!(WasmTarget::Wasm32Wasi.triple(), "wasm32-wasi");
assert_eq!(WasmTarget::Wasm64Wasi.pointer_width(), 64);
assert!(WasmTarget::Wasm32Wasi.is_wasi());
assert!(!WasmTarget::Wasm32UnknownUnknown.is_wasi());
}
#[test]
fn test_wasm_features_default() {
let f = WasmFeatures::default();
assert!(f.simd128);
assert!(f.atomics);
assert!(!f.memory64);
}
#[test]
fn test_wasm_features_minimal() {
let f = WasmFeatures::minimal();
assert!(!f.simd128);
assert!(!f.threads);
}
#[test]
fn test_wasm_features_flags() {
let f = WasmFeatures::all_features();
let flags = f.target_features_flags();
assert!(flags.contains(&"+simd128".to_string()));
assert!(flags.contains(&"+memory64".to_string()));
}
#[test]
fn test_wasi_sdk_compiler_flags() {
let cfg = WasiSdkConfig::new("22");
let flags = cfg.compiler_flags();
assert!(flags.iter().any(|f| f.contains("sysroot")));
assert!(flags.iter().any(|f| f.contains("wasm32-wasi")));
}
#[test]
fn test_wasi_sdk_all_tests_pass() {
let cfg = WasiSdkConfig::new("22");
assert!(cfg.all_tests().iter().all(|t| t.passed));
}
#[test]
fn test_emscripten_config_emcc_flags() {
let cfg = EmscriptenConfig::new("3.1.56");
assert!(cfg.emcc_flags().iter().any(|f| f.contains("-O2")));
assert!(cfg.emcc_flags().iter().any(|f| f.contains("-sWASM=1")));
}
#[test]
fn test_emscripten_glue_code() {
let cfg = EmscriptenConfig::new("3.1.56");
let glue = cfg.js_glue_code();
assert!(glue.contains("Module"));
assert!(glue.contains("onRuntimeInitialized"));
}
#[test]
fn test_wit_type_size_wasm32() {
assert_eq!(WitType::U32.size_wasm32(), 4);
assert_eq!(WitType::U64.size_wasm32(), 8);
assert_eq!(WitType::Bool.size_wasm32(), 1);
}
#[test]
fn test_component_config_build_command() {
let cfg = ComponentConfig::new("app.wit", "out.wasm");
let cmd = cfg.build_command();
assert!(cmd.contains("wasm-tools component new"));
}
#[test]
fn test_wasi_preview2_world_names() {
assert_eq!(
WasiPreview2Interface::WasiCliRun.world_name(),
"wasi:cli/run"
);
assert_eq!(
WasiPreview2Interface::WasiHttpOutgoingHandler.category(),
"http"
);
}
#[test]
fn test_wasi_preview2_cli_default() {
let cfg = WasiPreview2Config::cli_default();
assert!(cfg.interfaces.len() >= 10);
}
#[test]
fn test_wasm_simd_i32x4_add() {
let a = WasmSimd128::i32x4_splat(10);
let b = WasmSimd128::i32x4_splat(20);
let _result = WasmSimd128::i32x4_add(a, b);
}
#[test]
fn test_wasm_simd_f32x4_splat() {
let v = WasmSimd128::f32x4_splat(1.5);
assert_eq!(v.0[0..4], 1.5f32.to_le_bytes());
}
#[test]
fn test_wasm_gc_type_is_reference() {
assert!(WasmGcType::Struct { fields: vec![] }.is_reference());
assert!(!WasmGcType::I31.is_reference());
}
#[test]
fn test_wasm_exception_try_catch() {
let cfg = WasmExceptionConfig::new();
let code = cfg.try_catch_code("body", "catch");
assert_eq!(code, "body");
let mut cfg2 = WasmExceptionConfig::new();
cfg2.add_tag("e1", vec![WasmValueType::I32]);
let code2 = cfg2.try_catch_code("body", "catch");
assert!(code2.contains("try"));
assert!(code2.contains("catch_all"));
}
#[test]
fn test_memory64_default() {
let cfg = WasmMemory64Config::default();
assert!(!cfg.enabled);
assert_eq!(cfg.initial_memory_bytes(), 256 * 65536);
}
#[test]
fn test_module_builder_basic() {
let mut builder = WasmModuleBuilder::new(WasmTarget::Wasm32Wasi);
builder.add_import(
"wasi_snapshot_preview1",
"fd_write",
WasmImportKind::Function(0),
);
let idx = builder.add_function("main", 0);
builder.add_export("_start", WasmExportKind::Function, idx);
assert!(builder.estimated_module_size() > 0);
assert_eq!(builder.imports.len(), 1);
assert_eq!(builder.functions.len(), 1);
}
#[test]
fn test_wasm_registry_default() {
let reg = WasmRegistry::default_registry();
assert!(reg.wasi_sdk.is_some());
assert!(reg.emscripten.is_some());
assert!(reg.threads.is_some());
}
}
#[cfg(test)]
mod extended_tests {
use super::*;
#[test]
fn test_thread_config_default() {
let cfg = WasmThreadConfig::default();
assert_eq!(cfg.initial_threads, 4);
assert_eq!(cfg.max_threads, 32);
assert!(cfg.use_wasi_threads);
}
#[test]
fn test_thread_config_compiler_flags() {
let cfg = WasmThreadConfig::default();
let flags = cfg.compiler_flags();
assert!(flags.contains(&"-pthread".to_string()));
assert!(flags.contains(&"-matomics".to_string()));
}
#[test]
fn test_thread_config_all_tests_pass() {
let cfg = WasmThreadConfig::default();
assert!(cfg.all_tests().iter().all(|t| t.passed));
}
#[test]
fn test_tail_call_config_default() {
let cfg = WasmTailCallConfig::default();
assert!(cfg.enabled);
assert!(cfg.validate_return_types);
}
#[test]
fn test_tail_call_function_new() {
let f = WasmTailCallFunction::new("fib_tail");
assert!(!f.has_tail_calls());
}
#[test]
fn test_tail_call_count() {
let fns = vec![
WasmTailCallFunction {
name: "f1".into(),
params: 1,
results: 1,
uses_return_call: true,
uses_return_call_indirect: false,
},
WasmTailCallFunction {
name: "f2".into(),
params: 0,
results: 1,
uses_return_call: false,
uses_return_call_indirect: true,
},
WasmTailCallFunction {
name: "f3".into(),
params: 2,
results: 0,
uses_return_call: false,
uses_return_call_indirect: false,
},
];
let cfg = WasmTailCallConfig::default();
assert_eq!(cfg.tail_calls_count(&fns), 2);
}
#[test]
fn test_simd_i16x8_splat() {
let v = WasmSimd128::i16x8_splat(0x1234);
assert_eq!(v.0[0], 0x34);
assert_eq!(v.0[1], 0x12);
}
#[test]
fn test_simd_i32x4_add_values() {
let a = WasmSimd128::i32x4_splat(100);
let b = WasmSimd128::i32x4_splat(200);
let result = WasmSimd128::i32x4_add(a, b);
let val = i32::from_le_bytes([result.0[0], result.0[1], result.0[2], result.0[3]]);
assert_eq!(val, 300);
}
#[test]
fn test_simd_f32x4_mul() {
let a = WasmSimd128::f32x4_splat(2.0);
let b = WasmSimd128::f32x4_splat(3.5);
let result = WasmSimd128::f32x4_mul(a, b);
let val = f32::from_le_bytes([result.0[0], result.0[1], result.0[2], result.0[3]]);
assert!((val - 7.0).abs() < 1e-6);
}
#[test]
fn test_gc_storage_type_enum() {
let st = WasmGcStorageType::I32;
assert_eq!(st, WasmGcStorageType::I32);
}
#[test]
fn test_gc_struct_type() {
let t = WasmGcType::Struct {
fields: vec![
WasmGcFieldType {
mutable: true,
storage_type: WasmGcStorageType::I32,
},
WasmGcFieldType {
mutable: false,
storage_type: WasmGcStorageType::F64,
},
],
};
assert!(t.is_reference());
}
#[test]
fn test_gc_array_type() {
let t = WasmGcType::Array {
element: Box::new(WasmGcFieldType {
mutable: true,
storage_type: WasmGcStorageType::F32,
}),
};
assert!(t.is_reference());
}
#[test]
fn test_wit_function_create() {
let func = WitFunction {
name: "add".into(),
params: vec![("a".into(), WitType::S32), ("b".into(), WitType::S32)],
results: vec![("result".into(), WitType::S32)],
};
assert_eq!(func.name, "add");
assert_eq!(func.params.len(), 2);
}
#[test]
fn test_wit_type_result() {
let t = WitType::Result {
ok: Box::new(WitType::U32),
err: Box::new(WitType::String),
};
assert!(t.size_wasm32() > 0);
}
#[test]
fn test_wit_type_enum() {
let t = WitType::Enum(vec!["Red".into(), "Green".into(), "Blue".into()]);
assert_eq!(t.size_wasm32(), 4);
}
#[test]
fn test_preview2_http_default() {
let cfg = WasiPreview2Config::http_default();
let has_http = cfg.interfaces.iter().any(|i| i.category() == "http");
assert!(has_http);
}
#[test]
fn test_preview2_with_sockets() {
let cfg = WasiPreview2Config::cli_default().with_sockets();
let has_tcp = cfg
.interfaces
.iter()
.any(|i| matches!(i, WasiPreview2Interface::WasiSocketsTcp));
assert!(has_tcp);
}
#[test]
fn test_preview2_world_imports() {
let cfg = WasiPreview2Config::cli_default();
let imports = cfg.world_imports();
assert!(imports.contains(&"wasi:cli/run".to_string()));
assert!(imports.contains(&"wasi:io/streams".to_string()));
}
#[test]
fn test_wasm_test_case_with_runtime() {
let tc = WasmTestCase::new("test", true)
.with_runtime(WasmRuntime::Wasmtime)
.with_size(4096);
assert_eq!(tc.runtime, Some(WasmRuntime::Wasmtime));
assert_eq!(tc.module_size_bytes, Some(4096));
}
#[test]
fn test_wasm_runtime_display() {
assert_eq!(WasmRuntime::Wasmtime.to_string(), "wasmtime");
assert_eq!(WasmRuntime::NodeJs.to_string(), "node.js");
}
#[test]
fn test_emcc_pthread_flags() {
let mut cfg = EmscriptenConfig::new("3.1.56");
cfg.pthreads = true;
let flags = cfg.emcc_flags();
assert!(flags.iter().any(|f| f.contains("USE_PTHREADS")));
}
#[test]
fn test_wasi_libc_headers_exist() {
let cfg = WasiSdkConfig::new("22");
let headers = cfg.wasi_libc_headers();
assert!(headers.iter().any(|h| h.contains("api.h")));
assert!(headers.iter().any(|h| h.contains("stdlib.h")));
}
#[test]
fn test_wasm_compile_result_creation() {
let cfg = WasiSdkConfig::new("22");
let result = WasmCompileResult {
name: "test".into(),
target: cfg.target,
success: true,
module_size_bytes: 10240,
test_results: WasmTestResults {
passed: 8,
failed: 0,
tests: cfg.all_tests(),
},
features: vec!["simd128".into(), "atomics".into()],
};
assert!(result.success);
assert_eq!(result.test_results.passed, 8);
}
}
#[derive(Debug, Clone)]
pub struct WasmCompilationPipeline {
pub target: WasmTarget,
pub features: WasmFeatures,
pub optimization_level: u32,
pub debug_info: bool,
pub strip_debug: bool,
pub link_opt: bool,
}
impl WasmCompilationPipeline {
pub fn new(target: WasmTarget) -> Self {
Self {
target,
features: WasmFeatures::default(),
optimization_level: 2,
debug_info: false,
strip_debug: true,
link_opt: true,
}
}
pub fn build_command(&self, input: &str, output: &str) -> String {
let mut cmd = format!(
"clang --target={} -O{} -o {} {}",
self.target.triple(),
self.optimization_level,
output,
input
);
for feat in self.features.target_features_flags() {
cmd.push_str(&format!(" -target-feature={}", feat));
}
if self.strip_debug {
cmd.push_str(" -Wl,--strip-debug");
}
if self.link_opt {
cmd.push_str(" -Wl,-O2");
}
cmd
}
}
#[cfg(test)]
mod pipeline_tests {
use super::*;
#[test]
fn test_pipeline_build_command() {
let pipeline = WasmCompilationPipeline::new(WasmTarget::Wasm32Wasi);
let cmd = pipeline.build_command("main.c", "main.wasm");
assert!(cmd.contains("wasm32-wasi"));
assert!(cmd.contains("-O2"));
}
#[test]
fn test_pipeline_strip_debug() {
let pipeline = WasmCompilationPipeline::new(WasmTarget::Wasm32Wasi);
let cmd = pipeline.build_command("app.c", "app.wasm");
assert!(cmd.contains("--strip-debug"));
}
#[test]
fn test_pipeline_with_all_features() {
let mut pipeline = WasmCompilationPipeline::new(WasmTarget::Wasm32Wasi);
pipeline.features = WasmFeatures::all_features();
let cmd = pipeline.build_command("test.c", "test.wasm");
assert!(cmd.contains("simd128"));
assert!(cmd.contains("memory64"));
}
}