From bafa30b4b2594627efa50e03b5ea499792a1a027 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 10 May 2024 00:31:41 +0200 Subject: [PATCH] Add `Encode::None` to be able to encode vector / SIMD types --- crates/objc2-encode/src/encoding.rs | 51 +++++++++ crates/objc2-encode/src/encoding_box.rs | 6 +- crates/objc2-encode/src/helper.rs | 5 + crates/objc2-encode/src/parse.rs | 142 ++++++++++++++++++------ crates/objc2-encode/src/static_str.rs | 2 + crates/tests/extern/encode_utils.m | 19 +++- crates/tests/src/test_encode_utils.rs | 13 +++ 7 files changed, 202 insertions(+), 36 deletions(-) diff --git a/crates/objc2-encode/src/encoding.rs b/crates/objc2-encode/src/encoding.rs index d7115e1f8..26a122e76 100644 --- a/crates/objc2-encode/src/encoding.rs +++ b/crates/objc2-encode/src/encoding.rs @@ -150,6 +150,12 @@ pub enum Encoding { /// Note that the `=` may be omitted in some situations; this is /// considered equal to the case where there are no members. Union(&'static str, &'static [Encoding]), + /// The type does not have an Objective-C encoding. + /// + /// This is usually only used on types where Clang fails to generate the + /// Objective-C encoding, like SIMD types marked with + /// `__attribute__((__ext_vector_type__(1)))`. + None, // TODO: "Vector" types have the '!' encoding, but are not implemented in // clang @@ -260,6 +266,7 @@ impl fmt::Display for Encoding { mod tests { use super::*; use crate::static_str::{static_encoding_str_array, static_encoding_str_len}; + use alloc::boxed::Box; use alloc::string::ToString; use alloc::vec; use core::str::FromStr; @@ -602,6 +609,32 @@ mod tests { !"^{_CGLContextObject}"; !"^{SomeOtherStruct=}"; } + + fn none() { + Encoding::None; + ""; + !"?"; + } + + fn none_in_array() { + Encoding::Array(42, &Encoding::None); + !Encoding::Array(42, &Encoding::Unknown); + "[42]"; + !"[42i]"; + } + + fn none_in_pointer() { + Encoding::Pointer(&Encoding::None); + !Encoding::Pointer(&Encoding::Unknown); + "^"; + !""; + !"^i"; + } + + fn none_in_pointer_in_array() { + Encoding::Array(42, &Encoding::Pointer(&Encoding::None)); + "[42^]"; + } } #[test] @@ -640,4 +673,22 @@ mod tests { assert!(!enc.equivalent_to_box(&expected)); } + + // Similar to `?`, `` cannot be accurately represented inside pointers + // inside structs, and may be parsed incorrectly. + #[test] + fn none_in_struct() { + let enc = Encoding::Struct("?", &[Encoding::Pointer(&Encoding::None), Encoding::Int]); + let s = "{?=^i}"; + assert_eq!(&enc.to_string(), s); + + let parsed = EncodingBox::from_str(s).unwrap(); + let expected = EncodingBox::Struct( + "?".to_string(), + vec![EncodingBox::Pointer(Box::new(EncodingBox::Int))], + ); + assert_eq!(parsed, expected); + + assert!(!enc.equivalent_to_box(&expected)); + } } diff --git a/crates/objc2-encode/src/encoding_box.rs b/crates/objc2-encode/src/encoding_box.rs index 7a81b2ae2..c704596a8 100644 --- a/crates/objc2-encode/src/encoding_box.rs +++ b/crates/objc2-encode/src/encoding_box.rs @@ -88,6 +88,8 @@ pub enum EncodingBox { Struct(String, Vec), /// Same as [`Encoding::Union`]. Union(String, Vec), + /// Same as [`Encoding::None`]. + None, } impl EncodingBox { @@ -120,7 +122,7 @@ impl EncodingBox { let mut parser = Parser::new(s); parser.strip_leading_qualifiers(); - match parser.parse_encoding() { + match parser.parse_encoding_or_none() { Err(err) => Err(ParseError::new(parser, err)), Ok(encoding) => { let remaining = parser.remaining(); @@ -159,7 +161,7 @@ impl FromStr for EncodingBox { parser.strip_leading_qualifiers(); parser - .parse_encoding() + .parse_encoding_or_none() .and_then(|enc| parser.expect_empty().map(|()| enc)) .map_err(|err| ParseError::new(parser, err)) } diff --git a/crates/objc2-encode/src/helper.rs b/crates/objc2-encode/src/helper.rs index c66eaf4f5..a6694954e 100644 --- a/crates/objc2-encode/src/helper.rs +++ b/crates/objc2-encode/src/helper.rs @@ -107,6 +107,7 @@ pub(crate) fn compare_encodings( } } } + (NoneInvalid, NoneInvalid) => true, (_, _) => false, } } @@ -242,6 +243,7 @@ pub(crate) enum Helper<'a, E = Encoding> { Indirection(IndirectionKind, &'a E), Array(u64, &'a E), Container(ContainerKind, &'a str, &'a [E]), + NoneInvalid, } impl Helper<'_, E> { @@ -279,6 +281,7 @@ impl Helper<'_, E> { } write!(f, "{}", kind.end())?; } + Self::NoneInvalid => {} } Ok(()) } @@ -328,6 +331,7 @@ impl Helper<'_> { } Self::Container(ContainerKind::Union, name, members) } + None => Self::NoneInvalid, } } } @@ -376,6 +380,7 @@ impl<'a> Helper<'a, EncodingBox> { } Self::Container(ContainerKind::Union, name, members) } + None => Self::NoneInvalid, } } } diff --git a/crates/objc2-encode/src/parse.rs b/crates/objc2-encode/src/parse.rs index 30b29bc0d..3aedc7267 100644 --- a/crates/objc2-encode/src/parse.rs +++ b/crates/objc2-encode/src/parse.rs @@ -103,6 +103,13 @@ impl fmt::Display for ErrorKind { type Result = core::result::Result; +enum ParseInner { + Empty, + Encoding(EncodingBox), + ContainerEnd(ContainerKind), + ArrayEnd, +} + #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub(crate) struct Parser<'a> { data: &'a str, @@ -142,6 +149,10 @@ impl<'a> Parser<'a> { self.split_point += 1; } + fn rollback(&mut self) { + self.split_point -= 1; + } + fn consume_while(&mut self, mut condition: impl FnMut(u8) -> bool) { while let Some(b) = self.try_peek() { if condition(b) { @@ -288,11 +299,25 @@ impl Parser<'_> { self.expect_byte(b'=')?; // Parse as equal if the container is empty if items.is_empty() { - while self.try_peek() != Some(kind.end_byte()) { - let _ = self.parse_encoding().ok()?; + loop { + match self.parse_inner().ok()? { + ParseInner::Empty => { + // Require the container to have an end + return None; + } + ParseInner::Encoding(_) => {} + ParseInner::ContainerEnd(parsed_kind) => { + if parsed_kind == kind { + return Some(()); + } else { + return None; + } + } + ParseInner::ArrayEnd => { + return None; + } + } } - self.advance(); - return Some(()); } // Parse as equal if the string's container is empty if self.try_peek() == Some(kind.end_byte()) { @@ -305,6 +330,7 @@ impl Parser<'_> { } self.expect_byte(kind.end_byte()) } + Helper::NoneInvalid => Some(()), } } } @@ -337,34 +363,44 @@ impl Parser<'_> { let mut items = Vec::new(); // Parse items until hits end loop { - let b = self.try_peek().ok_or(ErrorKind::WrongEndContainer(kind))?; - if b == kind.end_byte() { - self.advance(); - break; - } else { - // Wasn't the end, so try to extract one more encoding - items.push(self.parse_encoding()?); + match self.parse_inner()? { + ParseInner::Empty => { + return Err(ErrorKind::WrongEndContainer(kind)); + } + ParseInner::Encoding(enc) => { + items.push(enc); + } + ParseInner::ContainerEnd(parsed_kind) => { + if parsed_kind == kind { + return Ok((s, items)); + } else { + return Err(ErrorKind::Unknown(parsed_kind.end_byte())); + } + } + ParseInner::ArrayEnd => { + return Err(ErrorKind::Unknown(b']')); + } } } - Ok((s, items)) } - pub(crate) fn parse_encoding(&mut self) -> Result { - self.try_parse_encoding() - .and_then(|res| res.ok_or(ErrorKind::UnexpectedEnd)) + pub(crate) fn parse_encoding_or_none(&mut self) -> Result { + match self.parse_inner()? { + ParseInner::Empty => Ok(EncodingBox::None), + ParseInner::Encoding(enc) => Ok(enc), + ParseInner::ContainerEnd(kind) => Err(ErrorKind::Unknown(kind.end_byte())), + ParseInner::ArrayEnd => Err(ErrorKind::Unknown(b']')), + } } - fn try_parse_encoding(&mut self) -> Result> { - Ok(if let Some(b) = self.try_peek() { - self.advance(); - Some(self.parse_encoding_inner(b)?) - } else { - None - }) - } + fn parse_inner(&mut self) -> Result { + if self.is_empty() { + return Ok(ParseInner::Empty); + } + let b = self.peek()?; + self.advance(); - fn parse_encoding_inner(&mut self, b: u8) -> Result { - Ok(match b { + Ok(ParseInner::Encoding(match b { b'c' => EncodingBox::Char, b's' => EncodingBox::Short, b'i' => EncodingBox::Int, @@ -422,26 +458,59 @@ impl Parser<'_> { EncodingBox::BitField(size, None) } } - b'^' => EncodingBox::Pointer(Box::new(self.parse_encoding()?)), - b'A' => EncodingBox::Atomic(Box::new(self.parse_encoding()?)), + b'^' => EncodingBox::Pointer(Box::new(match self.parse_inner()? { + ParseInner::Empty => EncodingBox::None, + ParseInner::Encoding(enc) => enc, + ParseInner::ContainerEnd(_) | ParseInner::ArrayEnd => { + self.rollback(); + EncodingBox::None + } + })), + b'A' => EncodingBox::Atomic(Box::new(match self.parse_inner()? { + ParseInner::Empty => EncodingBox::None, + ParseInner::Encoding(enc) => enc, + ParseInner::ContainerEnd(_) | ParseInner::ArrayEnd => { + self.rollback(); + EncodingBox::None + } + })), b'[' => { let len = self.parse_u64()?; - let item = self.parse_encoding()?; - self.expect_byte(b']').ok_or(ErrorKind::WrongEndArray)?; - EncodingBox::Array(len, Box::new(item)) + match self.parse_inner()? { + ParseInner::Empty => { + return Err(ErrorKind::WrongEndArray); + } + ParseInner::Encoding(item) => { + self.expect_byte(b']').ok_or(ErrorKind::WrongEndArray)?; + EncodingBox::Array(len, Box::new(item)) + } + ParseInner::ArrayEnd => EncodingBox::Array(len, Box::new(EncodingBox::None)), + ParseInner::ContainerEnd(kind) => { + return Err(ErrorKind::Unknown(kind.end_byte())) + } + } + } + b']' => { + return Ok(ParseInner::ArrayEnd); } b'{' => { let kind = ContainerKind::Struct; let (name, items) = self.parse_container(kind)?; EncodingBox::Struct(name.to_string(), items) } + b'}' => { + return Ok(ParseInner::ContainerEnd(ContainerKind::Struct)); + } b'(' => { let kind = ContainerKind::Union; let (name, items) = self.parse_container(kind)?; EncodingBox::Union(name.to_string(), items) } + b')' => { + return Ok(ParseInner::ContainerEnd(ContainerKind::Union)); + } b => return Err(ErrorKind::Unknown(b)), - }) + })) } fn try_parse_bitfield_gnustep(&mut self) -> Result> { @@ -516,7 +585,7 @@ mod tests { let mut parser = Parser::new(enc); assert_eq!( parser - .parse_encoding() + .parse_encoding_or_none() .and_then(|enc| parser.expect_empty().map(|()| enc)), expected ); @@ -550,4 +619,13 @@ mod tests { ); assert_bitfield("b2000C257", Err(ErrorKind::IntegerTooLarge)); } + + #[test] + fn parse_closing() { + let mut parser = Parser::new("]"); + assert_eq!( + parser.parse_encoding_or_none(), + Err(ErrorKind::Unknown(b']')) + ); + } } diff --git a/crates/objc2-encode/src/static_str.rs b/crates/objc2-encode/src/static_str.rs index 4c5751d97..e368eab70 100644 --- a/crates/objc2-encode/src/static_str.rs +++ b/crates/objc2-encode/src/static_str.rs @@ -65,6 +65,7 @@ pub(crate) const fn static_encoding_str_len(encoding: &Encoding, level: NestingL } res + 1 } + NoneInvalid => 0, } } @@ -208,6 +209,7 @@ pub(crate) const fn static_encoding_str_array( res[res_i] = kind.end_byte(); } + NoneInvalid => {} }; res } diff --git a/crates/tests/extern/encode_utils.m b/crates/tests/extern/encode_utils.m index b3778ccfb..02bf63ef7 100644 --- a/crates/tests/extern/encode_utils.m +++ b/crates/tests/extern/encode_utils.m @@ -2,8 +2,10 @@ #include #include #include -// For NSInteger / NSUInteger. Linking is not required. -#include +#include // For NSInteger / NSUInteger. Linking is not required. +#ifdef TARGET_OS_MAC +#include +#endif #define ENCODING_INNER(name, type) char* ENCODING_ ## name = @encode(type); @@ -203,6 +205,19 @@ ENCODING_NO_ATOMIC(UUID_T, uuid_t); +// simd + +#ifdef TARGET_OS_MAC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wencode-type" +ENCODING(SIMD_INT2, simd_int2); +ENCODING(SIMD_FLOAT1, simd_float1); +ENCODING(SIMD_FLOAT2, simd_float2); +ENCODING(SIMD_FLOAT2X4, simd_float2x4); +ENCODING(SIMD_FLOAT4X2, simd_float4x2); +# pragma clang diagnostic pop +#endif + // Possible extras #if __has_builtin(__int128_t) diff --git a/crates/tests/src/test_encode_utils.rs b/crates/tests/src/test_encode_utils.rs index bfe635399..2ccda8c19 100644 --- a/crates/tests/src/test_encode_utils.rs +++ b/crates/tests/src/test_encode_utils.rs @@ -298,6 +298,19 @@ assert_types! { UUID_T no_atomic => enc Encoding::Array(16, &u8::ENCODING), + // simd + + #[cfg(feature = "apple")] + SIMD_INT2 => enc Encoding::None, + #[cfg(feature = "apple")] + SIMD_FLOAT1 => c_float, + #[cfg(feature = "apple")] + SIMD_FLOAT2 => enc Encoding::None, + #[cfg(feature = "apple")] + SIMD_FLOAT2X4 => enc Encoding::Struct("?", &[Encoding::Array(2, &Encoding::None)]), + #[cfg(feature = "apple")] + SIMD_FLOAT4X2 => enc Encoding::Struct("?", &[Encoding::Array(4, &Encoding::None)]), + // Possible extras; need to be #[cfg]-ed somehow // SIGNED_INT_128 => i128,