-
Notifications
You must be signed in to change notification settings - Fork 190
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
Macro construction without syn
#556
Comments
I think this comes down to preference. On one hand, a proc-macro allows us to have extremely fine grain diagnostics, which I think are important. On the other, 95% of the time these string literals will be correct and so diagnostics aren't important. That being said, keep in mind most projects using I'm in support of const fns once const panic is stabilized but until then, I think the tradeoff for a marginally increased compile time is worth it for the substantially better error diagnostics. |
I think we don't have to wait for const panic because the parser should never panic. uuid/fuzz/fuzz_targets/fuzz_target_parse.rs Lines 7 to 12 in 4b30c32
|
What kinds of things do you imagine would break @Nugine? |
The fuzzer never panics because the parser returns a |
Error definition, parser tests, ui tests and merge |
I can see a The main loss holding us back at the moment would be nice diagnostics in the error and their reporting to end-users, but I don't think making |
You are right. I missed the point. But there is a way to emit a compile error without https://docs.rs/static_assertions/1.1.0/src/static_assertions/const_assert.rs.html#52-57
|
What do the diagnostics look like? I'm thinking we use this implementation in a const parser to return a |
The const parser provides ambiguous information. Good diagnostics require a second parsing to generate more detailed error reports. #[derive(Debug, PartialEq, Eq)]
pub struct Error(ErrorKind);
#[derive(Debug, PartialEq, Eq)]
enum ErrorKind {
InvalidLength,
InvalidCharacter,
}
const fn invalid_length() -> Error {
Error(ErrorKind::InvalidLength)
}
const fn invalid_character() -> Error {
Error(ErrorKind::InvalidCharacter)
} |
Running a second pass on error, even just in the macro and not in Is it possible to write const parsers without needing any additional dependencies? |
Yes. The only necessary dependency is rustc. We need to test MSRV in CI (currently 1.46). |
If the MSRV needs to bump too that is something we can do. As a guide I tend to look at whatever the current version of |
The nice thing about this is that the feature flag can be kept, but doesn't need to change the API - Users can use (I'd also say that removing |
I really like the idea of having a second parser that can go back over and provide fine-grain diagnostics. I think it's very reasonable for developers to have use cases where they know it won't fail at runtime and speed is more important, as well as cases where if something goes wrong, the developer doesn't particularly care what because they have their own error handling. I'm currently working on making some very minor modifications to the Of course, the core parsing function will be entirely |
I try to find the fastest way to parse a uuid text. So I write another implementation using AVX2. #[inline]
pub fn parse_uuid(s: &str) -> Uuid {
match try_parse_uuid(s) {
Ok(b) => Uuid::from_bytes(b),
Err(_) => panic!(),
}
}
#[inline]
pub fn try_parse_uuid(s: &str) -> Result<[u8; 16], Error> {
let mut s = s.as_bytes();
let n = s.len();
if n == 32 {
return unsafe { parse_uuid_simple(s) };
}
match n {
// hyphenated UUID
36 => {}
// URN prefixed UUID
45 => match s.strip_prefix(b"urn:uuid:") {
Some(val) => s = val,
None => return Err(invalid_length()),
},
// Microsoft GUID
38 => match s {
[b'{', xs @ .., b'}'] => s = xs,
_ => return Err(invalid_length()),
},
_ => return Err(invalid_length()),
}
unsafe { parse_uuid_hyphenated(s) }
}
unsafe fn parse_uuid_simple(s: &[u8]) -> Result<[u8; 16], Error> {
parse(_mm256_loadu_si256(s.as_ptr().cast()))
}
unsafe fn parse_uuid_hyphenated(s: &[u8]) -> Result<[u8; 16], Error> {
// b'-' is 0x2d
let x: u32 = mem::transmute([
*s.get_unchecked(8),
*s.get_unchecked(13),
*s.get_unchecked(18),
*s.get_unchecked(23),
]);
if x != 0x2d2d2d2d {
return Err(invalid_character());
}
#[repr(C, align(32))]
struct Buf {
p1: u64,
p2: u32,
p3: u32,
p4: u32,
p5: u32,
p6: u64,
}
let base: *const u8 = s.as_ptr();
let buf = Buf {
p1: base.cast::<u64>().read_unaligned(),
p2: base.add(9).cast::<u32>().read_unaligned(),
p3: base.add(14).cast::<u32>().read_unaligned(),
p4: base.add(19).cast::<u32>().read_unaligned(),
p5: base.add(24).cast::<u32>().read_unaligned(),
p6: base.add(28).cast::<u64>().read_unaligned(),
};
parse(_mm256_load_si256(&buf as *const Buf as *const _))
}
unsafe fn parse(a: __m256i) -> Result<[u8; 16], Error> {
if !hex_check(a) {
return Err(invalid_character());
}
let ans = hex_decode(a);
Ok(mem::transmute(ans))
}
/// Rewritten from <https://github.com/nervosnetwork/faster-hex/blob/0114ee6bcf8a02718bc19e769f9de076ce119726/src/decode.rs#L108-L143>
unsafe fn hex_check(a: __m256i) -> bool {
let m_0 = _mm256_set1_epi8((b'0' - 1) as i8);
let ge_0 = _mm256_cmpgt_epi8(a, m_0); // a > b'0' - 1
let m_9 = _mm256_set1_epi8((b'9' + 1) as i8);
let le_9 = _mm256_cmpgt_epi8(m_9, a); // b'9' + 1 > a
let in_0_9 = _mm256_and_si256(ge_0, le_9);
let m_ua = _mm256_set1_epi8((b'A' - 1) as i8);
let ge_ua = _mm256_cmpgt_epi8(a, m_ua); // a > b'A - 1
let m_uf = _mm256_set1_epi8((b'F' + 1) as i8);
let le_uf = _mm256_cmpgt_epi8(m_uf, a); // b'F' + 1 > a
let in_ua_uf = _mm256_and_si256(ge_ua, le_uf);
let m_la = _mm256_set1_epi8((b'a' - 1) as i8);
let ge_la = _mm256_cmpgt_epi8(a, m_la); // a > b'a' - 1
let m_lf = _mm256_set1_epi8((b'f' + 1) as i8);
let le_lf = _mm256_cmpgt_epi8(m_lf, a); // b'f' + 1 > a
let in_la_lf = _mm256_and_si256(ge_la, le_lf);
let ans = _mm256_or_si256(_mm256_or_si256(in_la_lf, in_ua_uf), in_0_9);
_mm256_movemask_epi8(ans) as u32 == 0xffffffff
}
unsafe fn hex_decode(a: __m256i) -> __m128i {
// epi16
// a1 = c & 0x0f0f;
// a2 = a1 >> 4;
// a3 = a1 + a2;
// a4 = c & 0x4040;
// a5 = a4 >> 6;
// a6 = a4 >> 10;
// a7 = a5 + a6;
// a8 = a7 * 9;
// a9 = a3 + a8;
// a10 = a9 & 0x00ff
#[repr(align(32))]
struct Align32I8x32([i8; 32]);
const SHUFFLE_MASK: Align32I8x32 = Align32I8x32([
1, 0, 3, 2, 5, 4, 7, 6, //
9, 8, 11, 10, 13, 12, 15, 14, //
17, 16, 19, 18, 21, 20, 23, 22, //
25, 24, 27, 26, 29, 28, 31, 30, //
]);
let m = _mm256_load_si256(SHUFFLE_MASK.0.as_ptr().cast());
let a = _mm256_shuffle_epi8(a, m); // big endian -> little endian
let a1 = _mm256_and_si256(a, _mm256_set1_epi16(0x0f0f));
let a2 = _mm256_srai_epi16::<4>(a1);
let a3 = _mm256_add_epi16(a1, a2);
let a4 = _mm256_and_si256(a, _mm256_set1_epi16(0x4040));
let a5 = _mm256_srai_epi16::<6>(a4);
let a6 = _mm256_srai_epi16::<10>(a4);
let a7 = _mm256_add_epi16(a5, a6);
let a8 = _mm256_mullo_epi16(a7, _mm256_set1_epi16(0x9));
let a9 = _mm256_add_epi16(a3, a8);
let a10 = _mm256_and_si256(a9, _mm256_set1_epi16(0x00ff));
avx2_mm256_cvtepi16_epi8(a10)
}
#[inline(always)]
unsafe fn avx2_mm256_cvtepi16_epi8(a: __m256i) -> __m128i {
let a1 = _mm256_castsi256_si128(a);
let a2 = _mm256_extractf128_si256::<1>(a);
_mm_packus_epi16(a1, a2)
} |
Wow that’s really awesome @Nugine! 😁 I wonder if it’s possible to write with |
I believe it's possible to write with |
That's awesome! Is this architecture specific though? Also, is there any way we can make this I also did some more thinking, and now I propose that we have some ultra-fast parsing function that returns something like |
AVX2 is widely used but not all CPUs support it. Unfortunately, SIMD operations are not available at const context now. |
We did have some designs for fallbacks in |
https://github.com/Nugine/uuid-simd
Drawbacks
|
Motivation
Macros are notoriously slow at compile time to include, it'd be nice to remove the tradeoff for users
Solution
The
uuid
macro can be implemented with const fn (Playground). This makes the decision easier for users - just move the call to a const context.If we wish, we can also have the
uuid
macro to force the parsing to be const.The text was updated successfully, but these errors were encountered: