Possible Bug - SAMD21 Bare Metal

Hi all,

I've been working with the S4A group for a while and am trying to be more active here. I have been doing some work on some bear metal support or proof of concept for the SAMD21. I have a very messy project that I was not really ready to share but I am having some issues. Talking to @carlos42421 over the weekend he thinks it might be a compiler or tooling issue. I am very much not an expert in the compiler stuff and hesitant to think it's something other than me doing strange or dumb stuff. :joy:

For reference I have this example project here: GitHub - pdshelley/SAMD21E at feature/hardfault · GitHub

This is supposed to be in the same style as the official Embedded Swift example projects here and has the same setup requirements: GitHub - swiftlang/swift-embedded-examples: A collection of example projects using Embedded Swift · GitHub

I'll try to briefly describe the issue here, happy to revise the example, add better steps, or slim it down if needed but I don't want to put hours of work into that if it's not needed.

In short I was working on a proof of concept for SPI, there is a setup function in SPI0.swift called "configure()". This function contains a huge Switch statement that I was using to debug and step through each line of code to figure out where things were breaking during early development. I got things working and was going to remove the Switch and when I simply move a single line from its own case then I get what I believe is a hard fault. There are comments on some of the lines and I can get the compiled files if that's easier to compare or check the LLVM output.

A lot of the lower level code has "@inline(__always)" as generally I believe these lower level register access calls should inline. Another way I was able to "fix" the hard fault issue was just removing the "@inline(__always)". I don't have this easily notated in the code but I can also get that example as well with very minimal effort.

I know all of this is still quite rough and needs refinement. I've been banging my head on the wall for the last few weeks and just don't know enough about how things work at this level and could use some direction or guidance. In the event that there is some strange edge case or bug I would love to help contribute whatever I can to fix it. Thanks.

1 Like

Hi Paul,

Thanks so much for your time and contribution to Swift! Let's see if we can get you moving. Could we ask a little more information to try to get to the bottom of it?

Firstly, could you please tell us which compiler/toolchain you are using to build your code? Have you downloaded one of the nightly build compilers? Could you let us know which build you downloaded? Also if possible could you run swift --version with it so we can be sure we are talking about the right compiler?

Would it be possible to raise a GitHub issue for this? I know it might not turn out to be a bug in the end, so you might be hesitant to raise one until you're sure, but it's probably still easier to track as a GitHub issue, and it means you can do things like add LLVM IR output without overloading this discussion. Contributing | Swift.org

---

And on the bug itself, just to clarify, my understanding is the function in question is on line 66 of this file in your repository: SAMD21E/Sources/Application/SPI0.swift at feature/hardfault · pdshelley/SAMD21E · GitHub . Is that right?

And as I understand what you're saying, in the first version of your code, you had a large switch statement to configure an SPI peripheral on your SAMD21 microcontroller, step by step. It looks like you probably called SPI0.configure() multiple times in your program setup, with it automatically moving to the next step each time, is that right? Then once you had that version working, you were trying to move to a more efficient approach, probably ultimately your idea was that in the final version the user would just call SPI0.configure() once and it would perform all the steps one at a time. Did I understand correctly?

However my reading of what you're saying (and looking at your code comments) is that when you started to move toward the final model, by slowly amalgamating the "configuration steps", at some point the program your compiler was producing stopped working as it used to and started to malfunction?

In particular, my understanding is that the code started to hard fault. I think a really helpful diagnostic would be to get the LLVM IR for the SPI0.configure() function in a known good version and compare it to the LLVM IR for the SPI0.configure() function in the first known bad/hard faulting version. Hopefully from that we can start to see what the issue might be.

I appreciate it's a lot to do, getting the compiler version, logging a bug on GitHub and getting LLVM output, but it should help greatly in people helping you to get unstuck.

And thank you again so much for contributing!

Regards,

Carl

1 Like

Swift Version: main-snapshot-2026-05-17
I had been using main-snapshot-2026-03-16 but updated this morning to main-snapshot-2026-05-17 and re-tested everything. The issue persists.

I will raise a GitHub issue and include the LLVM IR there.

Clarify Bug: Yes, it looks like your understanding is correct. The switch statement loops and runs each case once in order, this was for debugging originally. So combining lines of code into one case will run them in the same loop of the SPI0.configure() function. When moving from running a line of code in its own loop to moving it to run in the same loop this causes the hard fault. I was able to “fix” this hard fault in some places by removing the "@inline(__always)" further down the call stack of that line of code. I’m assuming that this is all the same issue.

1 Like

It certainly sounds like it might be a compiler issue. Adding or removing @inline(__always) should never affect program correctness. That's helpful. Definitely include that information in the GitHub issue if possible I think.

I've raised the issue: @inline(__always) causes a hard fault - Embedded Swift · Issue #89287 · swiftlang/swift · GitHub

I am sure more information will be needed so I'll monitor and try to provide as much support and clarity at possible. I've attached LLVM IR files in a zip folder there.

3 Likes

Hi, thanks for that! I took a look at the LLVM IR zip file. Unless I'm going mad, it has three files in it that are all ELF object files, despite their names being XX.ll ... and yet when I look in the Makefile, it should have made both LLVM IR (.ll) files and object files (.o) ... So I'm a bit puzzled. Any ideas??