Skip to content

Commit 0a87846

Browse files
aykevldeadprogram
authored andcommitted
cortexm: optimize code size for the HardFault_Handler
This function is called when a hard fault occurs. Hard faults happen when something really bad happens - like writing to unwritable memory or an unaligned memory access on Cortex-M0. It is not generally possible to recover from these. This commit optimizes the code size overhead of hard fault handling: * It removes the stack overflow checking code. This may seem like a bad thing, but the only thing this could check were stack overflows outside goroutines. In practice, this could only really happen on a stack overflow in the scheduler (unlikely), or in interrupt code (possible, but interrupts are small so still unlikely). Most stack overflows happen in regular goroutines, and weren't caught in the HardFault. * It makes the panic message similar to a regular panic. This has two advantages: * It reduces code size, because the string can be reused between the HardFault handler and the runtime panic function. * Using the same pattern automatically makes `-monitor` print the source address for the hard fault. Not a big benefit as we could trivially add any other pattern but a nice benefit nonetheless. Result: $ tinygo flash -target=microbit -size=short -programmer=openocd -monitor examples/serial code data bss | flash ram 3036 8 2256 | 3044 2264 [...snip] Connected to /dev/ttyACM0. Press Ctrl-C to exit. panic: runtime error at 0x00000344: HardFault with sp=0x200007d0 [tinygo: panic at /home/ayke/src/tinygo/tinygo/src/internal/task/task_stack_cortexm.go:48:4] (This is with #3680 not yet fixed and some local changes to configure the UART so I can actually see the panic). For atsamd21/nrf51 chips this results in a binary size reduction of around 100 bytes. For other Cortex-M chips it's around 24 bytes but I hope to change this in the future because a lot of the fault decoding in runtime_cortexm_hardfault_debug.go should IMHO be done by the TinyGo monitor instead (I estimate that this would save around 800 bytes on these chips).
1 parent 073862e commit 0a87846

File tree

6 files changed

+31
-54
lines changed

6 files changed

+31
-54
lines changed

builder/sizes_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ func TestBinarySize(t *testing.T) {
4343
tests := []sizeTest{
4444
// microcontrollers
4545
{"hifive1b", "examples/echo", 3884, 280, 0, 2268},
46-
{"microbit", "examples/serial", 2924, 388, 8, 2272},
47-
{"wioterminal", "examples/pininterrupt", 7365, 1491, 116, 6912},
46+
{"microbit", "examples/serial", 2852, 360, 8, 2272},
47+
{"wioterminal", "examples/pininterrupt", 7337, 1491, 116, 6912},
4848

4949
// TODO: also check wasm. Right now this is difficult, because
5050
// wasm binaries are run through wasm-opt and therefore the

src/device/arm/cortexm.S

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,6 @@
11
.syntax unified
22
.cfi_sections .debug_frame
33

4-
.section .text.HardFault_Handler
5-
.global HardFault_Handler
6-
.type HardFault_Handler, %function
7-
HardFault_Handler:
8-
.cfi_startproc
9-
// Put the old stack pointer in the first argument, for easy debugging. This
10-
// is especially useful on Cortex-M0, which supports far fewer debug
11-
// facilities.
12-
mov r0, sp
13-
14-
// Load the default stack pointer from address 0 so that we can call normal
15-
// functions again that expect a working stack. However, it will corrupt the
16-
// old stack so the function below must not attempt to recover from this
17-
// fault.
18-
movs r3, #0
19-
ldr r3, [r3]
20-
mov sp, r3
21-
22-
// Continue handling this error in Go.
23-
bl handleHardFault
24-
.cfi_endproc
25-
.size HardFault_Handler, .-HardFault_Handler
26-
274
// This is a convenience function for semihosting support.
285
// At some point, this should be replaced by inline assembly.
296
.section .text.SemihostingCall

src/runtime/panic.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ func runtimePanicAt(addr unsafe.Pointer, msg string) {
9393
trap()
9494
}
9595
if hasReturnAddr {
96+
// Note: the string "panic: runtime error at " is also used in
97+
// runtime_cortexm_hardfault.go. It is kept the same so that the string
98+
// can be deduplicated by the compiler.
9699
printstring("panic: runtime error at ")
97100
printptr(uintptr(addr) - callInstSize)
98101
printstring(": ")

src/runtime/runtime.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ func memzero(ptr unsafe.Pointer, size uintptr)
5757
// the current stack pointer in a platform-independent way.
5858
func stacksave() unsafe.Pointer
5959

60+
// Special LLVM intrinsic that returns the SP register on entry to the calling
61+
// function.
62+
//
63+
//export llvm.sponentry.p0
64+
func llvm_sponentry() unsafe.Pointer
65+
6066
//export strlen
6167
func strlen(ptr unsafe.Pointer) uintptr
6268

src/runtime/runtime_cortexm_hardfault.go

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,25 @@
22

33
package runtime
44

5-
import (
6-
"unsafe"
7-
)
8-
95
// This function is called at HardFault.
10-
// Before this function is called, the stack pointer is reset to the initial
11-
// stack pointer (loaded from address 0x0) and the previous stack pointer is
12-
// passed as an argument to this function. This allows for easy inspection of
13-
// the stack the moment a HardFault occurs, but it means that the stack will be
14-
// corrupted by this function and thus this handler must not attempt to recover.
156
//
167
// For details, see:
178
// https://community.arm.com/developer/ip-products/system/f/embedded-forum/3257/debugging-a-cortex-m0-hard-fault
189
// https://blog.feabhas.com/2013/02/developing-a-generic-hard-fault-handler-for-arm-cortex-m3cortex-m4/
1910
//
20-
//export handleHardFault
21-
func handleHardFault(sp *interruptStack) {
22-
print("fatal error: ")
23-
if uintptr(unsafe.Pointer(sp)) < 0x20000000 {
24-
print("stack overflow")
25-
} else {
26-
// TODO: try to find the cause of the hard fault. Especially on
27-
// Cortex-M3 and higher it is possible to find more detailed information
28-
// in special status registers.
29-
print("HardFault")
30-
}
31-
print(" with sp=", sp)
32-
if uintptr(unsafe.Pointer(&sp.PC)) >= 0x20000000 {
33-
// Only print the PC if it points into memory.
34-
// It may not point into memory during a stack overflow, so check that
35-
// first before accessing the stack.
36-
print(" pc=", sp.PC)
37-
}
11+
//export HardFault_Handler
12+
func HardFault_Handler() {
13+
// Obtain the stack pointer as it was on entry to the HardFault. It contains
14+
// the registers that were pushed by the NVIC and that we can now read back
15+
// to print the PC value at the time of the hard fault, for example.
16+
sp := (*interruptStack)(llvm_sponentry())
17+
18+
// Note: by reusing the string "panic: runtime error at " we save a little
19+
// bit in terms of code size as the string can be deduplicated.
20+
print("panic: runtime error at ", sp.PC, ": HardFault with sp=", sp)
21+
// TODO: try to find the cause of the hard fault. Especially on Cortex-M3
22+
// and higher it is possible to find more detailed information in special
23+
// status registers.
3824
println()
3925
abort()
4026
}

src/runtime/runtime_cortexm_hardfault_debug.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@ const (
1818

1919
// See runtime_cortexm_hardfault.go
2020
//
21-
//go:export handleHardFault
22-
func handleHardFault(sp *interruptStack) {
21+
//export HardFault_Handler
22+
func HardFault_Handler() {
23+
// Obtain the stack pointer as it was on entry to the HardFault. It contains
24+
// the registers that were pushed by the NVIC and that we can now read back
25+
// to print the PC value at the time of the hard fault, for example.
26+
sp := (*interruptStack)(llvm_sponentry())
27+
2328
fault := GetFaultStatus()
2429
spValid := !fault.Bus().ImpreciseDataBusError()
2530

0 commit comments

Comments
 (0)