Skip to content

[AVR] No cli for SPWRITE on XMEGA #147210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

tomtor
Copy link
Contributor

@tomtor tomtor commented Jul 6, 2025

From the XMEGA series manual:
To prevent corruption when updating the stack pointer from software,
a write to SPL will automatically disable interrupts
for up to four instructions or until the next I/O memory write.

This will save 6! instructions in every function with an FP on ALL Xmega devices and it
is compatible with gcc code generation for all those devices. It is also not very modern, because the first devices are from 2013. So this is really significant and it is a small change.

@benshi001 Note that I could not find any existing (SPWRITE) unit tests to which this variant could be added.

Copy link

github-actions bot commented Jul 6, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

.addImm(0x3d)
.addReg(SrcLoReg, getKillRegState(SrcIsKill))
.setMIFlags(Flags);
// From the XMEGA series manual:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please give a link to this manual ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8331-8-and-16-bit-AVR-Microcontroller-XMEGA-AU_Manual.pdf page 17 SPL description.

It is in every chip full manual in the section describing SPL

.addReg(SrcHiReg, getKillRegState(SrcIsKill))
.setMIFlags(Flags);

buildMI(MBB, MBBI, AVR::OUTARr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why restore the interrupt state before writing to SPL ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not my code. I wondered myself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, from SEI in the manual:

Sets the Global Interrupt Enable (I) bit in SREG (Status Register). The instruction following SEI will be executed
before any pending interrupts.


} else { // Disable interrupts for older devices (3 extra instructions)

buildMI(MBB, MBBI, AVR::INRdA)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This two branches have common code, if would be better to be

if (not xmega)
  disable interrupt.
common code;
if (not xmega)
  restore interrupt.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second branch is the unmodified original code. I can change it as suggested.

Copy link
Contributor Author

@tomtor tomtor Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SPL and SPH order is also reversed, so I do not think this is possible, clear to read? EDIT: we could switch the order in the original code, that should not matter.

if (STI.getELFArch() >= 102) { // An XMEGA device

buildMI(MBB, MBBI, AVR::OUTARr)
.addImm(0x3d)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to use STI.getIORegSPL() instead of fixed value.

Copy link
Contributor Author

@tomtor tomtor Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original code, could change that

.addReg(SrcLoReg, getKillRegState(SrcIsKill))
.setMIFlags(Flags);

buildMI(MBB, MBBI, AVR::OUTARr)
Copy link
Member

@benshi001 benshi001 Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to check if SPH is available here for devices with a small stack, for example

if (getIORegSPH() >= 0)
  ....

Copy link
Contributor Author

@tomtor tomtor Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original code. EDIT: so the original code is broken for those devices and has always been that way?

// To prevent corruption when updating the stack pointer from software,
// a write to SPL will automatically disable interrupts
// for up to four instructions or until the next I/O memory write.
if (STI.getELFArch() >= 102) { // An XMEGA device
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it applicable to all upper devices? should xmega3 be excluded ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should apply to all

// To prevent corruption when updating the stack pointer from software,
// a write to SPL will automatically disable interrupts
// for up to four instructions or until the next I/O memory write.
if (STI.getELFArch() >= 102) { // An XMEGA device
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it applicable to all upper devices? should xmega3 be excluded ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Xmega3 is included, all new devices have this.

.addImm(0x3d)
.addReg(SrcLoReg, getKillRegState(SrcIsKill))
.setMIFlags(Flags);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A unit test is needed for this change. Please see other tests for pseudo instructions at llvm/test/CodeGen/AVR/pseudo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of those vary architecture?

.addImm(0x3d)
.addReg(SrcLoReg, getKillRegState(SrcIsKill))
.setMIFlags(Flags);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not use git push -f when updating a PR, I need to trace if all of my concerns are fixed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just use push, more than one commits will be squashed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I never used push -f in this PR, but did indeed in earlier ones.

.addImm(0x3d)
.addReg(SrcLoReg, getKillRegState(SrcIsKill))
.setMIFlags(Flags);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just use push, more than one commits will be squashed.

@benshi001
Copy link
Member

benshi001 commented Jul 7, 2025

From the XMEGA series manual: To prevent corruption when updating the stack pointer from software, a write to SPL will automatically disable interrupts for up to four instructions or until the next I/O memory write.

This will save 6! instructions in every function with an FP on ALL Xmega devices and it is compatible with gcc code generation for all those devices. It is also not very modern, because the first devices are from 2013. So this is really significant and it is a small change.

@benshi001 Note that I could not find any existing (SPWRITE) unit tests to which this variant could be added.

You are appreciated to give a link about how gcc handles this, would be better to gcc's source code lines.

@tomtor
Copy link
Contributor Author

tomtor commented Jul 7, 2025

I prefer to not read GCC code in general, but regarding gcc output, compile any C fragment with many locals with gcc with eg -mmcu=attiny85 and -mmcu=attiny402 (an xmega cpu) and compare the prologue and epilogue of the different -S outputs.

extern const char fl[];

long loop()
{
  const char *s = fl;
  long sum = 1;
  long sum2 = 1;
  int sums[10];
  for (long l= sum; l > 0; l--) {
    while (*s++) {
      sum += *s;
      sum2 += *s+1;
      sums[l%10]++;
    }
  }
  for (int i=0; i < sizeof(sums)/sizeof(sums[0]); i++)
    sum += sums[i];
  return sum+sum2;
}

avr-gcc -S -Os loop.c -mmcu=attiny85

loop.s:

        in r28,__SP_L__
        in r29,__SP_H__
        sbiw r28,20
        in __tmp_reg__,__SREG__
        cli
        out __SP_H__,r29
        out __SREG__,__tmp_reg__
        out __SP_L__,r28
/* prologue: function */__

versus with -mmcu=attiny402

        in r28,__SP_L__
        in r29,__SP_H__
        sbiw r28,20
        out __SP_L__,r28
        out __SP_H__,r29
/* prologue: function */

Note that llvm allways generates the first variant. The same applies to the epilogue so the total difference is 6 extra instructions.

@tomtor tomtor requested a review from benshi001 July 7, 2025 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants