Skip to content

Add Clash numeric types plus basic impls#1176

Open
rslawson wants to merge 6 commits intomainfrom
rs/numeric-types
Open

Add Clash numeric types plus basic impls#1176
rslawson wants to merge 6 commits intomainfrom
rs/numeric-types

Conversation

@rslawson
Copy link
Collaborator

@rslawson rslawson commented Feb 2, 2026

This implements basic functionality for Index<N, T>, Unsigned<N, T>, Signed<N, T>, and BitVec<N, M>. This PR is currently marked as draft since I still need to:

  • Add documentation to the types themselves and to the size check traits, not just their methods
  • Create macros as aliases for the types
  • Create instantiation macros
  • Add tests?

Anywho, at least an initial review pass and/or suggestions on things I should add in this PR that I didn't (and that I don't have WIP stuff for already from before I did a lot of downscoping, like operator impls).

@hydrolarus
Copy link
Contributor

Have you done any tests to see if generated code does not need any trait bounds? To me it seems like it it shouldn't be required, but there's no usage examples, just making sure since I'm afraid of trait bounds 😱

@rslawson
Copy link
Collaborator Author

rslawson commented Feb 3, 2026

Have you done any tests to see if generated code does not need any trait bounds? To me it seems like it it shouldn't be required, but there's no usage examples, just making sure since I'm afraid of trait bounds 😱

I haven't done that yet, no. That's what I'll be working on today, so hopefully it's something I can complete today (^:

Comment on lines +396 to +413
let input_str = input.to_string();
if !(input_str.starts_with("0x") || input_str.starts_with("0X"))
|| input_str
.chars()
.skip(2)
.any(|c| !(c.is_ascii_hexdigit() || c == ' ' || c == '_'))
{
return syn::Error::new(
input.span(),
concat!(
"Expected a hexadecimal literal! Must start with `0x` or `0X` and contain ",
"only hexdigits, spaces, and underscores",
)
.to_string(),
)
.into_compile_error()
.into();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make sense to also support binary literals and maybe even base 10 literals?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could add support for binary literals pretty easily, just the issue is that for decimal literals there's the issue of not having a way to turn that into binary very easily past a certain size.

@rslawson rslawson force-pushed the rs/numeric-types branch 5 times, most recently from 3c4d3da to 5da7507 Compare March 2, 2026 15:46
@rslawson rslawson requested a review from hydrolarus March 2, 2026 18:09
let mut char_iter = input_str.chars().filter(char::is_ascii_hexdigit).rev();
let mut lower = true;
let mut idx = 0;
loop {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been thinking if there is a good way to use from_str_radix for this instead of hand rolling this, but I guess you want arbitrarily large literals too, so this wouldn't work 😔 Maybe this could be a comment here though? Otherwise I'll wonder the same thing in two months when I look at this again haha

Comment on lines +454 to +460
array_str.push('[');
for byte in buf {
write!(array_str, "0x{byte:02x}, ").unwrap();
}
array_str.pop();
array_str.pop();
array_str.push(']');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could check for the first element to have less manual shuffling of the characters in the string

Suggested change
array_str.push('[');
for byte in buf {
write!(array_str, "0x{byte:02x}, ").unwrap();
}
array_str.pop();
array_str.pop();
array_str.push(']');
array_str.push('[');
for (i, byte) in buf.iter().enumerate() {
if i > 0 {
write!(array_str, ", ").unwrap();
}
write!(array_str, "0x{byte:02x}").unwrap();
}
array_str.push(']');

@rslawson rslawson force-pushed the rs/numeric-types branch 2 times, most recently from dbbb82e to f2ca52c Compare March 3, 2026 12:27
@rslawson rslawson force-pushed the rs/numeric-types branch 3 times, most recently from 2bf627f to cdd3069 Compare March 18, 2026 12:31
@rslawson rslawson marked this pull request as ready for review March 18, 2026 13:50
@rslawson
Copy link
Collaborator Author

Marked as ready for review. I know there's one more thing I need to do (sign extension for Signed<N, T> when converting from BitVector<M, N>) before it's actually ready to go, but what with all the other things I want to do done plus all the tests passing I'm happy to call it ready for review.

@rslawson rslawson requested a review from hydrolarus March 19, 2026 15:16
Copy link
Contributor

@hydrolarus hydrolarus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looking good, but some questions regarding macro hygiene, style of type macros and usage of expr-macros

Comment on lines +692 to +694
let n = n as usize;
let len = n.div_ceil(8);
quote! { BitVector<#n, #len> }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This leaves suffixes everywhere, right? So BitVector<24usize, 3usize>. Wouldn't it make sense to use unsuffixed literals since usually the types of the literals aren't so important on the type level?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same goes for the other types below

Comment on lines +28 to 31
fn get_correct_size(n: u128) -> u32 {
let clog2 = (u128::BITS - (n - 1).leading_zeros()).next_power_of_two();
clog2.max(8)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name is a bit non-descriptive. Maybe native_size_in_bits or something? Same for the other local functions below

Comment on lines -47 to +44
bittide_hal::manual_additions::index::IndexTy<#n_lit, #ty>
Index<#n_lit, #ty>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly proc macros don't have the same hygiene as declarative macros... I guess it's because of using the macros in the bittide_hal? Otherwise I feel like the fully qualified version would be more robust?
Maaaybe it's possible to check if the macro is being invoked in bittide_hal and then change the prefix?

Comment on lines +169 to +172
quote! {
crate::manual_additions::signed::Signed<#n_lit, #ty>
}
.into()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it's using crate again, so this wouldn't work outside of bittide_hal?

Comment on lines 16 to +21
pub fn write_slice(&self, src: &[[u8; 8]], offset: usize) {
assert!(src.len() + offset <= Self::GATHER_MEMORY_LEN);
let mut off = offset;
for &val in src {
unsafe {
self.set_gather_memory_unchecked(off, val);
self.set_gather_memory_unchecked(off, BitVector::new_unchecked(val));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't checked usage sites of this function yet, but with the changed code generator, wouldn't src: &[BitVector![64]] make this easier? Same for the ScatterUnit below and the other scatter_gather impls in the other files


// Set margin and enable
elastic_buffer.set_auto_center_margin(margin);
elastic_buffer.set_auto_center_margin(Unsigned::new(margin).unwrap());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't unsigned!(margin, n = N) work here too?


// Manually adjust buffer off-center
elastic_buffer.set_adjustment(perturbation);
elastic_buffer.set_adjustment(Signed::new(perturbation).unwrap());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, signed!(perturbation, n = N)

Comment on lines +81 to +87
peripheral.set_status(Unsigned::new(*status_val).unwrap());
write!(name_buf, "peripheral{}.status.readback", i).unwrap();
expect(&name_buf, *status_val, peripheral.status());
expect(&name_buf, *status_val, peripheral.status().into_inner());
name_buf.clear();

// Write and read back control
peripheral.set_control(*control_val);
peripheral.set_control(Unsigned::new(*control_val).unwrap());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unsigned!(<expr>, n = N)

fn main() -> ! {
let active_entry0: [u8; 16] = core::array::from_fn(|i| i as u8);
let mut active_entry1: [u8; 16] = active_entry0;
let active_entry0 = core::array::from_fn(|i| Index::new(i as u8).unwrap());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

index!(i as u8, n = _)

Comment on lines +67 to +77
ve_repeat: Unsigned::new(8).unwrap(),
},
ValidEntry_12 {
ve_entry: active_entry1,
ve_repeat: 16,
ve_repeat: Unsigned::new(16).unwrap(),
},
];

let cal_shadow: [CalendarEntryType<Calendar>; 16] = core::array::from_fn(|i| ValidEntry_12 {
ve_entry: core::array::from_fn(|_j| i as u8),
ve_repeat: i as u16,
ve_entry: core::array::from_fn(|_j| Index::new(i as u8).unwrap()),
ve_repeat: Unsigned::new(i as u16).unwrap(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, contruction macros should probably be preferred unless there's a good reason to not use them

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants