diff --git a/builder/sizes_test.go b/builder/sizes_test.go index e935b6ec..b987ba19 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -41,9 +41,9 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers - {"hifive1b", "examples/echo", 4556, 272, 0, 2252}, - {"microbit", "examples/serial", 2680, 380, 8, 2256}, - {"wioterminal", "examples/pininterrupt", 6109, 1471, 116, 6816}, + {"hifive1b", "examples/echo", 4612, 276, 0, 2252}, + {"microbit", "examples/serial", 2724, 384, 8, 2256}, + {"wioterminal", "examples/pininterrupt", 6159, 1477, 116, 6816}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/src/runtime/arch-has-returnaddr.go b/src/runtime/arch-has-returnaddr.go new file mode 100644 index 00000000..a356d3c1 --- /dev/null +++ b/src/runtime/arch-has-returnaddr.go @@ -0,0 +1,10 @@ +//go:build !(avr || tinygo.wasm) + +package runtime + +import "unsafe" + +const hasReturnAddr = true + +//export llvm.returnaddress +func returnAddress(level uint32) unsafe.Pointer diff --git a/src/runtime/arch-no-returnaddr.go b/src/runtime/arch-no-returnaddr.go new file mode 100644 index 00000000..881eb2df --- /dev/null +++ b/src/runtime/arch-no-returnaddr.go @@ -0,0 +1,11 @@ +//go:build avr || tinygo.wasm + +package runtime + +import "unsafe" + +const hasReturnAddr = false + +func returnAddress(level uint32) unsafe.Pointer { + return nil +} diff --git a/src/runtime/arch_386.go b/src/runtime/arch_386.go index 14eda3c5..c4dbdf16 100644 --- a/src/runtime/arch_386.go +++ b/src/runtime/arch_386.go @@ -7,6 +7,8 @@ const TargetBits = 32 const deferExtraRegs = 0 +const callInstSize = 5 // "call someFunction" is 5 bytes + // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 15) &^ 15 diff --git a/src/runtime/arch_amd64.go b/src/runtime/arch_amd64.go index b9ac5104..99647601 100644 --- a/src/runtime/arch_amd64.go +++ b/src/runtime/arch_amd64.go @@ -7,6 +7,8 @@ const TargetBits = 64 const deferExtraRegs = 0 +const callInstSize = 5 // "call someFunction" is 5 bytes + // Align a pointer. // Note that some amd64 instructions (like movaps) expect 16-byte aligned // memory, thus the result must be 16-byte aligned. diff --git a/src/runtime/arch_arm.go b/src/runtime/arch_arm.go index 1868b7eb..33a7513b 100644 --- a/src/runtime/arch_arm.go +++ b/src/runtime/arch_arm.go @@ -9,6 +9,8 @@ const TargetBits = 32 const deferExtraRegs = 0 +const callInstSize = 4 // "bl someFunction" is 4 bytes + // Align on the maximum alignment for this platform (double). func align(ptr uintptr) uintptr { return (ptr + 7) &^ 7 diff --git a/src/runtime/arch_arm64.go b/src/runtime/arch_arm64.go index a481fb1a..ac28ab7c 100644 --- a/src/runtime/arch_arm64.go +++ b/src/runtime/arch_arm64.go @@ -7,6 +7,8 @@ const TargetBits = 64 const deferExtraRegs = 0 +const callInstSize = 4 // "bl someFunction" is 4 bytes + // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 15) &^ 15 diff --git a/src/runtime/arch_avr.go b/src/runtime/arch_avr.go index 5fe06605..251d1543 100644 --- a/src/runtime/arch_avr.go +++ b/src/runtime/arch_avr.go @@ -11,6 +11,8 @@ const TargetBits = 8 const deferExtraRegs = 1 // the frame pointer (Y register) also needs to be stored +const callInstSize = 2 // "call" is 4 bytes, "rcall" is 2 bytes + // Align on a word boundary. func align(ptr uintptr) uintptr { // No alignment necessary on the AVR. diff --git a/src/runtime/arch_cortexm.go b/src/runtime/arch_cortexm.go index 45134405..6ea4a483 100644 --- a/src/runtime/arch_cortexm.go +++ b/src/runtime/arch_cortexm.go @@ -13,6 +13,8 @@ const TargetBits = 32 const deferExtraRegs = 0 +const callInstSize = 4 // "bl someFunction" is 4 bytes + // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 7) &^ 7 diff --git a/src/runtime/arch_tinygoriscv.go b/src/runtime/arch_tinygoriscv.go index 904ff5d4..0d376c48 100644 --- a/src/runtime/arch_tinygoriscv.go +++ b/src/runtime/arch_tinygoriscv.go @@ -6,6 +6,8 @@ import "device/riscv" const deferExtraRegs = 0 +const callInstSize = 4 // 8 without relaxation, maybe 4 with relaxation + // RISC-V has a maximum alignment of 16 bytes (both for RV32 and for RV64). // Source: https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf func align(ptr uintptr) uintptr { diff --git a/src/runtime/arch_tinygowasm.go b/src/runtime/arch_tinygowasm.go index ac1a221c..c6e15b27 100644 --- a/src/runtime/arch_tinygowasm.go +++ b/src/runtime/arch_tinygowasm.go @@ -13,6 +13,8 @@ const TargetBits = 32 const deferExtraRegs = 0 +const callInstSize = 1 // unknown and irrelevant (llvm.returnaddress doesn't work), so make something up + //go:extern __heap_base var heapStartSymbol [0]byte diff --git a/src/runtime/arch_xtensa.go b/src/runtime/arch_xtensa.go index 03ca8c65..2ff4bf9e 100644 --- a/src/runtime/arch_xtensa.go +++ b/src/runtime/arch_xtensa.go @@ -9,6 +9,8 @@ const TargetBits = 32 const deferExtraRegs = 0 +const callInstSize = 3 // "callx0 someFunction" (and similar) is 3 bytes + // The largest alignment according to the Xtensa ABI is 8 (long long, double). func align(ptr uintptr) uintptr { return (ptr + 7) &^ 7 diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index 54c3cb91..7aaff89c 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -278,7 +278,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { } if interrupt.In() { - runtimePanic("alloc in interrupt") + runtimePanicAt(returnAddress(0), "alloc in interrupt") } gcTotalAlloc += uint64(size) @@ -318,7 +318,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { // Unfortunately the heap could not be increased. This // happens on baremetal systems for example (where all // available RAM has already been dedicated to the heap). - runtimePanic("out of memory") + runtimePanicAt(returnAddress(0), "out of memory") } } } diff --git a/src/runtime/panic.go b/src/runtime/panic.go index c9c69363..e8a67e4b 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -54,7 +54,19 @@ func _panic(message interface{}) { // Cause a runtime panic, which is (currently) always a string. func runtimePanic(msg string) { - printstring("panic: runtime error: ") + // As long as this function is inined, llvm.returnaddress(0) will return + // something sensible. + runtimePanicAt(returnAddress(0), msg) +} + +func runtimePanicAt(addr unsafe.Pointer, msg string) { + if hasReturnAddr { + printstring("panic: runtime error at ") + printptr(uintptr(addr) - callInstSize) + printstring(": ") + } else { + printstring("panic: runtime error: ") + } println(msg) abort() } @@ -119,52 +131,52 @@ func _recover(useParentFrame bool) interface{} { // Panic when trying to dereference a nil pointer. func nilPanic() { - runtimePanic("nil pointer dereference") + runtimePanicAt(returnAddress(0), "nil pointer dereference") } // Panic when trying to add an entry to a nil map func nilMapPanic() { - runtimePanic("assignment to entry in nil map") + runtimePanicAt(returnAddress(0), "assignment to entry in nil map") } // Panic when trying to acces an array or slice out of bounds. func lookupPanic() { - runtimePanic("index out of range") + runtimePanicAt(returnAddress(0), "index out of range") } // Panic when trying to slice a slice out of bounds. func slicePanic() { - runtimePanic("slice out of range") + runtimePanicAt(returnAddress(0), "slice out of range") } // Panic when trying to convert a slice to an array pointer (Go 1.17+) and the // slice is shorter than the array. func sliceToArrayPointerPanic() { - runtimePanic("slice smaller than array") + runtimePanicAt(returnAddress(0), "slice smaller than array") } // Panic when calling unsafe.Slice() (Go 1.17+) or unsafe.String() (Go 1.20+) // with a len that's too large (which includes if the ptr is nil and len is // nonzero). func unsafeSlicePanic() { - runtimePanic("unsafe.Slice/String: len out of range") + runtimePanicAt(returnAddress(0), "unsafe.Slice/String: len out of range") } // Panic when trying to create a new channel that is too big. func chanMakePanic() { - runtimePanic("new channel is too big") + runtimePanicAt(returnAddress(0), "new channel is too big") } // Panic when a shift value is negative. func negativeShiftPanic() { - runtimePanic("negative shift") + runtimePanicAt(returnAddress(0), "negative shift") } // Panic when there is a divide by zero. func divideByZeroPanic() { - runtimePanic("divide by zero") + runtimePanicAt(returnAddress(0), "divide by zero") } func blockingPanic() { - runtimePanic("trying to do blocking operation in exported function") + runtimePanicAt(returnAddress(0), "trying to do blocking operation in exported function") }