-
Notifications
You must be signed in to change notification settings - Fork 216
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
[RFC] The value of new_typed
vs always type erasing drivers
#2572
Comments
I'd like to point out that there is only a single place where this makes any difference:
|
One place where optional type retention makes sense is GPIOs, where the extra instance dispatch is probably comparable to the IO speed. Are we okay with treating GPIOs as an exception here? |
Thirdly, we can opt back into type checking DMA channels if we approach constructors differently. Instead of This would make it harder to implement |
I would be okay with that. It would be nice to maybe do a small benchmark on this to see the difference. |
Following on from the discussion here: #2573 (comment): I was looking at our drivers, and future chips, namely the P4. Fortunately I don't think we'll have any issues with our current driver set, with the possible exception of USB, the P4 has one full speed usb and one high speed usb, and the driver is currently not instanced. It's not clear to me how much the two will share, so maybe it's not an issue in this case. I think this leads us towards type erase by default though, because it only takes a new chip to ruin this. We could work around this later down the line with cfgs etc but I'd rather avoid the issue entirely. |
My LA isn't the best, but on an ESP32: Typed
#![no_std]
#![no_main]
use esp_backtrace as _;
use esp_hal::{
delay::Delay,
gpio::{GpioPin, Input, Level, Output, Pin, Pull},
prelude::*,
};
#[entry]
fn main() -> ! {
let peripherals = esp_hal::init(esp_hal::Config::default());
// Set LED GPIOs as an output:
let led = Output::new_typed(peripherals.GPIO2, Level::Low);
toggle_pins(led)
}
fn toggle_pins(mut pin: Output<GpioPin<2>>) -> ! {
loop {
pin.toggle();
}
} Erased
#![no_std]
#![no_main]
use esp_backtrace as _;
use esp_hal::{
delay::Delay,
gpio::{Input, Level, Output, Pin, Pull},
prelude::*,
};
#[entry]
fn main() -> ! {
let peripherals = esp_hal::init(esp_hal::Config::default());
// Set LED GPIOs as an output:
let led = Output::new(peripherals.GPIO2, Level::Low);
toggle_pins(led)
}
fn toggle_pins(mut pin: Output) -> ! {
loop {
pin.toggle();
}
} There seems to be a ~3x difference in this one experiment. |
So I'm happy with GPIO being the exception. I'm now just wondering what happens if we want to make this exception again in the future, or maybe there are some other drivers that exist now that might benefit from it (RMT maybe? latency is quite important there). |
GPIO is an exception in another matter: we currently must use enums to represent the AnyPin, because not everything can be neatly indexed. With other peripherals this isn't the case - the Any peripheral can be a pointer to the Instnance (or the register block if critical) (or two pointers with State), which may be easier propagated by the compiler. |
Just wanted to sprinkle my 2 cents. For example SPI2 can do things that SPI3 can't, how do you want to rectify this discrepancy without type safety. I2S1 can do things that I2S0 can't on the base ESP32, albeit not very well documented. |
Whilst these are good points, I think they are a bit orthogonal to this issue, as we'll have to solve this regardless because erasure is already implemented. The short answer is a runtime check, but depending on the functionality disparity we may be able to keep some type safety checks. For instance if a driver doesn't support some large'ish mode, like slave mode we can probably type check that on the constructor. If some config/submode on a specific driver isn't supported it will probably be a runtime error of some kind. I think these need to be assessed on a case-by-case basis, there won't be a silver bullet. |
Wildly different hardware features may also be best implemented as different drivers, which aligns well with @MabezDev's typecheck-in-constructor point. |
Is this really the case by the way? I understand that we have different banks of GPIOs, and that might make things a bit harder, but I think it should still be doable? With adequate perf from the type erased GPIO driver (doesn't have to be stellar, just not 3 times slower :D) it might be able to make the decision for us. |
I guess we are already switching on the value for stuff so we could just as well switch on the pin number, but at least with an enum we don't have unreachable branches... |
For our drivers we have the option of opting into the
new_typed
method to create a driver without type erasure, i.eSpi<SPI2, Blocking>
.I'm wondering what use cases this opens up (if any) and whether we should always type erase.
I believe having the concrete types helps at least with DMA channels on PDMA devices, as we can (at compile time) check a user isn't passing an invalid DMA channel for the peripheral. We still have a runtime check for the case where they pass
AnyDmaChannel
, but there is merit to a compile time check.One downside is that our compiler allocated "inference slot" (the last generic param can be inferred by rustc automatically, but any others can't) it taken by the instance type. This means if we ever want the compiler to infer a generic parameter we can't.
The text was updated successfully, but these errors were encountered: