Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

✨ Include constant int segments in interference analysis

+364 -13
+1
Cargo.lock
··· 314 314 dependencies = [ 315 315 "funty", 316 316 "radium", 317 + "serde", 317 318 "tap", 318 319 "wyz", 319 320 ]
+1 -1
compiler-core/Cargo.toml
··· 49 49 # Ensuring recursive type-checking doesn't stack overflow 50 50 stacker = "0.1.21" 51 51 # Manipulating bit arrays 52 - bitvec = "1" 52 + bitvec = { version = "1", features = ["serde"] } 53 53 54 54 async-trait.workspace = true 55 55 base16.workspace = true
+241 -12
compiler-core/src/exhaustiveness.rs
··· 82 82 is_prelude_module, string, 83 83 }, 84 84 }; 85 - use bitvec::{order::Msb0, slice::BitSlice, view::BitView}; 85 + use bitvec::{order::Msb0, slice::BitSlice, vec::BitVec, view::BitView}; 86 86 use ecow::EcoString; 87 87 use id_arena::{Arena, Id}; 88 88 use itertools::Itertools; ··· 91 91 use radix_trie::{Trie, TrieCommon}; 92 92 use std::{ 93 93 cell::RefCell, 94 + cmp::Ordering, 94 95 collections::{HashMap, HashSet, VecDeque}, 95 96 hash::Hash, 96 97 sync::Arc, ··· 1213 1214 /// is deemed unreachable: it is the location this literal value comes 1214 1215 /// from in the whole pattern. 1215 1216 location: SrcSpan, 1217 + /// The bits representing the given literal int, with the correct 1218 + /// signed- and endianness as specified in the bit array segment. 1219 + bits: Result<BitVec<u8, Msb0>, IntToBitsError>, 1216 1220 }, 1217 1221 LiteralString { 1218 1222 value: EcoString, ··· 1230 1234 } 1231 1235 1232 1236 impl BitArrayMatchedValue { 1237 + /// This is an arbitrary limit beyond which interference _may_ no longer be done. 1238 + /// This is necessary because people may write very silly segments like 1239 + /// `<<0:9_007_199_254_740_992>>`, which would allocate around a wee petabyte of memory. 1240 + /// Literal strings are already in memory, and thus ignore this limit. 1241 + const MAX_BITS_INTERFERENCE: u32 = u16::MAX as u32; 1242 + 1233 1243 pub(crate) fn is_literal(&self) -> bool { 1234 1244 match self { 1235 1245 BitArrayMatchedValue::LiteralFloat(_) ··· 1248 1258 match self { 1249 1259 BitArrayMatchedValue::LiteralString { bytes, .. } => Some(bytes.view_bits::<Msb0>()), 1250 1260 BitArrayMatchedValue::Assign { value, .. } => value.constant_bits(), 1261 + BitArrayMatchedValue::LiteralInt { bits, .. } => bits.as_deref().ok(), 1251 1262 1252 1263 // TODO: We could also implement the interfering optimisation for 1253 - // literal ints as well, but that will be a bit trickier than 1254 - // strings. 1255 - BitArrayMatchedValue::LiteralInt { .. } 1256 - | BitArrayMatchedValue::LiteralFloat(_) 1264 + // literal floats as well, but the usefulness is questionable 1265 + BitArrayMatchedValue::LiteralFloat(_) 1257 1266 | BitArrayMatchedValue::Variable(_) 1258 1267 | BitArrayMatchedValue::Discard(_) => None, 1259 1268 } ··· 1268 1277 ) -> Option<ImpossibleBitArraySegmentPattern> { 1269 1278 match self { 1270 1279 BitArrayMatchedValue::Assign { value, .. } => value.is_impossible_segment(read_action), 1271 - BitArrayMatchedValue::LiteralInt { value, location } => { 1272 - let size = read_action.size.constant_bits()?.to_u32()?; 1273 - if representable_with_bits(value, size, read_action.signed) { 1274 - None 1275 - } else { 1280 + BitArrayMatchedValue::LiteralInt { 1281 + value, 1282 + location, 1283 + bits, 1284 + } => match bits { 1285 + Err(IntToBitsError::Unrepresentable { size }) => { 1276 1286 Some(ImpossibleBitArraySegmentPattern::UnrepresentableInteger { 1277 1287 value: value.clone(), 1278 - size, 1288 + size: *size, 1279 1289 location: *location, 1280 1290 signed: read_action.signed, 1281 1291 }) 1282 1292 } 1283 - } 1293 + _ => None, 1294 + }, 1284 1295 1285 1296 BitArrayMatchedValue::LiteralFloat(_) 1286 1297 | BitArrayMatchedValue::LiteralString { .. } ··· 3520 3531 } => BitArrayMatchedValue::LiteralInt { 3521 3532 value: int_value.clone(), 3522 3533 location: *location, 3534 + bits: int_to_bits( 3535 + int_value, 3536 + &read_action.size, 3537 + read_action.endianness, 3538 + read_action.signed, 3539 + ), 3523 3540 }, 3524 3541 ast::Pattern::Float { value, .. } => BitArrayMatchedValue::LiteralFloat(value.clone()), 3525 3542 ast::Pattern::String { value, .. } if segment.has_utf16_option() => { ··· 3555 3572 }, 3556 3573 x => panic!("unexpected segment value pattern {x:?}"), 3557 3574 } 3575 + } 3576 + 3577 + fn int_to_bits( 3578 + value: &BigInt, 3579 + read_size: &ReadSize, 3580 + endianness: Endianness, 3581 + signed: bool, 3582 + ) -> Result<BitVec<u8, Msb0>, IntToBitsError> { 3583 + let size = read_size 3584 + .constant_bits() 3585 + .ok_or(IntToBitsError::NonConstantSize)? 3586 + .to_u32() 3587 + .ok_or(IntToBitsError::ExceedsMaximumSize)?; 3588 + 3589 + if !representable_with_bits(value, size, signed) { 3590 + return Err(IntToBitsError::Unrepresentable { size }); 3591 + } else if size > BitArrayMatchedValue::MAX_BITS_INTERFERENCE { 3592 + return Err(IntToBitsError::ExceedsMaximumSize); 3593 + } 3594 + 3595 + // Pad negative numbers with 1s (true) and non-negative numbers with 0s (false) 3596 + let pad_digit = value.sign() == Sign::Minus; 3597 + let size = size as usize; 3598 + let mut bytes = int_to_bytes(value, endianness, signed); 3599 + let bytes_size = bytes.len() * 8; 3600 + 3601 + // There are 3 cases, which are easier to handle separately by endianness 3602 + // If the size of the bigint bytes equals the expected bits, we can return them as-is 3603 + // If there are more bits than we need, we need to trim some of the most significant bits. 3604 + // E.g. `6:3` yields one byte of which we need to trim the 5 most significant bits. 3605 + // Values like 999:3 are illegal and caught by the guard at the start of the function. 3606 + // If there are fewer bits than we need, we need to add some. 3607 + // E.g. `6:13` yields one byte which we need to pad with 5 bits 3608 + let bits = match (endianness, bytes_size.cmp(&size)) { 3609 + (_, Ordering::Equal) => BitVec::from_vec(bytes), 3610 + 3611 + (Endianness::Big, Ordering::Greater) => { 3612 + BitVec::from_bitslice(&bytes.view_bits()[bytes_size - size..]) 3613 + } 3614 + (Endianness::Big, Ordering::Less) => { 3615 + let mut bits = BitVec::repeat(pad_digit, size - bytes_size); 3616 + bits.extend_from_raw_slice(&bytes); 3617 + bits 3618 + } 3619 + 3620 + (Endianness::Little, Ordering::Greater) => { 3621 + // If the difference is greater than a byte, we returned an Error earlier 3622 + let remainder = size % 8; 3623 + if remainder == 0 { 3624 + BitVec::from_vec(bytes) 3625 + } else { 3626 + // If the size is not a multiple of 8, we need to truncate the most significant bits. 3627 + // As they are in the last byte, we leftshift by the appropriate amount and 3628 + // truncate the final bits after conversion 3629 + let last_byte = bytes.last_mut().expect("bytes must not be empty"); 3630 + *last_byte <<= 8 - remainder; 3631 + 3632 + let mut bits = BitVec::from_vec(bytes); 3633 + bits.truncate(size); 3634 + bits 3635 + } 3636 + } 3637 + (Endianness::Little, Ordering::Less) => { 3638 + let mut bits = BitVec::from_vec(bytes); 3639 + let padding: BitVec<u8, Msb0> = BitVec::repeat(pad_digit, size - bytes_size); 3640 + bits.extend_from_bitslice(padding.as_bitslice()); 3641 + bits 3642 + } 3643 + }; 3644 + Ok(bits) 3645 + } 3646 + 3647 + fn int_to_bytes(value: &BigInt, endianness: Endianness, signed: bool) -> Vec<u8> { 3648 + match (endianness, signed) { 3649 + (Endianness::Big, false) => value.to_bytes_be().1, 3650 + (Endianness::Big, true) => value.to_signed_bytes_be(), 3651 + (Endianness::Little, false) => value.to_bytes_le().1, 3652 + (Endianness::Little, true) => value.to_signed_bytes_le(), 3653 + } 3654 + } 3655 + 3656 + #[derive(Clone, Copy, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)] 3657 + pub enum IntToBitsError { 3658 + Unrepresentable { size: u32 }, 3659 + ExceedsMaximumSize, 3660 + NonConstantSize, 3558 3661 } 3559 3662 3560 3663 fn segment_size( ··· 3761 3864 assert!(representable_with_bits(&BigInt::from(-9), 5, true)); 3762 3865 } 3763 3866 } 3867 + 3868 + #[cfg(test)] 3869 + mod int_to_bits_test { 3870 + use std::assert_eq; 3871 + 3872 + use crate::{ 3873 + ast::Endianness, 3874 + exhaustiveness::{BitArrayMatchedValue, IntToBitsError, ReadSize, int_to_bits}, 3875 + }; 3876 + use bitvec::{bitvec, order::Msb0, vec::BitVec}; 3877 + use num_bigint::BigInt; 3878 + 3879 + fn read_size(size: u32) -> ReadSize { 3880 + ReadSize::ConstantBits(BigInt::from(size)) 3881 + } 3882 + 3883 + #[test] 3884 + fn int_to_bits_size_too_big() { 3885 + assert_eq!( 3886 + int_to_bits( 3887 + &BigInt::ZERO, 3888 + &read_size(BitArrayMatchedValue::MAX_BITS_INTERFERENCE + 1), 3889 + Endianness::Big, 3890 + true, 3891 + ), 3892 + Err(IntToBitsError::ExceedsMaximumSize), 3893 + ); 3894 + } 3895 + 3896 + #[test] 3897 + fn int_to_bits_zero() { 3898 + let expect = Ok(bitvec![u8, Msb0; 0; 3]); 3899 + assert_eq!( 3900 + int_to_bits(&BigInt::ZERO, &read_size(3), Endianness::Big, false), 3901 + expect 3902 + ); 3903 + assert_eq!( 3904 + int_to_bits(&BigInt::ZERO, &read_size(3), Endianness::Little, false), 3905 + expect 3906 + ); 3907 + 3908 + let expect = Ok(bitvec![u8, Msb0; 0; 10]); 3909 + assert_eq!( 3910 + int_to_bits(&BigInt::ZERO, &read_size(10), Endianness::Big, false), 3911 + expect 3912 + ); 3913 + assert_eq!( 3914 + int_to_bits(&BigInt::ZERO, &read_size(10), Endianness::Little, false), 3915 + expect 3916 + ); 3917 + } 3918 + 3919 + #[test] 3920 + fn int_to_bits_positive() { 3921 + // Exact match 3922 + assert_eq!( 3923 + int_to_bits( 3924 + &BigInt::from(0xff00), 3925 + &read_size(16), 3926 + Endianness::Big, 3927 + false 3928 + ), 3929 + Ok(BitVec::<u8, Msb0>::from_vec(vec![0xff, 0x00])), 3930 + ); 3931 + assert_eq!( 3932 + int_to_bits( 3933 + &BigInt::from(0xff00), 3934 + &read_size(16), 3935 + Endianness::Little, 3936 + false 3937 + ), 3938 + Ok(BitVec::<u8, Msb0>::from_vec(vec![0x00, 0xff])), 3939 + ); 3940 + 3941 + assert_eq!( 3942 + int_to_bits( 3943 + &BigInt::from(0b11_1111_0000), 3944 + &read_size(10), 3945 + Endianness::Big, 3946 + false 3947 + ), 3948 + Ok(bitvec![u8, Msb0; 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]), 3949 + ); 3950 + assert_eq!( 3951 + int_to_bits( 3952 + &BigInt::from(0b11_1111_0000), 3953 + &read_size(10), 3954 + Endianness::Little, 3955 + false 3956 + ), 3957 + Ok(bitvec![u8, Msb0; 1, 1, 1, 1, 0, 0, 0, 0, 1, 1]), 3958 + ); 3959 + 3960 + // Too few bits in int 3961 + assert_eq!( 3962 + int_to_bits(&BigInt::from(0xff), &read_size(12), Endianness::Big, false), 3963 + Ok(bitvec![u8, Msb0; 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]), 3964 + ); 3965 + assert_eq!( 3966 + int_to_bits( 3967 + &BigInt::from(0xff), 3968 + &read_size(12), 3969 + Endianness::Little, 3970 + false 3971 + ), 3972 + Ok(bitvec![u8, Msb0; 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]), 3973 + ); 3974 + } 3975 + 3976 + #[test] 3977 + fn int_to_bits_signed() { 3978 + assert_eq!( 3979 + int_to_bits(&BigInt::from(-128), &read_size(12), Endianness::Big, true), 3980 + Ok(bitvec![u8, Msb0; 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]), 3981 + ); 3982 + assert_eq!( 3983 + int_to_bits( 3984 + &BigInt::from(-128), 3985 + &read_size(12), 3986 + Endianness::Little, 3987 + true 3988 + ), 3989 + Ok(bitvec![u8, Msb0; 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]), 3990 + ); 3991 + } 3992 + }
+28
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__reachable_pattern_after_unreachable_equal_pattern.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/warnings.rs 3 + expression: "\npub fn wibble(bits) {\n case bits {\n <<97:3>> -> 1\n <<\"a\">> -> 2\n _ -> 3\n }\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn wibble(bits) { 8 + case bits { 9 + <<97:3>> -> 1 10 + <<"a">> -> 2 11 + _ -> 3 12 + } 13 + } 14 + 15 + 16 + ----- WARNING 17 + warning: Unreachable pattern 18 + ┌─ /src/warning/wrn.gleam:4:5 19 + 20 + 4 │ <<97:3>> -> 1 21 + │ ^^^^^^^^ 22 + │ │ 23 + │ A 3 bits unsigned integer will never match this value 24 + 25 + This pattern cannot be reached as it contains segments that will never 26 + match. 27 + 28 + Hint: It can be safely removed.
+25
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_int_pattern_with_prefix_int.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/warnings.rs 3 + expression: "\npub fn wibble(bits) {\n case bits {\n <<0b1:1, _:1>> -> 1\n <<0b11:2>> -> 2\n _ -> 3\n }\n}" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn wibble(bits) { 8 + case bits { 9 + <<0b1:1, _:1>> -> 1 10 + <<0b11:2>> -> 2 11 + _ -> 3 12 + } 13 + } 14 + 15 + ----- WARNING 16 + warning: Unreachable pattern 17 + ┌─ /src/warning/wrn.gleam:5:5 18 + 19 + 5 │ <<0b11:2>> -> 2 20 + │ ^^^^^^^^^^ 21 + 22 + This pattern cannot be reached as a previous pattern matches the same 23 + values. 24 + 25 + Hint: It can be safely removed.
+25
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_int_pattern_with_string_of_same_value.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/warnings.rs 3 + expression: "\npub fn wibble(bits) {\n case bits {\n <<\"a\">> -> 1\n <<97>> -> 2\n _ -> 3\n }\n}" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn wibble(bits) { 8 + case bits { 9 + <<"a">> -> 1 10 + <<97>> -> 2 11 + _ -> 3 12 + } 13 + } 14 + 15 + ----- WARNING 16 + warning: Unreachable pattern 17 + ┌─ /src/warning/wrn.gleam:5:5 18 + 19 + 5 │ <<97>> -> 2 20 + │ ^^^^^^ 21 + 22 + This pattern cannot be reached as a previous pattern matches the same 23 + values. 24 + 25 + Hint: It can be safely removed.
+43
compiler-core/src/type_/tests/warnings.rs
··· 4516 4516 } 4517 4517 4518 4518 #[test] 4519 + fn unreachable_int_pattern_with_string_of_same_value() { 4520 + assert_warning!( 4521 + r#" 4522 + pub fn wibble(bits) { 4523 + case bits { 4524 + <<"a">> -> 1 4525 + <<97>> -> 2 4526 + _ -> 3 4527 + } 4528 + }"# 4529 + ); 4530 + } 4531 + 4532 + #[test] 4533 + fn unreachable_int_pattern_with_prefix_int() { 4534 + assert_warning!( 4535 + r#" 4536 + pub fn wibble(bits) { 4537 + case bits { 4538 + <<0b1:1, _:1>> -> 1 4539 + <<0b11:2>> -> 2 4540 + _ -> 3 4541 + } 4542 + }"# 4543 + ); 4544 + } 4545 + 4546 + #[test] 4547 + fn reachable_pattern_after_unreachable_equal_pattern() { 4548 + assert_warning!( 4549 + r#" 4550 + pub fn wibble(bits) { 4551 + case bits { 4552 + <<97:3>> -> 1 4553 + <<"a">> -> 2 4554 + _ -> 3 4555 + } 4556 + } 4557 + "# 4558 + ); 4559 + } 4560 + 4561 + #[test] 4519 4562 fn unused_recursive_function_argument() { 4520 4563 assert_warning!( 4521 4564 "