Lazarus

Programming => Embedded => Operating Systems => Embedded - AVR => Topic started by: dseligo on September 24, 2022, 11:22:20 am

Title: [SOLVED] Variable addresses in SRAM
Post by: dseligo on September 24, 2022, 11:22:20 am
I have project working on a ATmega2561.
I only use one timer and digital I/O pins.

When I run it on a ATmega32U4 I have problem like random restarts or garbled text shown on LCD.
Lazarus says '...22080 bytes code, 1047 bytes data', so it should fit into ATmega32U4's 32KB flash.

What I suspect is that variables in SRAM are overwritten with stack.

So finally my question: how can I see last location used with variables?

I can see current SP value in debugger, so last variable location would help me estimate how much space I have to 'free'.
Title: Re: Variable addresses in SRAM
Post by: d.ioannidis on September 24, 2022, 11:40:40 am
Hi,

  if you suspect heap and stack collision, you can find some information here [AVR] Investigating stack and heap management (https://github.com/ccrause/freepascal/wiki/%5BAVR%5D-Investigating-stack-and-heap-management).  Also in AVR Freaks you can find information ( c only ) for stack painting (https://www.avrfreaks.net/forum/soft-c-avrgcc-monitoring-stack-usage) to determine if you have stack problem ( I think I have something like that written in Free Pascal. I'll take a look tomorrow when I return home .... )

  Do you use the heap manager and/or recursive function/procedures ?

EDIT: Fixed url ....

regards,
Title: Re: Variable addresses in SRAM
Post by: dseligo on September 24, 2022, 12:01:37 pm
  if you suspect heap and stack collision, you can find some information here [AVR] Investigating stack and heap management (https://github.com/ccrause/freepascal/wiki/%5BAVR%5D-Investigating-stack-and-heap-management).  Also in AVR Freaks you can find information ( c only ) for stack painting (https://www.avrfreaks.net/forum/soft-c-avrgcc-monitoring-stack-usage) to determine if you have stack problem ( I think I have something like that written in Free Pascal. I'll take a look tomorrow when I return home .... )

Thank you for information.

Quote
  Do you use the heap manager and/or recursive function/procedures ?

No recursive functions, only consts, vars, arrays, records.
Title: Re: Variable addresses in SRAM
Post by: MarkMLl on September 24, 2022, 12:31:24 pm
No recursive functions, only consts, vars, arrays, records.

Any strings (as distinct from shortstrings) or dynamic arrays?

MarkMLl
Title: Re: Variable addresses in SRAM
Post by: d.ioannidis on September 24, 2022, 02:43:07 pm
Hi,


< snip >

So finally my question: how can I see last location used with variables?

I can see current SP value in debugger, so last variable location would help me estimate how much space I have to 'free'.

can you share your AVR development setup ?

OS, FPC version ?

How do you debug your AVR project ?

AVRSim, hardware debugger ( snap, medbg, atmel ICE ), Microchip( Atmel ) Studio's ATBackend, gdb version ?

No recursive functions, only consts, vars, arrays, records.

Also, if you use objects, IMHO, I recommend to switch to advancedrecords. I wasn't able to successfully debug objects when they're in different units ( I suspect bugs in dwarf  information ).

regards,
Title: Re: Variable addresses in SRAM
Post by: dseligo on September 24, 2022, 02:52:22 pm
No recursive functions, only consts, vars, arrays, records.

Any strings (as distinct from shortstrings) or dynamic arrays?

MarkMLl

In all units I have {$H-} on the top. Additionally, all strings are declared as String[10], String[16] or String[20].

All arrays are with defined boundaries, but I have types declared like this:
Code: Pascal  [Select][+][-]
  1. TInt16_array = array of Int16;

I use to access this array:
Code: Pascal  [Select][+][-]
  1. var vars_Int16: array [0 .. vars_Int16_num - 1] of Int16;

like this (Fvars_Int16 is pointer to vars_Int16):
Code: Pascal  [Select][+][-]
  1. editInt16 := TInt16_array(Fvars_Int16)[FCurrentItem.miVar];
Title: Re: Variable addresses in SRAM
Post by: MarkMLl on September 24, 2022, 02:54:43 pm
So your TInt16_array is dynamic and goes on the heap, and is possibly reallocated (possibly resulting in fragmentation) during operation.

MarkMLl
Title: Re: Variable addresses in SRAM
Post by: d.ioannidis on September 24, 2022, 02:58:31 pm
Hi,

All arrays are with defined boundaries, but I have types declared like this:
Code: Pascal  [Select][+][-]
  1. TInt16_array = array of Int16;

i don't think that you can use dynamic array's without using the heapmgr unit ...

regards,

Title: Re: Variable addresses in SRAM
Post by: dseligo on September 24, 2022, 03:10:25 pm
can you share your AVR development setup ?
OS, FPC version ?

How do you debug your AVR project ?

AVRSim, hardware debugger ( snap, medbg, atmel ICE ), Microchip( Atmel ) Studio's ATBackend, gdb version ?

Lazarus 2.3.0 (rev main-2_3-2515-g008af994c1) FPC 3.3.1 i386-win32-win32/win64 under Windows 11. Debug using atbackend.exe simulator described in your post here https://forum.lazarus.freepascal.org/index.php/topic,53342.0.html (https://forum.lazarus.freepascal.org/index.php/topic,53342.0.html) (btw. thanks for that :)).

Lazarus 2.3.0 (rev main-2_3-2551-g402c6a3c09) FPC 3.3.1 x86_64-linux-gtk2 under Debian 5.10.140-1 (virtual machine). Debugging using simavr described here https://wiki.freepascal.org/Remote_Debugging#Using_avr-gdb (https://wiki.freepascal.org/Remote_Debugging#Using_avr-gdb) (thanks @ccrause).

Quote
Also, if you use objects, IMHO, I recommend to switch to advancedrecords. I wasn't able to successfully debug objects when they're in different units ( I suspect bugs in dwarf  information ).

I use objects. Is there any other advantages/disadvantages of switching to advanced records (in regarding to AVR development)?
Title: Re: Variable addresses in SRAM
Post by: dseligo on September 24, 2022, 03:19:36 pm
So your TInt16_array is dynamic and goes on the heap, and is possibly reallocated (possibly resulting in fragmentation) during operation.

All arrays are with defined boundaries, but I have types declared like this:
Code: Pascal  [Select][+][-]
  1. TInt16_array = array of Int16;

i don't think that you can use dynamic array's without using the heapmgr unit ...

I must check in assembler, but I don't think it is used as dynamic array. Array is not declared as dynamic array, I just use this type so in my other unit I don't have to know size of the array.
Title: Re: Variable addresses in SRAM
Post by: MarkMLl on September 24, 2022, 03:48:12 pm
I use objects. Is there any other advantages/disadvantages of switching to advanced records (in regarding to AVR development)?

Objects or instances of classes? :-)

MarkMLl
Title: Re: Variable addresses in SRAM
Post by: dseligo on September 24, 2022, 03:55:27 pm
I use objects. Is there any other advantages/disadvantages of switching to advanced records (in regarding to AVR development)?

Objects or instances of classes? :-)

Old style object, like this:
Code: Pascal  [Select][+][-]
  1.   TMenu = object
  2.   private
  3. ...
  4.  
Title: Re: Variable addresses in SRAM
Post by: MarkMLl on September 24, 2022, 04:02:58 pm
Old style object, like this:

Just checking, because of the naming ambiguity :-)

MarkMLl
Title: Re: Variable addresses in SRAM
Post by: dseligo on September 24, 2022, 04:31:55 pm
I noticed one thing with constants: although I have {$WRITEABLECONST OFF} if I use typed constant like below they go into SRAM.

Code: Pascal  [Select][+][-]
  1. const
  2.   KrugAport: Byte = {%H-}Byte(@PORTB);
  3.   KrugApin: Byte = 1 shl 7;

I guess I won't use type with constants to save SRAM.
Title: Re: Variable addresses in SRAM
Post by: d.ioannidis on September 24, 2022, 05:50:03 pm
Hi,

< snip >
Also, if you use objects, IMHO, I recommend to switch to advancedrecords. I wasn't able to successfully debug objects when they're in different units ( I suspect bugs in dwarf  information ).

I use objects. Is there any other advantages/disadvantages of switching to advanced records (in regarding to AVR development)?


I don't know anything more than one can read here (https://forum.lazarus.freepascal.org/index.php/topic,30686.0.html). Debugging is the main reason I use advancedrecords for AVR ( maybe for other mcu's is the same regarding objects and debugging, I don't know ).


Lazarus 2.3.0 (rev main-2_3-2515-g008af994c1) FPC 3.3.1 i386-win32-win32/win64 under Windows 11. Debug using atbackend.exe simulator described in your post here https://forum.lazarus.freepascal.org/index.php/topic,53342.0.html (https://forum.lazarus.freepascal.org/index.php/topic,53342.0.html) (btw. thanks for that :)).

Thx ! Glad you find it useful.

regards,
Title: Re: Variable addresses in SRAM
Post by: MarkMLl on September 24, 2022, 06:00:23 pm
I noticed one thing with constants: although I have {$WRITEABLECONST OFF} if I use typed constant like below they go into SRAM.

That doesn't sound right, since a few days ago Sven said

Quote
if you declare the constant as {$WRITEABLECONST OFF} ... the storage location will be the binary's rdata (aka read only data!) section

https://forum.lazarus.freepascal.org/index.php/topic,60466.msg452560.html#msg452560

MarkMLl
Title: Re: Variable addresses in SRAM
Post by: Arioch on September 24, 2022, 09:48:53 pm
So your TInt16_array is dynamic and goes on the heap, and is possibly reallocated (possibly resulting in fragmentation) during operation.

All arrays are with defined boundaries, but I have types declared like this:
Code: Pascal  [Select][+][-]
  1. TInt16_array = array of Int16;

i don't think that you can use dynamic array's without using the heapmgr unit ...

I must check in assembler, but I don't think it is used as dynamic array. Array is not declared as dynamic array, I just use this type so in my other unit I don't have to know size of the array.

And what is the value for SizeOf(TInt16_array) then ?

Code: Pascal  [Select][+][-]
  1. var
  2.    data: array [0..10] of Int16;
  3.    sz1, sz2, adr1, adr2: IntPtr;
  4. type
  5.   TInt16_array = array of Int16;
  6. begin
  7.    sz1 := SizeOf(data);
  8.    sz2 := SizeOf(TInt16_array(data));
  9.  
  10.    adr1 := Integer(Pointer(@data[1]));
  11.    adr2 := Integer(Pointer(@TInt16_array(data)[1]));
  12.  
  13.    WriteLN( sz1: 16,  sz2: 16, adr1: 16, adr2: 16);
  14. end;
  15.  

Do values match?

This type IS dynamic array. And then a variable of dynarray type is a POINTER to array (see documentaiton about fundamental types implementation in FPC or Delphi)

If you typecast static array to dynamic array, then you typecast array to pointer.

The correct way to access would be either declare STATIC array [0..High(integer) div sizeof(elementtype)] of elementtype as accessor type, or to use Pointer Math/Arythmetics

https://www.freepascal.org/daily/doc/prog/progsu113.html
https://forum.lazarus.freepascal.org/index.php?topic=45986.0
https://docwiki.embarcadero.com/RADStudio/Sydney/en/Pointer_Math_(Delphi) (https://docwiki.embarcadero.com/RADStudio/Sydney/en/Pointer_Math_(Delphi))
Title: Re: Variable addresses in SRAM
Post by: dseligo on September 24, 2022, 11:54:22 pm
Code: Pascal  [Select][+][-]
  1. var
  2.    data: array [0..10] of Int16;
  3.    sz1, sz2, adr1, adr2: IntPtr;
  4. type
  5.   TInt16_array = array of Int16;
  6. begin
  7.    sz1 := SizeOf(data);
  8.    sz2 := SizeOf(TInt16_array(data));
  9.  
  10.    adr1 := Integer(Pointer(@data[1]));
  11.    adr2 := Integer(Pointer(@TInt16_array(data)[1]));
  12.  
  13.    WriteLN( sz1: 16,  sz2: 16, adr1: 16, adr2: 16);
  14. end;
  15.  

Do values match?

This doesn't compile.

If I change lines 8 and 11 to the code below I got the same address.

Code: Pascal  [Select][+][-]
  1.    sz2 := SizeOf(TInt16_array(@data));
  2.    adr2 := Integer(Pointer(@TInt16_array(@data)[1]));

Title: Re: Variable addresses in SRAM
Post by: Arioch on September 25, 2022, 01:35:29 am
You did not show your actual code, so i can only be guessing how you do accessing with type override...

> This doesn't compile.

Again, you did not show the exact error.

I suggest you to google for ESR's "how to ask questions smart way" - it is a harsh reading, from a man who wrote "a right to be rude", but it is helpful.

Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. type
  4.   TInt16_array = array of Int16;
  5.   PInt16_array = ^TInt16_array;
  6.   TData = packed array [0..10] of Int16;
  7.   PData=^TData;
  8. var
  9.    data: TData;
  10.    ptr1: PData; ptr2: PInt16_array;
  11.    sz0, sz1, sz2, adr0, adr1, adr2: IntPtr;
  12. begin
  13.    ptr1 := @data;
  14.    ptr2 := Pointer(ptr1);  // erase pointer type information, make different pointers compatible
  15.  
  16.   // make sure those pointer are equal, reference one and the same memory
  17.    writeln ( Integer(ptr1): 16, Integer(ptr2): 16, Integer(@data): 16);  
  18.  
  19.    sz1 := SizeOf(ptr1^);  // size of array typed to the pointer's type
  20.    sz2 := SizeOf(ptr2^);
  21.    sz0 := SizeOf(data);
  22.  
  23.    adr1 := Integer(Pointer(@ptr1^[1]));
  24.    adr2 := Integer(Pointer(@ptr2^[1]));
  25.    adr0 := Integer(Pointer(@data[1]));
  26.  
  27.    WriteLN(  sz0: 16,  sz1: 16,  sz2: 16);
  28.    WriteLN( adr0: 16, adr1: 16, adr2: 16);
  29.  
  30.    ReadLN;
  31. end.
  32.  

Win64:

Code: [Select]
           73744           73744           73744
              22              22               8
           73746           73746               2
Title: Re: Variable addresses in SRAM
Post by: Arioch on September 25, 2022, 01:50:23 am
Also read this: https://stackoverflow.com/questions/30410064/pascal-pointer-to-an-array-of-unknown-size



If I change lines 8 and 11 to the code below I got the same address.

Code: Pascal  [Select][+][-]
  1.    sz2 := SizeOf(TInt16_array(@data));
  2.    adr2 := Integer(Pointer(@TInt16_array(@data)[1]));

because here you convert not the array but the pointer to the array!

but dynarray is not merely pointer to values, the dynarray has a whole frame of ARC data BEFORE the values, and compiler would automatically change the data in that "before values" frame with you deal it with dynarrays.

so frankly, FPC should had baffled here and denied such a type miscast, if he denied my original one

but okay, for the sake as uniformity and completeness;

Code: Pascal  [Select][+][-]
  1.  
  2. type
  3.    Tint16_original = array[0..10] of int16;
  4. .....
  5.    adr1 := Integer(Pointer(@TInt16_original(@data)[1]));
  6.    adr2 := Integer(Pointer(@TInt16_array(@data)[1]));

Will it compile? No?
Well, if it will - you would see addresses do not match.

If it won't - you see those types are FUNDAMENTALLY DIFFERENT.
One only compiling with "@data" - pointer, not values. Another only compiling with "data" - value not pointer.



Frankly, i do not see what you say you see.
To me typecasts with @data do not compile, never and no way.

Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. type
  4.   ti = smallint;
  5.   pi = ^ti;
  6.  
  7.   tsa = array [0..4] of ti;
  8.   tpa = packed array [0..4] of ti;
  9.   tda = array of ti;
  10.  
  11.   psa = ^tsa;
  12.   ppa = ^tpa;
  13.   pda = ^tda;
  14.  
  15. var
  16.   p0: Pointer;
  17.   p1: pi;
  18.   data: array [0..4] of ti;
  19.  
  20. begin
  21. // {$T-}  or  {$T+}
  22.  
  23.   p1 := @data[1];
  24.  
  25.   p0 := @data;
  26.  
  27. // all those three are compiled fine
  28.   p0 := @tsa(data);
  29.   p0 := @tpa(data);
  30.   p0 := @tda(data);
  31.  
  32. // those three are not compiled, but errors differ
  33.   p0 := @tsa(@data);
  34.   p0 := @tpa(@data);
  35.   p0 := @tda(@data);
  36.  
  37. { if T- mode was set, then the errors would be:
  38.  
  39. project1.lpr(33,10) Error: Illegal type conversion: "Pointer" to "tsa"
  40. project1.lpr(34,10) Error: Illegal type conversion: "Pointer" to "tpa"
  41. project1.lpr(35,14) Error: Can't assign values to an address
  42.  
  43. if T+ mode was set, then the errors would be:
  44.  
  45. project1.lpr(33,10) Error: Illegal type conversion: "^Array[0..4] Of SmallInt" to "tsa"
  46. project1.lpr(34,10) Error: Illegal type conversion: "^Array[0..4] Of SmallInt" to "tpa"
  47. project1.lpr(35,14) Error: Can't assign values to an address
  48. }
  49.  
  50.   ReadLN;
  51. end.  

Also note that merely typecasting somethign to dynarray type already tries to WRITE somewhere into the @data
Did you expect it?
Title: Re: Variable addresses in SRAM
Post by: ccrause on September 25, 2022, 09:53:26 am
I noticed one thing with constants: although I have {$WRITEABLECONST OFF} if I use typed constant like below they go into SRAM.

That doesn't sound right, since a few days ago Sven said

Quote
if you declare the constant as {$WRITEABLECONST OFF} ... the storage location will be the binary's rdata (aka read only data!) section

https://forum.lazarus.freepascal.org/index.php/topic,60466.msg452560.html#msg452560

MarkMLl

A simple example suggests otherwise:
Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. {$WRITEABLECONST OFF}
  4. const
  5.   x: dword = $AABBCCDD;
  6.  
  7. begin
  8.   writeln(x);
  9. end.

Compiled on Linux 64 bit with FPC 3.2.2 and searching for the constant in the generated executable:
Code: [Select]
objdump -j .data  -s project1 | grep ccbb
 42a120 00000000 00000000 ddccbbaa 00000000  ................

Back to the AVR situation: currently FPC can either treat a const as a literal, or store the value in flash and copy to SRAM during startup.  In principle read-only data could be accessed in flash, but there is no compiler support for this (at the moment).
Title: Re: Variable addresses in SRAM
Post by: dseligo on September 25, 2022, 12:14:10 pm
You did not show your actual code, so i can only be guessing how you do accessing with type override...

I showed code in post #5 (copied directly from my project).


Quote
Again, you did not show the exact error.

I don't have errors with arrays, they work as I want them too.

Quote
I suggest you to google for ESR's "how to ask questions smart way" - it is a harsh reading, from a man who wrote "a right to be rude", but it is helpful.

I don't want to be rude, but I think you should read with understanding my first post.
Title: Re: Variable addresses in SRAM
Post by: Arioch on September 25, 2022, 12:22:29 pm
Code: Pascal  [Select][+][-]
  1. project1.lpr(35,14) Error: Can't assign values to an address

mere typecast to dynarray makes compiler write into ARC data frame time and again.

accessor should be a typed pointer (with pointer math one) or a static array of maximum attainable size
Title: Re: Variable addresses in SRAM
Post by: dseligo on September 25, 2022, 12:34:19 pm
Back to the AVR situation: currently FPC can either treat a const as a literal, or store the value in flash and copy to SRAM during startup.  In principle read-only data could be accessed in flash, but there is no compiler support for this (at the moment).

I managed to reduce SRAM usage by removing type from const declaration, so this consts are treated as a literal now. I still have a couple of strings which I'll move to flash with '.progmem'.

All in all, thank you all for help, especially you (https://github.com/ccrause/freepascal/wiki/%5BAVR%5D-Investigating-stack-and-heap-management (https://github.com/ccrause/freepascal/wiki/%5BAVR%5D-Investigating-stack-and-heap-management)) and d.ioannidis.
Title: Re: Variable addresses in SRAM
Post by: dseligo on September 25, 2022, 02:53:50 pm
accessor should be a typed pointer (with pointer math one) or a static array of maximum attainable size

You are correct regarding other platforms, but AFAIK in AVR platform dynamic arrays aren't supported, and it works like I did it. Nevertheless, I will rewrite it to avoid dynamic arrays in case that dynamic arrays become supported in AVR platform.


Regarding the original question: __bss_end is address location which I was looking for - stack pointer must stay above it not to corrupt variable data.

From ccrause's github account:
Code: Pascal  [Select][+][-]
  1. var
  2.   bss_end: pointer external name '__bss_end';
Title: Re: Variable addresses in SRAM
Post by: ccrause on September 25, 2022, 05:28:45 pm
Regarding the original question: __bss_end is address location which I was looking for - stack pointer must stay above it not to corrupt variable data.

From ccrause's github account:
Code: Pascal  [Select][+][-]
  1. var
  2.   bss_end: pointer external name '__bss_end';

This may not always be accurate, at the time I didn't realize there is one more section that follows __bss_end, the .noinit (https://gitlab.com/freepascal.org/fpc/source/-/blob/main/compiler/systems/t_embed.pas#L1139) section. If variables are located in .noinit, then __bss_end does not point to the end of all allocated data.  For reference, a more robust end of allocated data reference would be:
Code: Pascal  [Select][+][-]
  1. var
  2.   endOfAllocatedData: pointer external name '__noinit_end';
Title: Re: Variable addresses in SRAM
Post by: PascalDragon on September 25, 2022, 09:18:21 pm
No recursive functions, only consts, vars, arrays, records.

Any strings (as distinct from shortstrings) or dynamic arrays?

MarkMLl

In all units I have {$H-} on the top. Additionally, all strings are declared as String[10], String[16] or String[20].

All arrays are with defined boundaries, but I have types declared like this:
Code: Pascal  [Select][+][-]
  1. TInt16_array = array of Int16;

I use to access this array:
Code: Pascal  [Select][+][-]
  1. var vars_Int16: array [0 .. vars_Int16_num - 1] of Int16;

like this (Fvars_Int16 is pointer to vars_Int16):
Code: Pascal  [Select][+][-]
  1. editInt16 := TInt16_array(Fvars_Int16)[FCurrentItem.miVar];

These are not compatible. Don't use this. If it works for you it's by accident and not guaranteed to continue working.

Old style object, like this:

Just checking, because of the naming ambiguity :-)

In the context of AVR this isn't ambiguous, because Delphi-style classes aren't by default enabled on that target.

I noticed one thing with constants: although I have {$WRITEABLECONST OFF} if I use typed constant like below they go into SRAM.

That doesn't sound right, since a few days ago Sven said

Quote
if you declare the constant as {$WRITEABLECONST OFF} ... the storage location will be the binary's rdata (aka read only data!) section

https://forum.lazarus.freepascal.org/index.php/topic,60466.msg452560.html#msg452560

I should have clarified this a bit more: the location is read only data if the OS supports it and the compiler utilizes it correctly. On AVR this currently isn't supported and on Linux there are problems with relocations if variables are put into the .rdata section (at least that's what I remember).
Title: Re: Variable addresses in SRAM
Post by: MarkMLl on September 25, 2022, 09:32:51 pm
I should have clarified this a bit more: the location is read only data if the OS supports it and the compiler utilizes it correctly. On AVR this currently isn't supported and on Linux there are problems with relocations if variables are put into the .rdata section (at least that's what I remember).

Thanks for that.

MarkMLl
Title: Re: Variable addresses in SRAM
Post by: dseligo on September 25, 2022, 11:12:04 pm
No recursive functions, only consts, vars, arrays, records.

Any strings (as distinct from shortstrings) or dynamic arrays?

MarkMLl

In all units I have {$H-} on the top. Additionally, all strings are declared as String[10], String[16] or String[20].

All arrays are with defined boundaries, but I have types declared like this:
Code: Pascal  [Select][+][-]
  1. TInt16_array = array of Int16;

I use to access this array:
Code: Pascal  [Select][+][-]
  1. var vars_Int16: array [0 .. vars_Int16_num - 1] of Int16;

like this (Fvars_Int16 is pointer to vars_Int16):
Code: Pascal  [Select][+][-]
  1. editInt16 := TInt16_array(Fvars_Int16)[FCurrentItem.miVar];

These are not compatible. Don't use this. If it works for you it's by accident and not guaranteed to continue working.

Thanks, noted
Title: Re: Variable addresses in SRAM
Post by: dseligo on September 25, 2022, 11:12:29 pm
Regarding the original question: __bss_end is address location which I was looking for - stack pointer must stay above it not to corrupt variable data.

From ccrause's github account:
Code: Pascal  [Select][+][-]
  1. var
  2.   bss_end: pointer external name '__bss_end';

This may not always be accurate, at the time I didn't realize there is one more section that follows __bss_end, the .noinit (https://gitlab.com/freepascal.org/fpc/source/-/blob/main/compiler/systems/t_embed.pas#L1139) section. If variables are located in .noinit, then __bss_end does not point to the end of all allocated data.  For reference, a more robust end of allocated data reference would be:
Code: Pascal  [Select][+][-]
  1. var
  2.   endOfAllocatedData: pointer external name '__noinit_end';

Thank you
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: dseligo on October 05, 2022, 10:09:54 am
Eventually I discovered what consumed my SRAM: it was string concatenation.
After confirming that stack overwrites allocated data, with the compiler switch -at (together with -al) I easily found out how much SRAM was consumed for temporary variables in every unit. For each concatenation of strings, 256 bytes were consumed and in some units more than 1KB was reserved.
I solved it by making my own string concatenation procedures and now even AVRs with 2KB SRAM are enough for this project of mine.

Bottom line: be careful with strings in the embedded world.
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: Thaddy on October 05, 2022, 10:54:16 am
It is better to stick to shortstring instead of ansistring or others. On most if not all platforms this reserves just 256 byte or even less if declared properly.
E.g. if you know before hand what the maximum string length is, you can declare a shortstring type with the appropriate size:
Code: Pascal  [Select][+][-]
  1. type Tmystring = string[16];
will occupy just 17 bytes on embedded targets. (one for size)
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: MarkMLl on October 05, 2022, 10:55:58 am
It is better to stick to shortstring instead of ansistring or others. On most if not all platforms this reserves just 256 byte or even less if declared properly.
E.g. if you know before hand what the maximum string length is, you can declare a shortstring type with the appropriate size:
Code: Pascal  [Select][+][-]
  1. type Tmystring = string[16];
will occupy just 17 bytes on embedded targets. (one for size)

He'd already assured us he was (in response to my reply #3 in this thread). Something's not right...

MarkMLl
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: dseligo on October 05, 2022, 11:37:57 am
It is better to stick to shortstring instead of ansistring or others. On most if not all platforms this reserves just 256 byte or even less if declared properly.
E.g. if you know before hand what the maximum string length is, you can declare a shortstring type with the appropriate size:
Code: Pascal  [Select][+][-]
  1. type Tmystring = string[16];
will occupy just 17 bytes on embedded targets. (one for size)

Yes, I use shortstring with appropriate size and it occupies size + 1. Problem is when strings are concatenated.

Consider this program:
Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. {$mode ObjFPC}
  4.  
  5. var s1: String[10];
  6.  
  7. begin
  8.   s1 := 'test';
  9.   s1 := s1 + '123';
  10. end.

In assembler listing you see:
Code: ASM  [Select][+][-]
  1. ...
  2. # Temps allocated between r28+2 and r28+258
  3. ...
  4. # [8] s1 := 'test';
  5.         ldi     r18,lo8(_sPROJECT1s_Ld1)
  6.         ldi     r21,hi8(_sPROJECT1s_Ld1)
  7.         ldi     r24,lo8(U_sPsPROJECT1_ss_S1)
  8.         ldi     r25,hi8(U_sPsPROJECT1_ss_S1)
  9.         ldi     r22,10
  10.         mov     r23,r1
  11.         mov     r20,r18
  12.         call    fpc_shortstr_to_shortstr
  13. # Temp 2,256 allocated
  14. # [9] s1 := s1 + '123';
  15.         ldi     r18,lo8(_sPROJECT1s_Ld2)
  16.         ldi     r19,hi8(_sPROJECT1s_Ld2)
  17.         ldi     r20,lo8(U_sPsPROJECT1_ss_S1)
  18.         ldi     r21,hi8(U_sPsPROJECT1_ss_S1)
  19.         ldi     r24,lo8(2)
  20.         ldi     r25,hi8(2)
  21.         add     r24,r28
  22.         adc     r25,r29
  23.         ldi     r22,-1
  24.         mov     r23,r1
  25.         call    fpc_shortstr_concat
  26.         ldi     r20,lo8(2)
  27.         ldi     r21,hi8(2)
  28.         add     r20,r28
  29.         adc     r21,r29
  30.         ldi     r24,lo8(U_sPsPROJECT1_ss_S1)
  31.         ldi     r25,hi8(U_sPsPROJECT1_ss_S1)
  32.         ldi     r22,10
  33.         mov     r23,r1
  34.         call    fpc_shortstr_to_shortstr
  35. # Temp 2,256 released
  36. # [10] end.
  37.  

My problem was with '# Temp 2,256 allocated' - in addition to space variable occupies, FP allocated 256 bytes more in this example. It was for concatenation.

I attached complete asm listing.
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: Thaddy on October 05, 2022, 12:09:54 pm
But concatenations *must* assume  string[255] unless you declare an intermediate string type with the expected total length.
The compiler will respect that.
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: ccrause on October 05, 2022, 01:14:41 pm
The compiler uses the fpc_shortstr_concat (https://gitlab.com/freepascal.org/fpc/source/-/blob/main/rtl/inc/generic.inc#L934) helper function to copy the source strings into the destination string [in this context string means shortstring for brevity].  The destination string is passed to this function via a var parameter with a shortstring type, i.e. the compiler has to create a temporary variable of type shortstring (as Thaddy mentioned) which will take up 256 bytes of stack space.

To avoid this large temporary variable, consider using move (https://www.freepascal.org/docs-html/rtl/system/move.html) to copy the string fragments directly into the destination.  This way there is no temporary variable at play, but the programmer has to handle a couple more responsibilities to ensure you don't unintentionally overwrite data, find the correct offset into the destination, ensure the destination is correctly sized, and update the string length after adding data.  Basically re-implement the functionality of fpc_shortstr_concat, but without the use of a temporary variable.
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: MarkMLl on October 05, 2022, 01:41:41 pm
The compiler uses the fpc_shortstr_concat (https://gitlab.com/freepascal.org/fpc/source/-/blob/main/rtl/inc/generic.inc#L934) helper function to copy the source strings into the destination string [in this context string means shortstring for brevity].  The destination string is passed to this function via a var parameter with a shortstring type, i.e. the compiler has to create a temporary variable of type shortstring (as Thaddy mentioned) which will take up 256 bytes of stack space.

If that is the case, why is that code comparing the address of the destination with those of the source strings?

MarkMLl
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: dseligo on October 05, 2022, 04:22:15 pm
To avoid this large temporary variable, consider using move (https://www.freepascal.org/docs-html/rtl/system/move.html) to copy the string fragments directly into the destination.  This way there is no temporary variable at play, but the programmer has to handle a couple more responsibilities to ensure you don't unintentionally overwrite data, find the correct offset into the destination, ensure the destination is correctly sized, and update the string length after adding data.  Basically re-implement the functionality of fpc_shortstr_concat, but without the use of a temporary variable.

I did that, although I didn't use built-in move (I am careful about string sizes, but since I use strings only for LCD display I just cut if it doesn't fit):

Code: Pascal  [Select][+][-]
  1. procedure MyStringAddLeft(constref s1: TLCDString; var s: TLCDString);
  2. const MaxStrSize = SizeOf(TLCDString) - 1;
  3. var bLengthLeft, bFinalLength, bCounter, bIndex: Byte;
  4. begin
  5.   // get left string length and calculate final length
  6.   bLengthLeft := Length(s1);
  7.   If bLengthLeft > MaxStrSize then bLengthLeft := MaxStrSize;
  8.   bFinalLength := bLengthLeft + Length(s);
  9.   If bFinalLength > MaxStrSize then bFinalLength := MaxStrSize;
  10.   s[0] := Chr(bFinalLength);
  11.  
  12.   // move original string to the right, cut right side if it doesn't fit
  13.   bIndex := bFinalLength - bLengthLeft;
  14.   For bCounter := bFinalLength downto bLengthLeft + 1 do begin
  15.     s[bCounter] := s[bIndex];
  16.     dec(bIndex);
  17.   end;
  18.  
  19.   // copy string to the left side
  20.   For bCounter := 1 to bLengthLeft do
  21.     s[bCounter] := s1[bCounter];
  22. end;
  23.  
  24. procedure MyStringAddRight(var s: TLCDString; constref s1: TLCDString);
  25. const MaxStrSize = SizeOf(TLCDString) - 1;
  26. var bLengthLeft, bFinalLength, bCounter, bIndex: Byte;
  27. begin
  28.   // get left string length and calculate final length
  29.   bLengthLeft := Length(s);
  30.   bFinalLength := bLengthLeft + Length(s1);
  31.   If bFinalLength > MaxStrSize then bFinalLength := MaxStrSize;
  32.   s[0] := Chr(bFinalLength);
  33.   bIndex := 1;
  34.  
  35.   // copy string to the right side
  36.   For bCounter := bLengthLeft + 1 to bFinalLength do begin
  37.     s[bCounter] := s1[bIndex];
  38.     inc(bIndex);
  39.   end;
  40. end;
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: ccrause on October 05, 2022, 04:35:11 pm
The compiler uses the fpc_shortstr_concat (https://gitlab.com/freepascal.org/fpc/source/-/blob/main/rtl/inc/generic.inc#L934) helper function to copy the source strings into the destination string [in this context string means shortstring for brevity].  The destination string is passed to this function via a var parameter with a shortstring type, i.e. the compiler has to create a temporary variable of type shortstring (as Thaddy mentioned) which will take up 256 bytes of stack space.

If that is the case, why is that code comparing the address of the destination with those of the source strings?

MarkMLl
This is a good question.  Since fpc_shortstr_concat can handle the situation where either of the two sources can also be the destination, the compiler can omit the temp in this case.  However, in the disassembly listing @dseligo showed, the compiler is using a temporary shortstring to pass to fpc_shortstr_concat as the dests parameter, followed by a fpc_shortstr_to_shortstr call to copy the temporary string into the final destination.  Seems like there is an opportunity for optimization here?
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: d.ioannidis on October 05, 2022, 04:43:01 pm
Hi,


I did that, although I didn't use built-in move (I am careful about string sizes, but since I use strings only for LCD display I just cut if it doesn't fit):


if you use strings only for lcd display how about a global shortstring variable which you'll use it as a temp buffer ?

Concatenate a shortstring variable ( not a string[10] ) doesn't produce temp variables AFAIU . ( try the next example ; )

Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. {$mode ObjFPC}
  4.  
  5. var s1: ShortString;
  6.  
  7. begin
  8.   s1 := 'test';
  9.   s1 := s1 + '123';
  10. end.
  11.  

Also, you can use it with position index for storage for the lcd rows ( i.e. the rows of a 4x20 LCD will be at s1[10] upto s1[30] for row 1, s1[40] upto s1[60] for row 2, etc ) ...

No more stack problems ;)

regards,
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: MarkMLl on October 05, 2022, 05:06:30 pm
This is a good question.  Since fpc_shortstr_concat can handle the situation where either of the two sources can also be the destination, the compiler can omit the temp in this case.  However, in the disassembly listing @dseligo showed, the compiler is using a temporary shortstring to pass to fpc_shortstr_concat as the dests parameter, followed by a fpc_shortstr_to_shortstr call to copy the temporary string into the final destination.  Seems like there is an opportunity for optimization here?

Removal of pessimisation more like :-) My suspicion would be that the compiler is doing something odd to that it can get High() of the destination parameter... particularly since it's a var I certainly hope it's not doing spurious copies.

MarkMLl
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: alpine on October 05, 2022, 05:41:33 pm
Eventually I discovered what consumed my SRAM: it was string concatenation.
After confirming that stack overwrites allocated data, with the compiler switch -at (together with -al) I easily found out how much SRAM was consumed for temporary variables in every unit. For each concatenation of strings, 256 bytes were consumed and in some units more than 1KB was reserved.
I solved it by making my own string concatenation procedures and now even AVRs with 2KB SRAM are enough for this project of mine.

Bottom line: be careful with strings in the embedded world.
I strongly recommend using PChar instead of ShortString in such a restricted environments.
Then use strlen(), strcopy(), strcat() from the strings unit. It will be a bit tedious at first, but you won't hit the curbs as you did now.
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: MarkMLl on October 05, 2022, 06:25:20 pm
I strongly recommend using PChar instead of ShortString in such a restricted environments.
Then use strlen(), strcopy(), strcat() from the strings unit. It will be a bit tedious at first, but you won't hit the curbs as you did now.

What would you use for the base variables- array[something] of char?

It certainly sounds like a useful workaround, but the bottom line is that this shouldn't be happening: if OP really did define his strings as string[something] then they all have a fixed length and there should be no need of any temporaries... I can't see where shortstring has got into this.

MarkMLl
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: alpine on October 05, 2022, 07:01:47 pm
I strongly recommend using PChar instead of ShortString in such a restricted environments.
Then use strlen(), strcopy(), strcat() from the strings unit. It will be a bit tedious at first, but you won't hit the curbs as you did now.

What would you use for the base variables- array[something] of char?
Yes.

It certainly sounds like a useful workaround, but the bottom line is that this shouldn't be happening: if OP really did define his strings as string[something] then they all have a fixed length and there should be no need of any temporaries... I can't see where shortstring has got into this.
It happens because you can use assignments ( := ) and expressions with ShortString. Actually only the ( + ) operator is defined and you would say it can be easily translated, but (I suspect) the compiler don't bother for the associativity, commutativity, etc. of each type + (e.g. string concatenation and numeric addition have different properties) and makes it the easy way - first computes the expression in temporary, then makes the assignment. In the common case, the infix notation for expressions always may need some temporaries for the evaluation.

When using PChars then all that you can do is to call a subroutines and to pass them as parameters. There won't be a hidden allocations.
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: ccrause on October 06, 2022, 08:07:01 am
For the adventurous programmer, here is a proposed compiler enhancement that skips creating a temporary variable when assigning a shortstring concatenation expression to a shortstring variable: https://gitlab.com/ccrause/fpc-source/-/commits/shortstrconcat

The following test:
Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. var
  4.   s1: string[5] = '123';
  5.   s2: string[5] = '4567';
  6.  
  7. begin
  8.   s1 := s1 + s2;
  9.   writeln(s1);
  10. end.
now compiles to:
Code: [Select]
PASCALMAIN:
.Lc2:
.Ll1:
# [project1.lpr]
# [7] begin
call FPC_INIT_FUNC_TABLE
.Ll2:
# [8] s1 := s1 + s2;
ldi r18,lo8(TC_sPsPROJECT1_ss_S2)
ldi r19,hi8(TC_sPsPROJECT1_ss_S2)
ldi r20,lo8(TC_sPsPROJECT1_ss_S1)
ldi r21,hi8(TC_sPsPROJECT1_ss_S1)
ldi r24,lo8(TC_sPsPROJECT1_ss_S1)
ldi r25,hi8(TC_sPsPROJECT1_ss_S1)
ldi r22,5
mov r23,r1
call fpc_shortstr_concat
.Ll3:
# [9] writeln(s1);
call fpc_get_output
movw r2,r24
ldi r18,lo8(TC_sPsPROJECT1_ss_S1)
ldi r19,hi8(TC_sPsPROJECT1_ss_S1)
movw r20,r2
mov r22,r1
mov r23,r1
mov r24,r1
mov r25,r1
call fpc_write_text_shortstr
call fpc_iocheck
movw r24,r2
call fpc_writeln_end
call fpc_iocheck
.Ll4:
# [10] end.
call fpc_do_exit

This is only a start, since this patch only improves the case of s0 := s1 + s2 where all strings are of typeshortstring and s0 may the same variable as s1 or s2..

Note: there may be unintended side effects caused by this patch, so testing and feedback would help improve the quality...
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: PascalDragon on October 06, 2022, 08:58:37 am
This is a good question.  Since fpc_shortstr_concat can handle the situation where either of the two sources can also be the destination, the compiler can omit the temp in this case.  However, in the disassembly listing @dseligo showed, the compiler is using a temporary shortstring to pass to fpc_shortstr_concat as the dests parameter, followed by a fpc_shortstr_to_shortstr call to copy the temporary string into the final destination.  Seems like there is an opportunity for optimization here?

Removal of pessimisation more like :-) My suspicion would be that the compiler is doing something odd to that it can get High() of the destination parameter... particularly since it's a var I certainly hope it's not doing spurious copies.

High(SomeShortStringVar) simply returns the number of elements that SomeShortStringVar has available. This is always solved at compile time and in the case of fpc_shortstr_concat since dests is declared as ShortString it will always be 255.

For the adventurous programmer, here is a proposed compiler enhancement that skips creating a temporary variable when assigning a shortstring concatenation expression to a shortstring variable: https://gitlab.com/ccrause/fpc-source/-/commits/shortstrconcat

It might be better to extend fpc_shortstr_concat (and maybe also fpc_shortstr_concat_multi) by a parameter that contains the length of the target variable and use a PShortString instead of a reference to a ShortString. This way the assignment can be done better in the general way.
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: MarkMLl on October 06, 2022, 09:16:09 am
It might be better to extend fpc_shortstr_concat (and maybe also fpc_shortstr_concat_multi) by a parameter that contains the length of the target variable and use a PShortString instead of a reference to a ShortString. This way the assignment can be done better in the general way.

It's still unclear where shortstring- as distinct from string[something] is coming from.

MarkMLl
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: alpine on October 06, 2022, 09:50:11 am
@ccrause
Well done.

Without underestimating your effort (that is an improvement on itself) for the embedded it shouldn't change the picture. One can get a false confidence and pretty common statements as:
Code: Pascal  [Select][+][-]
  1.  msg := var_name + ': ' + var_value;
will have the same (bad) effects. Again, with the left/right associativity of concat, it can be optimized, but it will require much more effort.
Simply calling a procedures for assignment and concatenation, even with a shortstrings, removes all that burden of the expression evaluation.
The ( + ) operator isn't any good for defensive programming, something that should be practiced from all embedded programmers. I avoid using it even on a PC (+ have also other side effects that I'm not happy with).

Title: Re: [SOLVED] Variable addresses in SRAM
Post by: MarkMLl on October 06, 2022, 10:35:59 am
The ( + ) operator isn't any good for defensive programming, something that should be practiced from all embedded programmers. I avoid using it even on a PC (+ have also other side effects that I'm not happy with).

Agreed, but if basic text handling can't be done safely this negates the value of using Pascal- particularly now that C/C++ have, generally speaking, taken on board Wirth's ideas about type checking etc. (as far as allowed by their modus operandi and the amount of legacy code).

MarkMLl
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: ccrause on October 06, 2022, 10:53:11 am
Without underestimating your effort (that is an improvement on itself) for the embedded it shouldn't change the picture. One can get a false confidence and pretty common statements as:
Code: Pascal  [Select][+][-]
  1.  msg := var_name + ': ' + var_value;
will have the same (bad) effects. Again, with the left/right associativity of concat, it can be optimized, but it will require much more effort.
Indeed, this patch seems like a quick fix for a specific situation, not a general fix for other similar situations.

Quote
Simply calling a procedures for assignment and concatenation, even with a shortstrings, removes all that burden of the expression evaluation.
The ( + ) operator isn't any good for defensive programming, something that should be practiced from all embedded programmers. I avoid using it even on a PC (+ have also other side effects that I'm not happy with).
This is good advice.  I am used to using + for string concatenation, so would like to explore how much one can improve the situation.  I still need to digest PascalDragon's comment about using PShortString, perhaps a general fix is not too difficult :o...
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: PascalDragon on October 07, 2022, 07:29:27 am
It might be better to extend fpc_shortstr_concat (and maybe also fpc_shortstr_concat_multi) by a parameter that contains the length of the target variable and use a PShortString instead of a reference to a ShortString. This way the assignment can be done better in the general way.

It's still unclear where shortstring- as distinct from string[something] is coming from.

I don't get why that's unclear. The function needs to work with any kind of ShortString and the only one that covers that is ShortString aka String[255] itself.

Quote
Simply calling a procedures for assignment and concatenation, even with a shortstrings, removes all that burden of the expression evaluation.
The ( + ) operator isn't any good for defensive programming, something that should be practiced from all embedded programmers. I avoid using it even on a PC (+ have also other side effects that I'm not happy with).
This is good advice.  I am used to using + for string concatenation, so would like to explore how much one can improve the situation.  I still need to digest PascalDragon's comment about using PShortString, perhaps a general fix is not too difficult :o...

Just checked again and remembered that the compiler allows to pass a String[X] (with X < 255) to a var parameter of type ShortString so you wouldn't need to change that. But you as I had written you'd need to add the maximum length of the destination string as that gets lost then:

Code: Pascal  [Select][+][-]
  1. program tshortstr;
  2.  
  3. procedure Test(var s: ShortString);
  4. begin
  5.   Writeln(Length(s), ' ', High(s));
  6. end;
  7.  
  8. var
  9.   s: String[20];
  10. begin
  11.   Test(s); // will print “0 255”
  12. end.

With the destination length you should be able to always avoid the passing of a temporary variable (except that's a given due to how the expression works, but that shouldn't bother you).
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: MarkMLl on October 07, 2022, 09:23:19 am
I don't get why that's unclear. The function needs to work with any kind of ShortString and the only one that covers that is ShortString aka String[255] itself.

But the destination /isn't/ a string[255], it's got a known maximum length which has to be respected.

MarkMLl
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: 440bx on October 07, 2022, 10:54:20 am
Just checked again and remembered that the compiler allows to pass a String[X] (with X < 255) to a var parameter of type ShortString so you wouldn't need to change that. But you as I had written you'd need to add the maximum length of the destination string as that gets lost then:
That doesn't sound right.  "var" parameters have to match exactly for good reason and one of them is not allowing passing a string[10] (or whatever other size) for a shortstring type which is supposed to be string[255].

Here is a slightly modified version of your sample that shows the problem(s) with allowing that.
Code: Pascal  [Select][+][-]
  1. {$APPTYPE CONSOLE}
  2.  
  3.  
  4. program _ShortStrings;
  5.  
  6.  
  7. procedure Test(var s: ShortString);
  8. begin
  9.   Writeln(Length(s), ' ', High(s));
  10.  
  11.   s := 'a very long string just to see what happens when a string longer ' +
  12.        'than what fits is used.';
  13. end;
  14.  
  15. var
  16.   s1 : string[10];
  17.   s2 : string[10];
  18.   s3 : string[10];
  19.  
  20. begin
  21.   writeln;
  22.  
  23.   writeln('s1: ', s1);
  24.   writeln('s2: ', s2);
  25.   writeln('s3: ', s3);
  26.  
  27.  
  28.   Test(s1);
  29.  
  30.   writeln('s1: ', s1);
  31.   writeln('s2: ', s2);
  32.   writeln('s3: ', s3);
  33.  
  34.   readln;
  35. end.                  
Test assumes that shortstring holds 255 characters, therefore it places that long string into s1 but, by doing that, its messing up strings s2 and s3.  That should not be happening and the reason it's happening is because the compiler decided to treat string[10] as if it were string[255].  IOW, the compiler can't have its string and eat it too.

ETA:

changed "vary" (typo) to "very" in the string constant.
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: ccrause on October 07, 2022, 02:33:39 pm
Just checked again and remembered that the compiler allows to pass a String[X] (with X < 255) to a var parameter of type ShortString so you wouldn't need to change that. But you as I had written you'd need to add the maximum length of the destination string as that gets lost then:
That doesn't sound right.  "var" parameters have to match exactly for good reason and one of them is not allowing passing a string[10] (or whatever other size) for a shortstring type which is supposed to be string[255].
Strict parameter enforcement would prevent the problem (bug?), but would also prevent the current ease of mixing different sized shortstrings.  The ease of use is specifically mentioned in the documentation (https://www.freepascal.org/docs-html/ref/refsu9.html): Whatever the actual type, single byte strings can be used interchangeably. The compiler always takes care of the necessary type conversions.

PascalDragon's comment on adding the shortstring size when a shortstring parameter is declared as var seems like the viable fix.  Of course working with short shortstrings is probably more prevalent on embedded type targets with low memory, so adding an extra hidden parameter to the call overhead is not great - but seems unavoidable.
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: 440bx on October 07, 2022, 04:22:26 pm
I just tried it with Delphi 2 and Delphi 2 accepts passing a string[10] for a shortstring BUT, when the program is run, the Test function knows that only 10 characters fit in the string.

IOW, when high(s) is taken, the result is _not_ 255, it is 10, consequently the string constant moved into the parameter is truncated thereby leaving s2 and s3 unaffected (which is how it should be.)



Title: Re: [SOLVED] Variable addresses in SRAM
Post by: funlw65 on October 07, 2022, 05:45:23 pm
... but if basic text handling can't be done safely this negates the value of using Pascal- particularly now that C/C++ have, generally speaking, taken on board Wirth's ideas about type checking etc. (as far as allowed by their modus operandi and the amount of legacy code).

MarkMLl

Absolutely! If I go back to using functions for concatenating strings (as in C), then there is no point in using Pascal - better staying in C or JAL. I think FreePascal for embedded must be rewritten. And striped. That means a new project? I don't think it will happen...

BTW, how good is FreePascal at dead code removal? Personally, I think that the Unit system is already a burden for embedded...
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: PascalDragon on October 08, 2022, 10:02:18 am
I don't get why that's unclear. The function needs to work with any kind of ShortString and the only one that covers that is ShortString aka String[255] itself.

But the destination /isn't/ a string[255], it's got a known maximum length which has to be respected.

That's why the compiler passes a temporary ShortString variable and then assigns that to the shorter string. Yes, that might mean that data gets lost then, but that's how short strings work!

I just tried it with Delphi 2 and Delphi 2 accepts passing a string[10] for a shortstring BUT, when the program is run, the Test function knows that only 10 characters fit in the string.

IOW, when high(s) is taken, the result is _not_ 255, it is 10, consequently the string constant moved into the parameter is truncated thereby leaving s2 and s3 unaffected (which is how it should be.)

It turns out that Delphi (and probably also TP) is a cheeky, little bugger: If you have a function or method with a var parameter of type ShortString then it also passes the maximum size of the provided string variable as a hidden parameter. This is only done if the parameter is declared as ShortString, but not for any other String[<N>] even String[255]! Also this is not done for out parameters. Something like this also isn't done if the result is a ShortString.

(Funny sidenote: this also means that methods that use this don't have any parameters listed in the RTTI ::) )

Essentially this means that we have to extend the compiler to handle this correctly and then the ShortString concatenation would handle this transparently as long as the compiler passes the left side of the assignment directly instead of using a temporary. :D

I think FreePascal for embedded must be rewritten. And striped. That means a new project? I don't think it will happen...

You are always free to use your own RTL, but as part of the project we'll only use our main RTL with features disabled, because maintaining the embedded targets only makes sense if we don't have unnecessary maintenance burden for them.

BTW, how good is FreePascal at dead code removal? Personally, I think that the Unit system is already a burden for embedded...

The unit system itself is not a “burden” for Embedded.

As long as you compile the units with -CX and the program with -XX the linker can strip rather well because every function will have its own section and dead code removal by the linker is done by section.
Biggest problem are the various managers that we have. E.g. the memory manager: imagine your code only uses GetMem and FreeMem. This would however still mean that the code for ReallocMem is still linked in as well because it's referenced by the memory manager. This is not something that can be easily solved, though I do have a potential optimization for that in mind, but it will take some time till I find the time to implement it.
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: MarkMLl on October 08, 2022, 10:44:57 am
That's why the compiler passes a temporary ShortString variable and then assigns that to the shorter string. Yes, that might mean that data gets lost then, but that's how short strings work!

I'm sorry, it's working wrong in that case: the size of the destination is known, and even if data loss is expected there's absolutely no need to move more than that number of characters.

And don't remind /me/ that strings get truncated: I've seen a "professional" development environment which truncated every line at 80 chars without warning... and yes, it /was/ written in Pascal.

But the fact that the result is truncated is not relevant here. What /is/ relevant is that the stack has to have the potential of being 256 bytes larger than expected, and that this requirement is multiplied if the overall program uses multiple threads or coroutines. And speaking as a sometime embedded system programmer, that is a grievous fault.

This is fundamental, 1980s stuff. And if it breaks something more recent like RTTI, then it's the more recent stuff that has to be adjusted.

MarkMLl
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: PascalDragon on October 08, 2022, 11:28:07 am
That's why the compiler passes a temporary ShortString variable and then assigns that to the shorter string. Yes, that might mean that data gets lost then, but that's how short strings work!

I'm sorry, it's working wrong in that case: the size of the destination is known, and even if data loss is expected there's absolutely no need to move more than that number of characters.

If it is a plain ShortString parameter then the maximum size is 255, because that's what a ShortString's maximum length is. However in the bug (https://gitlab.com/freepascal.org/fpc/source/-/issues/39944) reported by ccrause FPK highlighted an aspect that neither of us had on the radar: Open Strings (and also Strict String Checking).

Open Strings is exactly what solves this problem in Delphi and which is also supported in FPC: if that switch is enabled ($P+ or {$OpenStrings On}) then if you have a var parameter of type ShortString then the compiler will provide an additional, hidden High parameter which will contain the maximum length of the provided string variable. If that switch is not enabled then there will be no hidden High parameter and High(aArg) will always return 255 for a ShortString variable. The thing is that in Delphi (and presumably also TP) the Open Strings are enabled by default, while in FPC they aren't enabled by default (in any mode). Also the switch is a local one in Delphi, but a global one in FPC (that should be changed).

Also there is the Strict String Checking which is controlled by the $V or $VarStringChecks directive. This controls whether you can pass a String[<N>] to a function declared with a var parameter of type String[<M>] where <N> <> <M>. This is a local switch and by default is is enabled in Delphi and it's also enabled by default in FPC in modes TP and Delphi.

So every passing of a by-reference ShortString here works by design. And yes, that includes potentially doing buffer overflows.

Side note: Delphi applies these switches only to var parameters, but FPC also applies them to out parameters.

But the fact that the result is truncated is not relevant here. What /is/ relevant is that the stack has to have the potential of being 256 bytes larger than expected, and that this requirement is multiplied if the overall program uses multiple threads or coroutines. And speaking as a sometime embedded system programmer, that is a grievous fault.

I'm not saying that the string concatenation shouldn't be improved (after all I did give ccrause feedback to his changes). I'm only saying that everything here works as designed (though one can argue over the default settings of the switches).

This is fundamental, 1980s stuff. And if it breaks something more recent like RTTI, then it's the more recent stuff that has to be adjusted.

That was just some side note I got when testing this in Delphi. FPC correctly generates the RTTI for a method containing an OpenString parameter ;)
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: 440bx on October 08, 2022, 05:29:16 pm
If you have a function or method with a var parameter of type ShortString then it also passes the maximum size of the provided string variable as a hidden parameter. This is only done if the parameter is declared as ShortString, but not for any other String[<N>] even String[255]! Also this is not done for out parameters. Something like this also isn't done if the result is a ShortString.
As @ccrause already mentioned above, it really seems that, in some cases, the extra hidden "max size" parameter to manage the string size correctly is a necessity.
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: nanobit on October 08, 2022, 08:20:08 pm
High(s) is correct if parameter is "const {/var/out} s: OpenString;",
regardless of {$P} directive.
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: PascalDragon on October 09, 2022, 05:58:08 pm
If you have a function or method with a var parameter of type ShortString then it also passes the maximum size of the provided string variable as a hidden parameter. This is only done if the parameter is declared as ShortString, but not for any other String[<N>] even String[255]! Also this is not done for out parameters. Something like this also isn't done if the result is a ShortString.
As @ccrause already mentioned above, it really seems that, in some cases, the extra hidden "max size" parameter to manage the string size correctly is a necessity.

Yes, and these cases are var and out parameters (the later FPC only) with a ShortString parameter if and only if $P / $OpenStrings is enabled. In that case the type of the parameter becomes in fact OpenString (which is an internal type provided by both FPC and Delphi) which in turn leads to a hidden parameter that contains the maximum length of the string variable passed in (if you explicitly declare the parameter as OpenString it will have the additional parameter for any modifier except for ordinary by-value; at least in FPC, in Delphi that's only the case for var parameters of that type).
If $P / $OpenStrings is not enabled - which is the default in FPC (except for mode Delphi since yesterday (https://gitlab.com/freepascal.org/fpc/source/-/commit/188cac3bc6dc666167aacf47fedff1a81d378137) ;) ) - then there simply will be no such parameter. In that case it will depend upon the setting of the $V / $VarStringChecks directive: if it's enabled (default in modes Delphi and TP) then the compiler won't allow you to pass smaller string variables to such functions. Otherwise (which is the default in the other modes) the compiler will allow you to do that and you as a developer need to make sure that you don't pass anything that could lead to a buffer overflow or something like that.
This behaviour itself is fully compatible to how Delphi handles this, only the defaults are different.

Regarding my feedback to ccrause: it turns out that the System unit contains an explicit $P+ in rtl/inc/systemh.inc which means that any of the helper functions take a var or out parameter of type ShortString already contains the correct High value of the parameter. It was simply a matter of implementing ccrause's change correctly and now (https://gitlab.com/freepascal.org/fpc/source/-/commit/02aac653d2599332b70af2cb3433e8f5d76e6400) FPC will handle the example by dseligo better:

It is better to stick to shortstring instead of ansistring or others. On most if not all platforms this reserves just 256 byte or even less if declared properly.
E.g. if you know before hand what the maximum string length is, you can declare a shortstring type with the appropriate size:
Code: Pascal  [Select][+][-]
  1. type Tmystring = string[16];
will occupy just 17 bytes on embedded targets. (one for size)

Yes, I use shortstring with appropriate size and it occupies size + 1. Problem is when strings are concatenated.

Consider this program:
Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. {$mode ObjFPC}
  4.  
  5. var s1: String[10];
  6.  
  7. begin
  8.   s1 := 'test';
  9.   s1 := s1 + '123';
  10. end.

While dseligo used AVR code I'll show the result in x86_64 code (the result will be the same on all platforms as the optimization is done at a higher level):

Before:

Code: ASM  [Select][+][-]
  1. .globl  main
  2. main:
  3. .globl  PASCALMAIN
  4. PASCALMAIN:
  5. .Lc2:
  6. # Temps allocated between rbp-256 and rbp+0
  7. .seh_proc main
  8. # [tshortstr.pp]
  9. # [5] begin
  10.         pushq   %rbp
  11. .seh_pushreg %rbp
  12. .Lc3:
  13.         movq    %rsp,%rbp
  14. .Lc4:
  15.         leaq    -288(%rsp),%rsp
  16. .seh_stackalloc 288
  17. .seh_endprologue
  18.         call    fpc_initializeunits
  19. # [6] s1 := 'test';
  20.         leaq    _$TSHORTSTR$_Ld1(%rip),%rax
  21.         leaq    U_$P$TSHORTSTR_$$_S1(%rip),%rcx
  22.         movq    $10,%rdx
  23.         movq    %rax,%r8
  24.         call    fpc_shortstr_to_shortstr
  25. # [7] s1 := s1 + '123';
  26.         leaq    _$TSHORTSTR$_Ld2(%rip),%r9
  27.         leaq    U_$P$TSHORTSTR_$$_S1(%rip),%r8
  28.         leaq    -256(%rbp),%rcx
  29.         movq    $255,%rdx
  30.         call    fpc_shortstr_concat
  31.         leaq    -256(%rbp),%r8
  32.         leaq    U_$P$TSHORTSTR_$$_S1(%rip),%rcx
  33.         movq    $10,%rdx
  34.         call    fpc_shortstr_to_shortstr
  35. # [8] end.
  36.         call    fpc_do_exit
  37.         ret
  38. .seh_endproc
  39.  

After:

Code: ASM  [Select][+][-]
  1. .globl  main
  2. main:
  3. .globl  PASCALMAIN
  4. PASCALMAIN:
  5. .Lc2:
  6. .seh_proc main
  7. # [tshortstr.pp]
  8. # [5] begin
  9.         pushq   %rbp
  10. .seh_pushreg %rbp
  11. .Lc3:
  12.         movq    %rsp,%rbp
  13. .Lc4:
  14.         leaq    -32(%rsp),%rsp
  15. .seh_stackalloc 32
  16. .seh_endprologue
  17.         call    fpc_initializeunits
  18. # [6] s1 := 'test';
  19.         leaq    _$TSHORTSTR$_Ld1(%rip),%rax
  20.         leaq    U_$P$TSHORTSTR_$$_S1(%rip),%rcx
  21.         movq    $10,%rdx
  22.         movq    %rax,%r8
  23.         call    fpc_shortstr_to_shortstr
  24. # [7] s1 := s1 + '123';
  25.         leaq    _$TSHORTSTR$_Ld2(%rip),%r9
  26.         leaq    U_$P$TSHORTSTR_$$_S1(%rip),%r8
  27.         leaq    U_$P$TSHORTSTR_$$_S1(%rip),%rcx
  28.         movq    $10,%rdx
  29.         call    fpc_shortstr_concat
  30. # [8] end.
  31.         call    fpc_do_exit
  32.         ret
  33. .seh_endproc
  34. .Lc1:
  35.  

Note the highlighted stack allocations and also the missing “Temps allocated” line in the second example.

High(s) is correct if parameter is "const {/var/out} s: OpenString;",
regardless of {$P} directive.

Please note that this is only true in FPC (also for constref). In Delphi this will be only true for var parameters.
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: MarkMLl on October 09, 2022, 06:26:50 pm
... Note the highlighted stack allocations and also the missing “Temps allocated” line in the second example.

Nicely done. Leaving aside all consideration of compiler version and mode for a moment, is there any quick way, e.g. by using a SizeOf(), that something can be put into application code to report that an oversize temporary has been allocated, either at compilation or runtime?

This is much more of a niggle than anything else, but I think that the last few days' discussion has emphasised that it's worth users being moderately familiar with the mechanics of parameter passing and being able to keep an eye on what's going on.

MarkMLl
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: ccrause on October 09, 2022, 08:38:47 pm
Regarding my feedback to ccrause: it turns out that the System unit contains an explicit $P+ in rtl/inc/systemh.inc which means that any of the helper functions take a var or out parameter of type ShortString already contains the correct High value of the parameter. It was simply a matter of implementing ccrause's change correctly and now (https://gitlab.com/freepascal.org/fpc/source/-/commit/02aac653d2599332b70af2cb3433e8f5d76e6400) FPC will handle the example by dseligo better:
Multi string concatenation still has an issue, as illustrated by the following example:
Code: Pascal  [Select][+][-]
  1. var
  2.   s1: string[4] = 'wxyz';
  3.   s2: string[2] = '??';
  4.  
  5. begin
  6.   s1 := s1 + s2 + s1;
  7. end.
This code generates the following assembly:
Code: [Select]
# [20] s1 := s1 + s2 + s1;
ldi r18,lo8(TC_sPsPROJECT1_ss_S1)
ldi r21,hi8(TC_sPsPROJECT1_ss_S1)
ldi r24,lo8(8)
ldi r25,hi8(8)
add r24,r28
adc r25,r29
ldi r22,-1
mov r23,r1
mov r20,r18
call fpc_shortstr_to_shortstr
ldi r18,lo8(8)
ldi r19,hi8(8)
add r18,r28
adc r19,r29
std Y+2,r18
std Y+3,r19
ldi r20,lo8(TC_sPsPROJECT1_ss_S2)
ldi r21,hi8(TC_sPsPROJECT1_ss_S2)
ldi r24,lo8(264)
ldi r25,hi8(264)
add r24,r28
adc r25,r29
ldi r22,-1
mov r23,r1
call fpc_shortstr_to_shortstr
ldi r18,lo8(264)
ldi r19,hi8(264)
add r18,r28
adc r19,r29
std Y+4,r18
std Y+5,r19
ldi r20,lo8(TC_sPsPROJECT1_ss_S1)
ldi r21,hi8(TC_sPsPROJECT1_ss_S1)
ldi r24,lo8(520)
ldi r25,hi8(520)
add r24,r28
adc r25,r29
ldi r22,-1
mov r23,r1
call fpc_shortstr_to_shortstr
ldi r18,lo8(520)
ldi r19,hi8(520)
add r18,r28
adc r19,r29
std Y+6,r18
std Y+7,r19
ldi r20,lo8(2)
ldi r21,hi8(2)
add r20,r28
adc r21,r29
ldi r24,lo8(TC_sPsPROJECT1_ss_S1)
ldi r25,hi8(TC_sPsPROJECT1_ss_S1)
ldi r18,2
mov r19,r1
ldi r22,4
mov r23,r1
call fpc_shortstr_concat_multi
The strings on the RHS are  copied to temporaries before being passed to fpc_shortstr_concat_multi. A bit more digging required...
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: PascalDragon on October 10, 2022, 07:33:57 am
... Note the highlighted stack allocations and also the missing “Temps allocated” line in the second example.

Nicely done. Leaving aside all consideration of compiler version and mode for a moment, is there any quick way, e.g. by using a SizeOf(), that something can be put into application code to report that an oversize temporary has been allocated, either at compilation or runtime?

No, you need to look at the generated assembly code.

The strings on the RHS are  copied to temporaries before being passed to fpc_shortstr_concat_multi. A bit more digging required...

Agreed...  %)
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: MarkMLl on October 10, 2022, 08:59:43 am
No, you need to look at the generated assembly code.

Thanks for that. I need to get back into the habit... the last 30 years have made me soft.

MarkMLl
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: PascalDragon on October 11, 2022, 07:43:02 am
The strings on the RHS are  copied to temporaries before being passed to fpc_shortstr_concat_multi. A bit more digging required...

Should be fixed (https://gitlab.com/freepascal.org/fpc/source/-/commit/bb51ac77dd8ba9ad3512f3552515f5ddcd051d11).
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: ccrause on October 11, 2022, 08:15:10 am
The strings on the RHS are  copied to temporaries before being passed to fpc_shortstr_concat_multi. A bit more digging required...

Should be fixed (https://gitlab.com/freepascal.org/fpc/source/-/commit/bb51ac77dd8ba9ad3512f3552515f5ddcd051d11).
Great, thank you!  The patch works for the couple of cases I tested against.
Title: Re: [SOLVED] Variable addresses in SRAM
Post by: ccrause on October 13, 2022, 08:47:46 am
Code: Pascal  [Select][+][-]
  1. var
  2.   s1: string[10] = 'wxyz';
  3.   s2: string[2] = '??';
  4.  
  5. begin
  6.   s1 := s1 + s2 + s1;
  7. end.
While this discussion (and PascalDragon's efforts) have already greatly improved the RAM usage for the above code snippet, there is still a temporary shortstring used in the RTL procedure fpc_shortstr_concat_multi (https://gitlab.com/freepascal.org/fpc/source/-/blob/eb17e6fd2d85f700250dcbf721dd41d3bc096b55/rtl/inc/generic.inc#L968).  Here is an attempt to rewrite fpc_shortstr_concat_multi to eliminate the use of a temporary shortstring: https://gitlab.com/ccrause/fpc-source/-/commit/52a1e6040485d268c97cef1f5011370066eb31ad

This patch leads to a slightly larger code footprint for AVR, so eagle eyed coders are encouraged to critisize and/or improve this patch.
TinyPortal © 2005-2018