use std::fmt;
use crate::Preview;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Feature {
SignExt,
NontrappingFptoint,
BulkMemoryOpt,
BulkMemory,
Multivalue,
MutableGlobals,
ExtendedConst,
CallIndirectOverlong,
ReferenceTypes,
ExceptionHandling,
TailCall,
Simd128,
RelaxedSimd,
WideArithmetic,
Atomics,
Multimemory,
Gc,
Fp16,
CompactImports,
RelaxedAtomics,
}
impl Feature {
pub const ALL: [Feature; 20] = [
Feature::SignExt,
Feature::NontrappingFptoint,
Feature::BulkMemoryOpt,
Feature::BulkMemory,
Feature::Multivalue,
Feature::MutableGlobals,
Feature::ExtendedConst,
Feature::CallIndirectOverlong,
Feature::ReferenceTypes,
Feature::ExceptionHandling,
Feature::TailCall,
Feature::Simd128,
Feature::RelaxedSimd,
Feature::WideArithmetic,
Feature::Atomics,
Feature::Multimemory,
Feature::Gc,
Feature::Fp16,
Feature::CompactImports,
Feature::RelaxedAtomics,
];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Feature::SignExt => "sign-ext",
Feature::NontrappingFptoint => "nontrapping-fptoint",
Feature::BulkMemoryOpt => "bulk-memory-opt",
Feature::BulkMemory => "bulk-memory",
Feature::Multivalue => "multivalue",
Feature::MutableGlobals => "mutable-globals",
Feature::ExtendedConst => "extended-const",
Feature::CallIndirectOverlong => "call-indirect-overlong",
Feature::ReferenceTypes => "reference-types",
Feature::ExceptionHandling => "exception-handling",
Feature::TailCall => "tail-call",
Feature::Simd128 => "simd128",
Feature::RelaxedSimd => "relaxed-simd",
Feature::WideArithmetic => "wide-arithmetic",
Feature::Atomics => "atomics",
Feature::Multimemory => "multimemory",
Feature::Gc => "gc",
Feature::Fp16 => "fp16",
Feature::CompactImports => "compact-imports",
Feature::RelaxedAtomics => "relaxed-atomics",
}
}
#[must_use]
pub fn named(name: &str) -> Option<Feature> {
Feature::ALL.into_iter().find(|f| f.name() == name)
}
#[must_use]
pub fn macro_name(self) -> Option<String> {
match self {
Feature::CallIndirectOverlong | Feature::CompactImports => None,
_ => Some(format!("__wasm_{}__", self.name().replace('-', "_"))),
}
}
#[must_use]
pub const fn needs(self) -> &'static [Feature] {
match self {
Feature::BulkMemory => &[Feature::BulkMemoryOpt],
Feature::ReferenceTypes => &[Feature::CallIndirectOverlong],
Feature::RelaxedSimd | Feature::Fp16 => &[Feature::Simd128],
Feature::Gc => &[Feature::ReferenceTypes],
_ => &[],
}
}
const fn bit(self) -> u32 {
1 << self as u32
}
}
impl fmt::Display for Feature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Features(u32);
impl Features {
pub const NONE: Features = Features(0);
#[must_use]
pub fn of(features: &[Feature]) -> Features {
features.iter().fold(Features::NONE, |set, &f| set.with(f))
}
#[must_use]
pub const fn has(self, feature: Feature) -> bool {
self.0 & feature.bit() != 0
}
#[must_use]
pub fn with(self, feature: Feature) -> Features {
let set = Features(self.0 | feature.bit());
feature.needs().iter().fold(set, |set, &f| set.with(f))
}
#[must_use]
pub fn closed(self) -> Features {
let mut set = self;
for f in [Feature::BulkMemory, Feature::Gc, Feature::ReferenceTypes] {
if set.has(f) {
set = set.with(f);
}
}
set
}
#[must_use]
pub const fn union(self, other: Features) -> Features {
Features(self.0 | other.0)
}
pub fn iter(self) -> impl Iterator<Item = Feature> {
Feature::ALL.into_iter().filter(move |&f| self.has(f))
}
pub fn macros(self) -> impl Iterator<Item = String> {
self.iter().filter_map(Feature::macro_name)
}
}
#[must_use]
pub fn resolve(cpu: Cpu, flags: &[(Feature, bool)]) -> Features {
let mut said: [Option<bool>; 20] = [None; 20];
for f in cpu.features().iter() {
said[f as usize] = Some(true);
}
for &(f, on) in flags {
said[f as usize] = Some(on);
match (f, on) {
(Feature::Simd128, false) => said[Feature::RelaxedSimd as usize] = Some(false),
(Feature::RelaxedSimd, true) => said[Feature::Simd128 as usize] = Some(true),
_ => {}
}
}
let mut set = Features::NONE;
let mut level = 0;
for f in Feature::ALL.into_iter().filter(|&f| said[f as usize] == Some(true)) {
level = level.max(match f {
Feature::Simd128 | Feature::Fp16 => 1,
Feature::RelaxedSimd => 2,
_ => 0,
});
set.0 |= f.bit();
}
for f in Feature::ALL.into_iter().filter(|&f| said[f as usize] == Some(false)) {
level = level.min(match f {
Feature::Simd128 => 0,
Feature::RelaxedSimd => 1,
_ => level,
});
}
set.0 &= !(Feature::Simd128.bit() | Feature::RelaxedSimd.bit());
if level >= 1 {
set.0 |= Feature::Simd128.bit();
}
if level >= 2 {
set.0 |= Feature::RelaxedSimd.bit();
}
set.closed()
}
#[must_use]
pub fn required(preview: Preview) -> Features {
match preview {
Preview::P1 | Preview::P2 => Features::NONE,
Preview::P3 => {
Features::of(&[Feature::BulkMemory, Feature::MutableGlobals, Feature::SignExt])
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Cpu {
Mvp,
Generic,
#[default]
Lime1,
BleedingEdge,
}
impl Cpu {
pub const ALL: [Cpu; 4] = [Cpu::Mvp, Cpu::Generic, Cpu::Lime1, Cpu::BleedingEdge];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Cpu::Mvp => "mvp",
Cpu::Generic => "generic",
Cpu::Lime1 => "lime1",
Cpu::BleedingEdge => "bleeding-edge",
}
}
#[must_use]
pub fn named(name: &str) -> Option<Cpu> {
Cpu::ALL.into_iter().find(|c| c.name() == name)
}
#[must_use]
pub fn features(self) -> Features {
use Feature::*;
match self {
Cpu::Mvp => Features::NONE,
Cpu::Generic => Features::of(&[
BulkMemory,
BulkMemoryOpt,
CallIndirectOverlong,
Multivalue,
MutableGlobals,
NontrappingFptoint,
ReferenceTypes,
SignExt,
]),
Cpu::Lime1 => Features::of(&[
BulkMemoryOpt,
CallIndirectOverlong,
ExtendedConst,
Multivalue,
MutableGlobals,
NontrappingFptoint,
SignExt,
]),
Cpu::BleedingEdge => Features::of(
&Feature::ALL.into_iter().filter(|&f| f != CompactImports).collect::<Vec<_>>(),
),
}
}
}
impl fmt::Display for Cpu {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn macros(set: Features) -> Vec<String> {
let mut names: Vec<String> = set.macros().collect();
names.sort();
names
}
#[test]
fn each_set_defines_the_macros_that_clang_23_defines() {
assert!(macros(Cpu::Mvp.features()).is_empty());
assert_eq!(
macros(Cpu::Lime1.features()),
[
"__wasm_bulk_memory_opt__",
"__wasm_extended_const__",
"__wasm_multivalue__",
"__wasm_mutable_globals__",
"__wasm_nontrapping_fptoint__",
"__wasm_sign_ext__",
]
);
assert_eq!(
macros(Cpu::Generic.features()),
[
"__wasm_bulk_memory__",
"__wasm_bulk_memory_opt__",
"__wasm_multivalue__",
"__wasm_mutable_globals__",
"__wasm_nontrapping_fptoint__",
"__wasm_reference_types__",
"__wasm_sign_ext__",
]
);
assert_eq!(macros(Cpu::BleedingEdge.features()).len(), 18);
assert_eq!(Cpu::default(), Cpu::Lime1);
}
#[test]
fn a_feature_turns_on_what_it_needs() {
let alone = |f| macros(Features::NONE.with(f));
assert_eq!(
alone(Feature::BulkMemory),
["__wasm_bulk_memory__", "__wasm_bulk_memory_opt__"]
);
assert_eq!(alone(Feature::RelaxedSimd), ["__wasm_relaxed_simd__", "__wasm_simd128__"]);
assert_eq!(alone(Feature::Fp16), ["__wasm_fp16__", "__wasm_simd128__"]);
assert_eq!(alone(Feature::Gc), ["__wasm_gc__", "__wasm_reference_types__"]);
assert!(alone(Feature::CallIndirectOverlong).is_empty());
assert!(alone(Feature::CompactImports).is_empty());
assert!(Features::NONE.with(Feature::Gc).has(Feature::CallIndirectOverlong));
}
#[test]
fn wasip3_turns_on_what_clang_23_turns_on_there() {
assert_eq!(
macros(required(Preview::P3)),
[
"__wasm_bulk_memory__",
"__wasm_bulk_memory_opt__",
"__wasm_mutable_globals__",
"__wasm_sign_ext__",
]
);
assert_eq!(required(Preview::P1), Features::NONE);
assert_eq!(required(Preview::P2), Features::NONE);
}
#[test]
fn the_flags_change_the_set_as_they_change_it_in_clang_23() {
use Feature::*;
let names = |cpu, flags: &[(Feature, bool)]| -> Vec<String> {
macros(resolve(cpu, flags)).iter().map(|m| m[7..m.len() - 2].to_owned()).collect()
};
assert_eq!(resolve(Cpu::Lime1, &[]), Cpu::Lime1.features());
assert_eq!(names(Cpu::Mvp, &[(Fp16, true)]), ["fp16", "simd128"]);
assert_eq!(names(Cpu::Mvp, &[(Fp16, true), (Simd128, false)]), ["fp16"]);
assert_eq!(names(Cpu::Mvp, &[(Simd128, false), (Fp16, true)]), ["fp16"]);
assert!(names(Cpu::Mvp, &[(RelaxedSimd, true), (Simd128, false)]).is_empty());
assert_eq!(
names(Cpu::Mvp, &[(Simd128, false), (RelaxedSimd, true)]),
["relaxed_simd", "simd128"]
);
assert_eq!(names(Cpu::Mvp, &[(Simd128, true), (RelaxedSimd, false)]), ["simd128"]);
assert_eq!(
names(Cpu::Mvp, &[(Gc, true), (ReferenceTypes, false)]),
["gc", "reference_types"]
);
assert_eq!(
names(Cpu::Mvp, &[(BulkMemory, true), (BulkMemoryOpt, false)]),
["bulk_memory", "bulk_memory_opt"]
);
assert!(!resolve(Cpu::Lime1, &[(BulkMemoryOpt, false)]).has(BulkMemoryOpt));
assert!(!resolve(Cpu::Lime1, &[(SignExt, false)]).has(SignExt));
assert!(resolve(Cpu::Lime1, &[(Simd128, true)]).has(Simd128));
let edge = |flags: &[(Feature, bool)]| resolve(Cpu::BleedingEdge, flags);
let without_simd = edge(&[(Simd128, false)]);
assert!(!without_simd.has(Simd128) && !without_simd.has(RelaxedSimd));
assert!(without_simd.has(Fp16));
assert!(!edge(&[(RelaxedSimd, false)]).has(RelaxedSimd));
assert!(edge(&[(RelaxedSimd, false)]).has(Simd128));
assert!(edge(&[(ReferenceTypes, false)]).has(ReferenceTypes));
assert!(edge(&[(BulkMemoryOpt, false)]).has(BulkMemoryOpt));
assert!(!edge(&[(BulkMemory, false)]).has(BulkMemory));
assert!(!edge(&[(Fp16, false), (Simd128, false)]).has(Fp16));
}
#[test]
fn every_name_reads_back() {
for f in Feature::ALL {
assert_eq!(Feature::named(f.name()), Some(f));
}
for c in Cpu::ALL {
assert_eq!(Cpu::named(c.name()), Some(c));
}
assert_eq!(Feature::named("simd"), None);
assert_eq!(Cpu::named("lime2"), None);
}
}