Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2020-10

Proposal for implementing named address space support

18 messages · 4 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Christo Crause

The AVR architecture uses different address spaces with potentially different access methods [1]. Currently constant data (messages, bitmaps etc.) are stored in flash and copied to RAM by the start-up code. Data access makes use of the LD* instructions (and ST* if the data is writeable). Accessing e.g. read-only data directly from flash saves RAM space - this can be done byt informing the linker to locate the data in the progmem section. While one can use the section modifier to inform the linker to place data in specific address spaces it still require user supplied access code. This leads to code and maintenance duplication for the user to cater for different memory spaces, with associated code bloat.

The xtensa architecture has several address reagions that can be used for different purposes [2]. One example would be to place time critical interrupt code in the IRAM address space. The DRAM space can also be used to store constant data which can be accessed from an interrupt routine. Several other address spaces are available for specific use cases [3].

FPC can use the section modifier to specify which address space should be used for data. It is however very limited in that the code generator is not aware of the potentially different address spaces that can be specified, hence it is up to the user to provide the correct access code (for AVR). The section modifier also takes a user supplied string, which currently isn't checked for sensible or allowed values. I want to propose a unified mechanism for specifying both data and instruction address spaces using the same syntax for functions/procedures and variables, together with implementing the necessary access functionality as required (mainly AVR) for the compiler to generate the appropriate code without further user involvement.

The first proposal is to extend the section modifier to accept predefined (target specific) keywords (flash, eeprom, iram, dram, rtc_noinit and others), e.g.:
// AVR const data in flash
const
smileyGlyph: array[0..6] of byte = (19, 35, 64, 92, 64, 35, 19); section flash; // keep data in flash, read using spm (or LD* for newer AVRs)

// AVR Read/write access to data stored in EEPROM
var
  restartCount: byte; section eeprom; // If one needs a fixed location,
perhaps use in combination with absolute?
// Xtensa interrupt handler user code
procedure encoderPulse; section iram;
// Xtensa code that executes after wakeup from deep sleep
procedure afterDeepSleep; section rtc_iram;
// Xtensa variable that retains value after deep sleep:
var
  oldState: uint32; section rtc_slow_data;

The alternative is to skip the section modifier and simply use the various keywords (flash, iram etc.) as modifiers. Not sure if there is any advantage of this compared to the first proposal.

Critique, improvements and other use cases to consider would be appreciated.

[1]
https://gcc.gnu.org/onlinedocs/gcc/Named-Address-Spaces.html#AVR-Named-Address-Spaces [2]
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/general-notes.html#memory-layout [3]
https://github.com/espressif/esp-idf/blob/7b7c641076ef4c5dff7b994170d445bbb46f81ab/components/xtensa/include/esp_attr.h#L21

Reply by email

Christo Crause
to Christo Crause

I'm busy adding EEPROM access to the code generator for AVR. Although the general code required is relatively straight forward, the problem I ran into is that the EEPROM access registers are not at the same addresses for the different controllers. The register names and addresses are included in the respective controller units, so the addresses are available in the automatically loaded controller unit at compile time.

For example I do not know how to generate the equivalent to the following user assembler code:
lds r0, EECR
where EECR is defined as follows in a different unit: var EECR: byte absolute $1F;

The following line illustrates what I'm attempting to do:

  list.concat(taicpu.op_reg_ref(A_LDS,tmpreg,tmpref));

where tempref should reference the EECR symbol in another unit.

How do I build such a reference in the code generator so that the reference can resolve to the appropriate address?

Regards,
Christo

PM Christo Crause wrote 66 lines
The AVR architecture uses different address spaces with potentially different access methods [1]. Currently constant data (messages, bitmaps etc.) are stored in flash and copied to RAM by the start-up code. Data access makes use of the LD* instructions (and ST* if the data is writeable). Accessing e.g. read-only data directly from flash saves RAM space - this can be done byt informing the linker to locate the data in the progmem section. While one can use the section modifier to inform the linker to place data in specific address spaces it still require user supplied access code. This leads to code and maintenance duplication for the user to cater for different memory spaces, with associated code bloat. The xtensa architecture has several address reagions that can be used for different purposes [2]. One example would be to place time critical interrupt code in the IRAM address space. The DRAM space can also be used to store constant data which can be accessed from an interrupt routine. Several other address spaces are available for specific use cases [3]. FPC can use the section modifier to specify which address space should be used for data. It is however very limited in that the code generator is not aware of the potentially different address spaces that can be specified, hence it is up to the user to provide the correct access code (for AVR). The section modifier also takes a user supplied string, which currently isn't checked for sensible or allowed values. I want to propose a unified mechanism for specifying both data and instruction address spaces using the same syntax for functions/procedures and variables, together with implementing the necessary access functionality as required (mainly AVR) for the compiler to generate the appropriate code without further user involvement. The first proposal is to extend the section modifier to accept predefined (target specific) keywords (flash, eeprom, iram, dram, rtc_noinit and others), e.g.: // AVR const data in flash const smileyGlyph: array[0..6] of byte = (19, 35, 64, 92, 64, 35, 19); section flash; // keep data in flash, read using spm (or LD* for newer AVRs) // AVR Read/write access to data stored in EEPROM var restartCount: byte; section eeprom; // If one needs a fixed location, perhaps use in combination with absolute? // Xtensa interrupt handler user code procedure encoderPulse; section iram; // Xtensa code that executes after wakeup from deep sleep procedure afterDeepSleep; section rtc_iram; // Xtensa variable that retains value after deep sleep: var oldState: uint32; section rtc_slow_data; The alternative is to skip the section modifier and simply use the various keywords (flash, iram etc.) as modifiers. Not sure if there is any advantage of this compared to the first proposal. Critique, improvements and other use cases to consider would be appreciated. [1] https://gcc.gnu.org/onlinedocs/gcc/Named-Address-Spaces.html#AVR-Named-Address-Spaces [2] https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/general-notes.html#memory-layout [3] https://github.com/espressif/esp-idf/blob/7b7c641076ef4c5dff7b994170d445bbb46f81ab/components/xtensa/include/esp_attr.h#L21

Reply by email

Jonas Maebe
to Christo Crause
Christo Crause wrote 12 lines
For example I do not know how to generate the equivalent to the following user assembler code:   lds r0, EECR where EECR is defined as follows in a different unit: var EECR: byte absolute $1F; The following line illustrates what I'm attempting to do:   list.concat(taicpu.op_reg_ref(A_LDS,tmpreg,tmpref)); where tempref should reference the EECR symbol in another unit. How do I build such a reference in the code generator so that the reference can resolve to the appropriate address?

While possible, the code generator is the wrong abstraction level for this. Can't you generate a load node for that variable instead? What triggers this code generation?

Jonas

Reply by email

Christo Crause
to Jonas Maebe

On Thu, Dec 31, 2020 at 9:42 PM Jonas Maebe via fpc-devel <

Show quoted text 21 lines
wrote: On 31/12/2020 20:32, Christo Crause via fpc-devel wrote: > For example I do not know how to generate the equivalent to the > following user assembler code: > lds r0, EECR > where EECR is defined as follows in a different unit: > var EECR: byte absolute $1F; > > The following line illustrates what I'm attempting to do: > list.concat(taicpu.op_reg_ref(A_LDS,tmpreg,tmpref)); > where tempref should reference the EECR symbol in another unit. > > How do I build such a reference in the code generator so that the > reference can resolve to the appropriate address? While possible, the code generator is the wrong abstraction level for this. Can't you generate a load node for that variable instead? What triggers this code generation?

Jonas,

Thank you for your help. I'm trying to implement compiler support for different address spaces (flash and eeprom memory at the moment), mostly relevant to embedded and freertos type targets. My current approach is to check for specific section names of references in the code generator and then generate the appropriate access code in tavr.g_concatcopy tavr.a_load_ref_reg and so forth. Example code I want to support:

program store_restarts;
var
  numrestarts: byte; section '.eeprom';
begin
  numrestarts := 42;
end;

Reading from section .progmem closely follows the semantics of accessing general RAM so it was relatively easy to extend the existing code generator for that. EEPROM access requires more code (not a problem) but also controller specific register addresses (here is my problem, the information sits in a different unit). I haven't thought of modifying or creating a the load node since I'm much more familiar with the code generator side of the compiler (but still no expert...).

Any further hints or examples in the compiler I can study to tackle this via a load node would be appreciated.

Christo

Reply by email

Florian Klämpfl
to Christo Crause
Christo Crause wrote 49 lines
On Thu, Dec 31, 2020 at 9:42 PM Jonas Maebe via fpc-devel < > wrote: On 31/12/2020 20:32, Christo Crause via fpc-devel wrote: > For example I do not know how to generate the equivalent to the > following user assembler code: >   lds r0, EECR > where EECR is defined as follows in a different unit: > var EECR: byte absolute $1F; > > The following line illustrates what I'm attempting to do: >   list.concat(taicpu.op_reg_ref(A_LDS,tmpreg,tmpref)); > where tempref should reference the EECR symbol in another unit. > > How do I build such a reference in the code generator so that the > reference can resolve to the appropriate address? While possible, the code generator is the wrong abstraction level for this. Can't you generate a load node for that variable instead? What triggers this code generation? Jonas, Thank you for your help.  I'm trying to implement compiler support for different address spaces (flash and eeprom memory at the moment), mostly relevant to embedded and freertos type targets.  My current approach is to check for specific section names of references in the code generator and then generate the appropriate access code in tavr.g_concatcopy tavr.a_load_ref_reg and so forth.  Example code I want to support: program store_restarts; var   numrestarts: byte; section '.eeprom'; begin   numrestarts := 42; end; Reading from section .progmem closely follows the semantics of accessing general RAM so it was relatively easy to extend the existing code generator for that.  EEPROM access requires more code (not a problem) but also controller specific register addresses (here is my problem, the information sits in a different unit).  I haven't thought of modifying or creating a the load node since I'm much more familiar with the code generator side of the compiler (but still no expert...). Any further hints or examples in the compiler I can study to tackle this via a load node would be appreciated.

- you need to get the value of EECR: I would try with symtable.searchsym_in_module
- for ideas how a load node could generate code for a variable access as above, I would take the various generate_threadvar_access methods as example.

Is your current code somewhere available?

Reply by email

Christo Crause
to Florian Klämpfl

On Fri, Jan 1, 2021 at 3:59 PM Florian Klämpfl via fpc-devel <

Show quoted text 8 lines
wrote: - you need to get the value of EECR: I would try with symtable.searchsym_in_module - for ideas how a load node could generate code for a variable access as above, I would take the various generate_threadvar_access methods as example.

Thanks for the hints Florian. I'll look into this.

Is your current code somewhere available?

It is now on Github:
https://github.com/ccrause/freepascal/tree/sectionsupport

Reply by email

Christo Crause
to Christo Crause
PM Christo Crause wrote 8 lines
On Fri, Jan 1, 2021 at 3:59 PM Florian Klämpfl via fpc-devel < > wrote: > - you need to get the value of EECR: I would try with > symtable.searchsym_in_module >

Thanks for this hint. Settled on searchsym to find the symbol, I managed to make some further progress from there.

Reply by email

Christo Crause
to Christo Crause: The AVR architecture uses different address spaces with potentially different access…
PM Christo Crause wrote
FPC can use the section modifier to specify which address space should be used for data.
I've made some progress in certain areas (basically static variables), the
following type of AVR code example works:
var
  w: word = $BEEF; section '.eeprom';
  b: byte;
begin
  b := hi(w);
end.

However I see problems with propagating section information through reference type parameters. I'm not sure how to propagate the section information of the variable "w" through the parameter into the procedure of the following example:
var

  w: word = $BEEF; section '.eeprom';
  b: byte;
procedure doSomething(constref tmp: word);
begin
  b := hi(tmp);
end;
begin
  doSomething(w);
end.

At the moment there doesn't appear to be a way to do this without changing or adding something to the compiler. One idea could be to define new types identifying types in a specific address space, such as Teepromword. In this case the above example could be written as:
var

  w: Teepromword = $BEEF;
  b: byte;
procedure doSomething(constref tmp: word); overload;
begin
  b := hi(tmp);
end;
procedure doSomething(constref tmp: Teepromword); overload;
begin
  b := hi(tmp);
end;
begin
  doSomething(w);
end.

This way the compiler knows how to generate appropriate access based on the parameter type. It however leads to lots of code duplication. Which got me thinking about generics - could this be handled internally in the compiler via generics? It isn't clear to me how to tackle these issues so any advice would be appreciated.

Best regards,
Christo

Reply by email

Jonas Maebe
to Christo Crause
Christo Crause wrote 19 lines
On Sun, Oct 4, 2020 at 1:49 PM Christo Crause < > wrote: FPC can use the section modifier to specify which address space should be used for data. I've made some progress in certain areas (basically static variables), the following type of AVR code example works: var   w: word = $BEEF; section '.eeprom';   b: byte; begin   b := hi(w); end. However I see problems with propagating section information through reference type parameters. I'm not sure how to propagate the section information of the variable "w" through the parameter

Can't you use something like the far pointers used on the 8086 target? ( (a regular pointer only contains an offset, a far pointer contains both a segment and an offset) If so, the main downside is that it would increase the size of all reference parameters to include room for the section part of the pointer, unless you add a directive to disable that (or couple it to the existing farpointers directive).

Jonas

Reply by email

Christo Crause
to Jonas Maebe

On Sat, Jan 9, 2021 at 3:52 PM Jonas Maebe via fpc-devel <

Show quoted text 14 lines
wrote: On 08/01/2021 22:00, Christo Crause via fpc-devel wrote: > However I see problems with propagating section information through > reference type parameters. I'm not sure how to propagate the section > information of the variable "w" through the parameter Can't you use something like the far pointers used on the 8086 target? ( (a regular pointer only contains an offset, a far pointer contains both a segment and an offset) If so, the main downside is that it would increase the size of all reference parameters to include room for the section part of the pointer, unless you add a directive to disable that (or couple it to the existing farpointers directive).

Thanks for the idea Jonas. This could be a solution as is, but as you mentioned it comes with a parameter size penalty. Using a generic offset for the section (unknown at compile time on the callee side) would require runtime handing of the different access methods to the different address spaces, increasing code size. These two concerns are deal breakers for microcontrollers (at least in my opinion).

Could your idea not be simplified by allowing the use of the section
directive when defining a pointer?
type
  Peeprom = pointer; section '.eeprom';

This way when a parameter is defined as e.g. type Peeprom the compiler knows what access code should be generated for the specified section. It would also help with compile time error checking where assigning a regular pointer to a section pointer should not be allowed (unless the user applies a type cast).

Reply by email

Jonas Maebe
to Christo Crause
Christo Crause wrote 13 lines
On Sat, Jan 9, 2021 at 3:52 PM Jonas Maebe via fpc-devel wrote: Could your idea not be simplified by allowing the use of the section directive when defining a pointer? type Peeprom = pointer; section '.eeprom'; This way when a parameter is defined as e.g. type Peeprom the compiler knows what access code should be generated for the specified section. It would also help with compile time error checking where assigning a regular pointer to a section pointer should not be allowed (unless the user applies a type cast).

Something like that is definitely possible (and is similar to how LLVM handles cases like this: every pointer by default is in address space 0, but you can define as many additional address spaces as you want and associate pointers with them). However, it does not solve the issue with call-by-reference parameters. You can of course only allow those for the default address space, and require explicit pointers for other address spaces.

Jonas

Reply by email

Florian Klämpfl
to Christo Crause: On Sun, Oct 4, 2020 at 1:49 PM Christo Crause I've made some progress in certain areas…
Christo Crause wrote 60 lines
On Sun, Oct 4, 2020 at 1:49 PM Christo Crause < > wrote: FPC can use the section modifier to specify which address space should be used for data. I've made some progress in certain areas (basically static variables), the following type of AVR code example works: var   w: word = $BEEF; section '.eeprom';   b: byte; begin   b := hi(w); end. However I see problems with propagating section information through reference type parameters. I'm not sure how to propagate the section information of the variable "w" through the parameter into the procedure of the following example: var   w: word = $BEEF; section '.eeprom';   b: byte; procedure doSomething(constref tmp: word); begin   b := hi(tmp); end; begin   doSomething(w); end. At the moment there doesn't appear to be a way to do this without changing or adding something to the compiler.  One idea could be to define new types identifying types in a specific address space, such as Teepromword. In this case the above example could be written as: var   w: Teepromword = $BEEF;   b: byte; procedure doSomething(constref tmp: word); overload; begin   b := hi(tmp); end; procedure doSomething(constref tmp: Teepromword); overload; begin   b := hi(tmp); end; begin   doSomething(w); end. This way the compiler knows how to generate appropriate access based on the parameter type. It however leads to lots of code duplication.  Which got me thinking about generics - could this be handled internally in the compiler via generics?  It isn't clear to me how to tackle these issues so any advice would be appreciated.

I would simply not allow to use such references as independent pointers. This causes too much bloat. They can be used in assignments and expressions and that's it.

Reply by email

Christo Crause
to Florian Klämpfl

On Sun, Jan 17, 2021 at 3:51 PM Florian Klämpfl via fpc-devel <

Show quoted text 50 lines
wrote: Am 08.01.21 um 22:00 schrieb Christo Crause via fpc-devel: > On Sun, Oct 4, 2020 at 1:49 PM Christo Crause < > > wrote: > var > w: word = $BEEF; section '.eeprom'; > b: byte; > > procedure doSomething(constref tmp: word); > begin > b := hi(tmp); > end; > > begin > doSomething(w); > end. > > At the moment there doesn't appear to be a way to do this without > changing or adding something to the compiler. One idea could be to > define new types identifying types in a specific address space, such as > Teepromword. In this case the above example could be written as: > var > w: Teepromword = $BEEF; > b: byte; > > procedure doSomething(constref tmp: word); overload; > begin > b := hi(tmp); > end; > > procedure doSomething(constref tmp: Teepromword); overload; > begin > b := hi(tmp); > end; > > begin > doSomething(w); > end. > > This way the compiler knows how to generate appropriate access based on > the parameter type. It however leads to lots of code duplication. Which > got me thinking about generics - could this be handled internally in the > compiler via generics? It isn't clear to me how to tackle these issues > so any advice would be appreciated. I would simply not allow to use such references as independent pointers. This causes too much bloat. They can be used in assignments and expressions and that's it.

I have given up on that idea. I'm now busy working on Jonas's suggestion of section specific pointers (example:
https://github.com/ccrause/freepascal/wiki/Better-support-for-address-spaces-in-FPC#section-pointer-example ).

At the moment it introduces no runtime overhead (other than the required access instructions of course),
but to make it work across units is problematic. I suspect an extra field is required to store the section information associated with a pointer type (at least the way I've implemented it) in a ppu. If unit A defining such a pointer and a procedure using it is as parameter is called from unit B, it works if both units are compiled together. If unit A is already compiled and unit B is recompiled (symbols loaded from ppu) it doesn't carry the section information across and the compiler cannot determine which overloaded procedure to call.

Reply by email

Sven Barth
to Christo Crause

Christo Crause via fpc-devel schrieb am
Di., 19. Jan. 2021, 05:37:

Show quoted text 72 lines
On Sun, Jan 17, 2021 at 3:51 PM Florian Klämpfl via fpc-devel < > wrote: > Am 08.01.21 um 22:00 schrieb Christo Crause via fpc-devel: > > On Sun, Oct 4, 2020 at 1:49 PM Christo Crause < > > > wrote: > > var > > w: word = $BEEF; section '.eeprom'; > > b: byte; > > > > procedure doSomething(constref tmp: word); > > begin > > b := hi(tmp); > > end; > > > > begin > > doSomething(w); > > end. > > > > At the moment there doesn't appear to be a way to do this without > > changing or adding something to the compiler. One idea could be to > > define new types identifying types in a specific address space, such as > > Teepromword. In this case the above example could be written as: > > var > > w: Teepromword = $BEEF; > > b: byte; > > > > procedure doSomething(constref tmp: word); overload; > > begin > > b := hi(tmp); > > end; > > > > procedure doSomething(constref tmp: Teepromword); overload; > > begin > > b := hi(tmp); > > end; > > > > begin > > doSomething(w); > > end. > > > > This way the compiler knows how to generate appropriate access based on > > the parameter type. It however leads to lots of code duplication. > Which > > got me thinking about generics - could this be handled internally in > the > > compiler via generics? It isn't clear to me how to tackle these issues > > so any advice would be appreciated. > > I would simply not allow to use such references as independent pointers. > This causes too much bloat. They can be used in assignments and > expressions and that's it. > I have given up on that idea. I'm now busy working on Jonas's suggestion of section specific pointers (example: https://github.com/ccrause/freepascal/wiki/Better-support-for-address-spaces-in-FPC#section-pointer-example ). At the moment it introduces no runtime overhead (other than the required access instructions of course), but to make it work across units is problematic. I suspect an extra field is required to store the section information associated with a pointer type (at least the way I've implemented it) in a ppu. If unit A defining such a pointer and a procedure using it is as parameter is called from unit B, it works if both units are compiled together. If unit A is already compiled and unit B is recompiled (symbols loaded from ppu) it doesn't carry the section information across and the compiler cannot determine which overloaded procedure to call.

It isn't that hard. You essentially need to make sure that the necessary information is written in tpointerdef.ppuwrite and read again in tpointerdef.ppuload. You also need to increase the PPULongVersion in unit ppu and adjust the code in the ppudump utility.

Regards,
Sven

Reply by email

Christo Crause
to Sven Barth

On Tue, Jan 19, 2021 at 8:52 AM Sven Barth via fpc-devel <

Show quoted text 8 lines
wrote: It isn't that hard. You essentially need to make sure that the necessary information is written in tpointerdef.ppuwrite and read again in tpointerdef.ppuload. You also need to increase the PPULongVersion in unit ppu and adjust the code in the ppudump utility.

Thank you Sven and Jonas, your hints helped me a lot. For AVR, pointer types can now refer to sections and the compiler can generate appropriate access for pointer parameter types.

My next challenge is to extend error checking so that call parameters are checked for section incompatibility.

Reply by email

Christo Crause
to Christo Crause
PM Christo Crause wrote
My next challenge is to extend error checking so that call parameters are checked for section incompatibility.

Is there a way to define and run specific new tests only (say in a tests/testfolder/avr), in the compiler test folder? At the moment I've butchered the MakeFile and commented out the different test folders I'm not focussing on at the moment.

Reply by email

Florian Klämpfl
to Christo Crause
Christo Crause wrote 11 lines
On Sun, Jan 24, 2021 at 10:13 PM Christo Crause < > wrote: My next challenge is to extend error checking so that call parameters are checked for section incompatibility. Is there a way to define and run specific new tests only (say in a tests/testfolder/avr), in the compiler test folder? At the moment I've butchered the MakeFile and commented out the different test folders I'm not focussing on at the moment.

There is none, that's what I do normally as well.

Reply by email

Christo Crause
to Christo Crause: On Tue, Jan 19, 2021 at 8:52 AM Sven Barth via fpc-devel < Thank you Sven and Jonas, your…
PM Christo Crause wrote 18 lines
On Tue, Jan 19, 2021 at 8:52 AM Sven Barth via fpc-devel < > wrote: > > It isn't that hard. You essentially need to make sure that the necessary > information is written in tpointerdef.ppuwrite and read again in > tpointerdef.ppuload. You also need to increase the PPULongVersion in unit > ppu and adjust the code in the ppudump utility. > Thank you Sven and Jonas, your hints helped me a lot. For AVR, pointer types can now refer to sections and the compiler can generate appropriate access for pointer parameter types. My next challenge is to extend error checking so that call parameters are checked for section incompatibility.

A quick update:
By adding section information to type definitions (tdef) and type symbols it is possible to propagate the necessary information via parameters. Now it is possible to write overloaded procedures that handle section information correctly.

type
  Byte_e = type Byte; section '.eeprom';
procedure writeTest(var a: byte); overload;
begin
  a := 123;
end;
procedure writeTest(var a: byte_e); overload;
begin
  a := 234;
end;

Other functionality and error checking is demonstrated in the various test cases here:
https://github.com/ccrause/freepascal/tree/sectionsupport-rtl/tests/test/avr

Comments and suggestions are welcome. Next development will focus more on complicated types such as strings.

Reply by email