* implement wasmtime::component::flags! per #4308 This is the last macro needed to complete #4308. It supports generating a Rust type that represents a `flags` component type, analogous to how the [bitflags crate](https://crates.io/crates/bitflags) operates. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * wrap `format_flags` output in parens This ensures we generate non-empty output even when no flags are set. Empty output for a `Debug` implementation would be confusing. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * unconditionally derive `Lift` and `Lower` in wasmtime::component::flags! Per feedback on #4414, we now derive impls for those traits unconditionally, which simplifies the syntax of the macro. Also, I happened to notice an alignment bug in `LowerExpander::expand_variant`, so I fixed that and cleaned up some related code. Finally, I used @jameysharp's trick to calculate bit masks without looping. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * fix shift overflow regression in previous commit Jamey pointed out my mistake: I didn't consider the case when the flag count was evenly divisible by the representation size. This fixes the problem and adds test cases to cover it. Signed-off-by: Joel Dice <joel.dice@fermyon.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use super::TypedFuncExt;
|
||||
use anyhow::Result;
|
||||
use component_macro_test::add_variants;
|
||||
use component_macro_test::{add_variants, flags_test};
|
||||
use std::fmt::Write;
|
||||
use wasmtime::component::{Component, ComponentType, Lift, Linker, Lower};
|
||||
use wasmtime::Store;
|
||||
@@ -476,6 +476,8 @@ fn enum_derive() -> Result<()> {
|
||||
.get_typed_func::<(Foo,), Foo, _>(&mut store, "echo")
|
||||
.is_err());
|
||||
|
||||
// Happy path redux, with large enums (i.e. more than 2^8 cases)
|
||||
|
||||
#[add_variants(257)]
|
||||
#[derive(ComponentType, Lift, Lower, PartialEq, Eq, Debug, Copy, Clone)]
|
||||
#[component(enum)]
|
||||
@@ -514,3 +516,396 @@ fn enum_derive() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flags() -> Result<()> {
|
||||
wasmtime::component::flags! {
|
||||
Foo {
|
||||
#[component(name = "foo-bar-baz")]
|
||||
const A;
|
||||
const B;
|
||||
const C;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(Foo::default(), (Foo::A | Foo::B) & Foo::C);
|
||||
assert_eq!(Foo::B, (Foo::A | Foo::B) & Foo::B);
|
||||
assert_eq!(Foo::A, (Foo::A | Foo::B) & Foo::A);
|
||||
assert_eq!(Foo::A | Foo::B, Foo::A ^ Foo::B);
|
||||
assert_eq!(Foo::default(), Foo::A ^ Foo::A);
|
||||
assert_eq!(Foo::B | Foo::C, !Foo::A);
|
||||
|
||||
let engine = super::engine();
|
||||
let mut store = Store::new(&engine, ());
|
||||
|
||||
// Happy path: component type matches flag count and names
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (flags "foo-bar-baz" "B" "C"))"#, 4),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_typed_func::<(Foo,), Foo, _>(&mut store, "echo")?;
|
||||
|
||||
for n in 0..8 {
|
||||
let mut input = Foo::default();
|
||||
if (n & 1) != 0 {
|
||||
input |= Foo::A;
|
||||
}
|
||||
if (n & 2) != 0 {
|
||||
input |= Foo::B;
|
||||
}
|
||||
if (n & 4) != 0 {
|
||||
input |= Foo::C;
|
||||
}
|
||||
|
||||
let output = func.call_and_post_return(&mut store, (input,))?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
}
|
||||
|
||||
// Sad path: flag count mismatch (too few)
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (flags "foo-bar-baz" "B"))"#, 4),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
assert!(instance
|
||||
.get_typed_func::<(Foo,), Foo, _>(&mut store, "echo")
|
||||
.is_err());
|
||||
|
||||
// Sad path: flag count mismatch (too many)
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (flags "foo-bar-baz" "B" "C" "D"))"#, 4),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
assert!(instance
|
||||
.get_typed_func::<(Foo,), Foo, _>(&mut store, "echo")
|
||||
.is_err());
|
||||
|
||||
// Sad path: flag name mismatch
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (flags "A" "B" "C"))"#, 4),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
assert!(instance
|
||||
.get_typed_func::<(Foo,), Foo, _>(&mut store, "echo")
|
||||
.is_err());
|
||||
|
||||
// Happy path redux, with large flag count (exactly 8)
|
||||
|
||||
flags_test!(Foo8Exact, 8);
|
||||
|
||||
assert_eq!(
|
||||
Foo8Exact::default(),
|
||||
(Foo8Exact::F0 | Foo8Exact::F6) & Foo8Exact::F7
|
||||
);
|
||||
assert_eq!(
|
||||
Foo8Exact::F6,
|
||||
(Foo8Exact::F0 | Foo8Exact::F6) & Foo8Exact::F6
|
||||
);
|
||||
assert_eq!(
|
||||
Foo8Exact::F0,
|
||||
(Foo8Exact::F0 | Foo8Exact::F6) & Foo8Exact::F0
|
||||
);
|
||||
assert_eq!(Foo8Exact::F0 | Foo8Exact::F6, Foo8Exact::F0 ^ Foo8Exact::F6);
|
||||
assert_eq!(Foo8Exact::default(), Foo8Exact::F0 ^ Foo8Exact::F0);
|
||||
assert_eq!(
|
||||
Foo8Exact::F1
|
||||
| Foo8Exact::F2
|
||||
| Foo8Exact::F3
|
||||
| Foo8Exact::F4
|
||||
| Foo8Exact::F5
|
||||
| Foo8Exact::F6
|
||||
| Foo8Exact::F7,
|
||||
!Foo8Exact::F0
|
||||
);
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (flags {}))"#,
|
||||
(0..8)
|
||||
.map(|index| format!(r#""F{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
),
|
||||
4,
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_typed_func::<(Foo8Exact,), Foo8Exact, _>(&mut store, "echo")?;
|
||||
|
||||
for &input in &[
|
||||
Foo8Exact::F0,
|
||||
Foo8Exact::F1,
|
||||
Foo8Exact::F5,
|
||||
Foo8Exact::F6,
|
||||
Foo8Exact::F7,
|
||||
] {
|
||||
let output = func.call_and_post_return(&mut store, (input,))?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
}
|
||||
|
||||
// Happy path redux, with large flag count (more than 8)
|
||||
|
||||
flags_test!(Foo16, 9);
|
||||
|
||||
assert_eq!(Foo16::default(), (Foo16::F0 | Foo16::F7) & Foo16::F8);
|
||||
assert_eq!(Foo16::F7, (Foo16::F0 | Foo16::F7) & Foo16::F7);
|
||||
assert_eq!(Foo16::F0, (Foo16::F0 | Foo16::F7) & Foo16::F0);
|
||||
assert_eq!(Foo16::F0 | Foo16::F7, Foo16::F0 ^ Foo16::F7);
|
||||
assert_eq!(Foo16::default(), Foo16::F0 ^ Foo16::F0);
|
||||
assert_eq!(
|
||||
Foo16::F1
|
||||
| Foo16::F2
|
||||
| Foo16::F3
|
||||
| Foo16::F4
|
||||
| Foo16::F5
|
||||
| Foo16::F6
|
||||
| Foo16::F7
|
||||
| Foo16::F8,
|
||||
!Foo16::F0
|
||||
);
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (flags {}))"#,
|
||||
(0..9)
|
||||
.map(|index| format!(r#""F{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
),
|
||||
4,
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_typed_func::<(Foo16,), Foo16, _>(&mut store, "echo")?;
|
||||
|
||||
for &input in &[Foo16::F0, Foo16::F1, Foo16::F6, Foo16::F7, Foo16::F8] {
|
||||
let output = func.call_and_post_return(&mut store, (input,))?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
}
|
||||
|
||||
// Happy path redux, with large flag count (exactly 16)
|
||||
|
||||
flags_test!(Foo16Exact, 16);
|
||||
|
||||
assert_eq!(
|
||||
Foo16Exact::default(),
|
||||
(Foo16Exact::F0 | Foo16Exact::F14) & Foo16Exact::F5
|
||||
);
|
||||
assert_eq!(
|
||||
Foo16Exact::F14,
|
||||
(Foo16Exact::F0 | Foo16Exact::F14) & Foo16Exact::F14
|
||||
);
|
||||
assert_eq!(
|
||||
Foo16Exact::F0,
|
||||
(Foo16Exact::F0 | Foo16Exact::F14) & Foo16Exact::F0
|
||||
);
|
||||
assert_eq!(
|
||||
Foo16Exact::F0 | Foo16Exact::F14,
|
||||
Foo16Exact::F0 ^ Foo16Exact::F14
|
||||
);
|
||||
assert_eq!(Foo16Exact::default(), Foo16Exact::F0 ^ Foo16Exact::F0);
|
||||
assert_eq!(
|
||||
Foo16Exact::F0 | Foo16Exact::F15,
|
||||
!((!Foo16Exact::F0) & (!Foo16Exact::F15))
|
||||
);
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (flags {}))"#,
|
||||
(0..16)
|
||||
.map(|index| format!(r#""F{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
),
|
||||
4,
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_typed_func::<(Foo16Exact,), Foo16Exact, _>(&mut store, "echo")?;
|
||||
|
||||
for &input in &[
|
||||
Foo16Exact::F0,
|
||||
Foo16Exact::F1,
|
||||
Foo16Exact::F13,
|
||||
Foo16Exact::F14,
|
||||
Foo16Exact::F15,
|
||||
] {
|
||||
let output = func.call_and_post_return(&mut store, (input,))?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
}
|
||||
|
||||
// Happy path redux, with large flag count (more than 16)
|
||||
|
||||
flags_test!(Foo32, 17);
|
||||
|
||||
assert_eq!(Foo32::default(), (Foo32::F0 | Foo32::F15) & Foo32::F16);
|
||||
assert_eq!(Foo32::F15, (Foo32::F0 | Foo32::F15) & Foo32::F15);
|
||||
assert_eq!(Foo32::F0, (Foo32::F0 | Foo32::F15) & Foo32::F0);
|
||||
assert_eq!(Foo32::F0 | Foo32::F15, Foo32::F0 ^ Foo32::F15);
|
||||
assert_eq!(Foo32::default(), Foo32::F0 ^ Foo32::F0);
|
||||
assert_eq!(Foo32::F0 | Foo32::F16, !((!Foo32::F0) & (!Foo32::F16)));
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (flags {}))"#,
|
||||
(0..17)
|
||||
.map(|index| format!(r#""F{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
),
|
||||
4,
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_typed_func::<(Foo32,), Foo32, _>(&mut store, "echo")?;
|
||||
|
||||
for &input in &[Foo32::F0, Foo32::F1, Foo32::F14, Foo32::F15, Foo32::F16] {
|
||||
let output = func.call_and_post_return(&mut store, (input,))?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
}
|
||||
|
||||
// Happy path redux, with large flag count (exactly 32)
|
||||
|
||||
flags_test!(Foo32Exact, 32);
|
||||
|
||||
assert_eq!(
|
||||
Foo32Exact::default(),
|
||||
(Foo32Exact::F0 | Foo32Exact::F30) & Foo32Exact::F31
|
||||
);
|
||||
assert_eq!(
|
||||
Foo32Exact::F30,
|
||||
(Foo32Exact::F0 | Foo32Exact::F30) & Foo32Exact::F30
|
||||
);
|
||||
assert_eq!(
|
||||
Foo32Exact::F0,
|
||||
(Foo32Exact::F0 | Foo32Exact::F30) & Foo32Exact::F0
|
||||
);
|
||||
assert_eq!(
|
||||
Foo32Exact::F0 | Foo32Exact::F30,
|
||||
Foo32Exact::F0 ^ Foo32Exact::F30
|
||||
);
|
||||
assert_eq!(Foo32Exact::default(), Foo32Exact::F0 ^ Foo32Exact::F0);
|
||||
assert_eq!(
|
||||
Foo32Exact::F0 | Foo32Exact::F15,
|
||||
!((!Foo32Exact::F0) & (!Foo32Exact::F15))
|
||||
);
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (flags {}))"#,
|
||||
(0..32)
|
||||
.map(|index| format!(r#""F{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
),
|
||||
4,
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_typed_func::<(Foo32Exact,), Foo32Exact, _>(&mut store, "echo")?;
|
||||
|
||||
for &input in &[
|
||||
Foo32Exact::F0,
|
||||
Foo32Exact::F1,
|
||||
Foo32Exact::F29,
|
||||
Foo32Exact::F30,
|
||||
Foo32Exact::F31,
|
||||
] {
|
||||
let output = func.call_and_post_return(&mut store, (input,))?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
}
|
||||
|
||||
// Happy path redux, with large flag count (more than 32)
|
||||
|
||||
flags_test!(Foo64, 33);
|
||||
|
||||
assert_eq!(Foo64::default(), (Foo64::F0 | Foo64::F31) & Foo64::F32);
|
||||
assert_eq!(Foo64::F31, (Foo64::F0 | Foo64::F31) & Foo64::F31);
|
||||
assert_eq!(Foo64::F0, (Foo64::F0 | Foo64::F31) & Foo64::F0);
|
||||
assert_eq!(Foo64::F0 | Foo64::F31, Foo64::F0 ^ Foo64::F31);
|
||||
assert_eq!(Foo64::default(), Foo64::F0 ^ Foo64::F0);
|
||||
assert_eq!(Foo64::F0 | Foo64::F32, !((!Foo64::F0) & (!Foo64::F32)));
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (flags {}))"#,
|
||||
(0..33)
|
||||
.map(|index| format!(r#""F{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
),
|
||||
8,
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_typed_func::<(Foo64,), Foo64, _>(&mut store, "echo")?;
|
||||
|
||||
for &input in &[Foo64::F0, Foo64::F1, Foo64::F30, Foo64::F31, Foo64::F32] {
|
||||
let output = func.call_and_post_return(&mut store, (input,))?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
}
|
||||
|
||||
// Happy path redux, with large flag count (more than 64)
|
||||
|
||||
flags_test!(Foo96, 65);
|
||||
|
||||
assert_eq!(Foo96::default(), (Foo96::F0 | Foo96::F63) & Foo96::F64);
|
||||
assert_eq!(Foo96::F63, (Foo96::F0 | Foo96::F63) & Foo96::F63);
|
||||
assert_eq!(Foo96::F0, (Foo96::F0 | Foo96::F63) & Foo96::F0);
|
||||
assert_eq!(Foo96::F0 | Foo96::F63, Foo96::F0 ^ Foo96::F63);
|
||||
assert_eq!(Foo96::default(), Foo96::F0 ^ Foo96::F0);
|
||||
assert_eq!(Foo96::F0 | Foo96::F64, !((!Foo96::F0) & (!Foo96::F64)));
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (flags {}))"#,
|
||||
(0..65)
|
||||
.map(|index| format!(r#""F{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
),
|
||||
12,
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_typed_func::<(Foo96,), Foo96, _>(&mut store, "echo")?;
|
||||
|
||||
for &input in &[Foo96::F0, Foo96::F1, Foo96::F62, Foo96::F63, Foo96::F64] {
|
||||
let output = func.call_and_post_return(&mut store, (input,))?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user