Menu

#914 Allow isaddr flag not just for POINTER_SET?

None
open
nobody
None
5
2024-07-09
2024-05-02
No

For historic reasons, writes to pointers are '=' iCodes, with the isaddr flag set on the result. In contrast, pointer reads have their own GET_VALUE_AT_ADDRESS iCode. For consistency, we might want to replace GET_VALUE_AT_ADDRESS by SET_VALUE_AT_ADDRESS.

But what if we do the opposite? Allow the isaddr flag in more cases? I.e. instead of GET_VALUE_AT_ADDRESS, we'd have '=' with the isaddr flag set on the source? With isaddr flag set on both, we'd get a pointer-to-pointer copy, i.e. *dest = *src;, and thus a good implementation of [feature-requests:#913].

Disadvantages:
1) Code generation gets more complex. We should set a clear boundary between stages that need to be able to handle iCodes with isaddr flag, and those that don't.
2) If we decide to introduce the feature, we'd want to do so gradually, so the port struct would need to indicate for which iCodes with which operands the port supports iCodes with the isaddr flag.
3) For the result, one needs to look at the isaddr flag to know if it is read or written when doing transformation on iCode.
4) There are possible complications when a source and result operand overlap.

Advantages:
1) We can make much better use of indirect addressing modes. Many architectures (e.g. z80 and related, hc08 and related, stm8, but not pdk) support indirect or indexed addressing modes. If codegen sees iCodes with the isaddr flag, these can be used efficiently (currently we need to load into a temporary first before doing the actual operation).
2) Better codegen for struct/union: SDCC uses pointers to access these. But with the isaddr flag, a struct/union member can be handled in an operation just like any other. E.g. a member of a struct can be directly used as an operand (assuming a rematerialzed address), just like a non-struct variable is now.
3) Efficient use of bit-set/reset instructions in memory-mapped I/O (e.g. on stm8 this would close the current codegen gap between uint8_t __sfr __at(0x1234) and (*((uint8_t *)(0x1234)))).
4) Many atomic operations can be naturally represented in iCode: The atomic fetch and modify function from stdatomic.c would just become iCodes with isaddr flags, and atomic qualifiers on operands.

P.S.: Regarding advantage 1): This would also reduce register pressure. E.g. in functions like strncmp, we'd no longer need any temporaries to hold *s1 and *s2, so all registers could be used for s1, s2 and n.

Related

Feature Requests: #912
Feature Requests: #913

Discussion

  • Maarten Brock

    Maarten Brock - 2024-05-02

    Personally, I always found the isaddr flag confusing. And I have the idea that some or all of the above (dis)advantages also hold when we drop this flag and introduce SET_VALUE_AT_ADDRESS.
    I like to see that something is a pointer in the specifier by a chain in the type instead of just an etype with some obscure flag set.

     
    • Philipp Klaus Krause

      Well, the naming of the isaddr flag seems a bit confusing. Maybe indir would be a better name, to indicate that an indirect access will happen.

       

      Last edit: Philipp Klaus Krause 2024-05-02
  • Philipp Klaus Krause

    • Description has changed:

    Diff:

    --- old
    +++ new
    @@ -13,3 +13,5 @@
     2) Better codegen for struct/union: SDCC uses pointers to access these. But with the isaddr flag, a struct/union member can be handled in an operation just like any other. E.g. a member of a struct can be directly used as an operand (assuming a rematerialzed address), just like a non-struct variable  is now.
     3) Efficient use of bit-set/reset instructions in memory-mapped I/O (e.g. on stm8 this would close the current codegen gap between `uint8_t __sfr __at(0x1234)` and `(*((uint8_t *)(0x1234)))`).
     4) Many atomic operations can be naturally represented in iCode: The atomic fetch and modify function from stdatomic.c would just become iCodes with isaddr flags, and atomic qualifiers on operands.
    +
    +P.S.: Regarding advantage 1): This would also reduce register pressure. E.g. in functions like `strncmp`, we'd no longer need any temporaries to hold `*s1` and `*s2`, so all registers could be used for `s1`, `s2` and `n`.
    
    • Group: -->
     
    👍
    1
  • Philipp Klaus Krause

    I think that if we do want to implement this, it should be introduced at a late stage (so transformation on the iCode do not have to worry about about indirect access - though that somewhat conflicts with advantage 4)): instead of POINTER_SET have SET_VALUE_AT_ADDRESS, until just before register allocation. Then transform SET_VALUE_AT_ADDRESS to '=', GET_VALUE_AT_ADDRESS to '='.
    Then, where the port supports it, merge these into other iCodes (e.g. merge a former GET_VALUE_AT_ADDRESS into a use of the result, if there is only one).

    Support could be introduced partially and gradually, e.g. for mcs51, we might want to only merge when the pointer read or write is using a pointer to __idata (so we can use @Ri adressing mode), but not for other types of pointers; and we could have restrictions on how many operands could be indirect; for stm8 and z80 we might want to at first only implement support for 8-bit operands.

     
  • Ragozini Arturo

    Ragozini Arturo - 2024-05-02

    I do not have idea of how codegen works, so I'm not able to evaluate the solution.

    What I see is that, at moment, indirect accesses are resolved in elementary opcodes using registers. This leads to generic 8 bit code, not really efficient for the z80 whose most relevant feature is block instructions ( LDI/LDIR/LDD/LDDR, CPI/CPIR/CPD/CPDR and OUTI/OTIR/OUTD/OTDR).
    What I see is that if the compiler keeps the information that the operands are accessed by indirect access until the opcodes are selected, it could choose to resort to block instructions, saving registers and cycles. If you resolve the indirect accesses before selecting the opcodes, you get long sequences of elementary opcodes using registers, where no peephole rule will be able to recover the lost efficiency.

    Probably this could also enable the use of instructions like CP (HL), CP (IX+n) , ADD (HL), ADD (IX+n) , SUB (HL) etc etc

     

    Last edit: Ragozini Arturo 2024-05-02
    • Janko Stamenović

      And my view is that as general approach as possible to avoid having something like this in the iCode before the registers and instructions are decided is a way for the improvements across the targets (the iCode couldn't have been optimal enough):

      ld  c, (hl)
      ld  a, (de)
      ld  b, a
      ld  a, c
      sub a, b
      
       
      • Ragozini Arturo

        Ragozini Arturo - 2024-05-16

        This segment could have been:

        ex de,hl
        ld a,(de)
        sub (hl)
        
         
        👍
        1
  • Ragozini Arturo

    Ragozini Arturo - 2024-05-05

    May I continue to report oddness in the generated code?

     
  • Ragozini Arturo

    Ragozini Arturo - 2024-07-09

    Be aware that for coping two variables, LDI is the best option only for long long (8 bytes) or larger structs. For shorter types, when addresses are known at compile time, the best option is to use HL and direct addressing.
    In particular:

    ld hl,(#addr1)
    ld (#addr2),hl
    

    is the fastest solution.
    It takes 34 cycles and 6 bytes, while the LDI version

    ld hl,#addr1
    ld de,#addr2
    ldi
    ldi
    

    takes 58 cycles and 10 bytes.
    The comparison starts reverting when the data structure is larger than 8 bytes.
    For 8 bytes the first solution costs 136 cycles and 24 bytes
    The one with LDI costs 166 cycles but 22 bytes, while using LDIR gives 212 cycles and 11 bytes.

     

    Last edit: Ragozini Arturo 2024-07-10
  • Ragozini Arturo

    Ragozini Arturo - 2024-07-09

    Everything changes when the source and the destination addresses are computed on fly and are not available at compile time.
    In this case you cannot use the solution with

    ld hl,(#addr1)
    ld (#addr2),hl
    

    and LDI becomes the best option

     

    Last edit: Ragozini Arturo 2024-07-09

Log in to post a comment.