-
Notifications
You must be signed in to change notification settings - Fork 211
Description
Motivations
- Would you like to implement this feature? [y/n] maybe
An ESP32 has a special section (see https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/memory-types.html#iram-instruction-ram) where functions are loaded from much faster than the regular code. This is useful when peripherals have to frequently access functions or these functions are called in an interrupt service routine.
For this the ESP-IDF framework provides the IRAM_ATTR that can be attached to functions that should be placed there.
Solution
This library should use a proc-macro to accomplish what the IRAM_ATTR does.
I am thinking of something like this:
impl LoopEncoder {
#[iram]
fn encode(&mut self, data: &[Self::Item]) -> usize {
// ...
}
}which might then expand to
impl LoopEncoder {
#[unsafe(link_section = ".iram1.someuniquename")]
#[inline(never)]
fn encode(&mut self, data: &[Self::Item]) -> usize {
// ...
}
}The unique name might be optional (maybe one could link to .iram1 and that's it?). I am not sure how exactly the linking works and if it works differently on some chips.
Conditionally placing it in iram could be accomplished through the #[cfg_attr(feature = "use-iram"), iram] attribute, so there is no need for that.
Considerations
Safety?
In newer versions, it seems like rust introduced unsafe proc-macro attributes? The link macro should therefore be wrapped in one to prevent errors in future rust versions.
If one could introduce undefined behavior by placing the function in ram, it would be sensible for this proc-macro to be unsafe as well (assuming this can be done and is not limited to built-in macros)
Other sections?
There are more sections of interest than just the iram, for example one is for placing in dram and another for placing it in tram. It seems sensible to add support for these as well.
What is iram0/iram1 used for?
The C-macro IRAM_ATTR will link the items annotated with it into the section .iram1.%d where %d is a globally unique number.
How these sections are mapped then depends on the linker script. The IRAM_ATTR is in esp_common
-> it will use this linker script that maps .iram1 to iram.
If there is no mapping of the iram section, I think it will fall back to the one defined in [esp_system/app.lf](https://github.com/espressif/esp-idf/blob/800f141f94c0f880c162de476512e183df671307/components/esp_system/app.lf#L17C5-L17C13, which maps it to .iram0.text.
So it seems like .iram1 is equivalent to .iram0.text, but different components can change this behavior through their linker scripts.
(by the way, the .iram0.text is then mapped to the binary in the esp_system/ld/{chip}/section.ld.in)
Address the concerns mentioned in the docs for the iram_attr, mainly place constants in the right section
Used constants aren't automatically placed in iram -> what would happen then? I assume it simply loads them from where they are stored, introducing delays
proc-macro attributes can rewrite the items they are attached to, so one could attach the right attributes to constants that are used inline like a string literal.
For references to constant items, it is unlikely that the macro is able to do much or even detect them (no type checking or traversing through the source code is available). These have to be addressed by the macro user.
Unique names/ids for each linked function
The C macro seems to use the __counter__ to give each one a unique name, in rust we should be able to do this better. For example we could use the function name, but this might not be unique -> some unique value would have to be introduced as well. Maybe a global invocation counter, but this depends on how the compiler invokes the macro. A much better solution would be to combine the name of the associated type, its location in the crate and the function name?
The module_path!() in combination with the line!/column! macros might be ideal for this use-case. Only thing to consider here is which characters are valid for a section name and which ones should be avoided? I assume . and most of the special characters shouldn't be used. The problem here would be how to remove these characters from the expanded macro.
Maybe they can be extracted from the Span of the TokenStream that is passed to the macro? Span::file.
Alternatives
Keep the current way of doing things where link attributes are placed on some functions.
Prior Art
As mentioned before, we should try to replicate what the IRAM_ATTR does.
The esp-hal does something similar with their #[ram] macro, but they use different linker sections, making it incompatible with this library.
//Normally, the linker script will put all code and rodata in flash,
//and all variables in shared RAM. These macros can be used to redirect
//particular functions/variables to other memory regions.
// Places code into IRAM instead of flash
#define IRAM_ATTR _SECTION_ATTR_IMPL(".iram1", __COUNTER__)
// Forces code into IRAM instead of flash
#define FORCE_IRAM_ATTR _SECTION_FORCE_ATTR_IMPL(".iram1", __COUNTER__)
// Forces data into DRAM instead of flash
#define DRAM_ATTR _SECTION_ATTR_IMPL(".dram1", __COUNTER__)
// Places code into TCM instead of flash
#define TCM_IRAM_ATTR _SECTION_ATTR_IMPL(".tcm.text", __COUNTER__)source
and
// Implementation for a unique custom section
//
// This prevents gcc producing "x causes a section type conflict with y"
// errors if two variables in the same source file have different linkage (maybe const & non-const) but are placed in the same custom section
//
// Using unique sections also means --gc-sections can remove unused
// data with a custom section type set
#ifndef CONFIG_IDF_TARGET_LINUX
#define _SECTION_ATTR_IMPL(SECTION, COUNTER) __attribute__((section(SECTION "." _COUNTER_STRINGIFY(COUNTER))))
#define _SECTION_FORCE_ATTR_IMPL(SECTION, COUNTER) __attribute__((noinline, section(SECTION "." _COUNTER_STRINGIFY(COUNTER))))
#define _COUNTER_STRINGIFY(COUNTER) #COUNTER
#else
// Custom section attributes are generally not used in the port files for Linux target, but may be found
// in the common header files. Don't declare custom sections in that case.
#define _SECTION_ATTR_IMPL(SECTION, COUNTER)
#define _SECTION_FORCE_ATTR_IMPL(SECTION, COUNTER)
#endif