From 9af535bf98a7cd01db85468c427020969a9b68d1 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 17 Jun 2022 16:55:18 +0200 Subject: [PATCH] avr: add support for recover() You can see that it works with the following command: tinygo run -target=simavr ./testdata/recover.go This also gets the following tests to pass again: go test -run=Build -target=simavr -v Adding support for AVR was a bit more compliated because it's also necessary to save and restore the Y register. --- compiler/defer.go | 22 ++++++++++++++--- compiler/testdata/defer-cortex-m-qemu.ll | 2 +- main_test.go | 1 + src/runtime/arch_386.go | 2 ++ src/runtime/arch_amd64.go | 2 ++ src/runtime/arch_arm.go | 2 ++ src/runtime/arch_arm64.go | 2 ++ src/runtime/arch_avr.go | 2 ++ src/runtime/arch_cortexm.go | 2 ++ src/runtime/arch_tinygoriscv.go | 2 ++ src/runtime/arch_tinygowasm.go | 2 ++ src/runtime/arch_xtensa.go | 2 ++ src/runtime/asm_avr.S | 30 ++++++++++++++++++++++++ src/runtime/panic.go | 11 +++++---- 14 files changed, 75 insertions(+), 9 deletions(-) diff --git a/compiler/defer.go b/compiler/defer.go index 6c69bd51..ee5ce07f 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -31,7 +31,7 @@ func (b *builder) supportsRecover() bool { // proposal of WebAssembly: // https://github.com/WebAssembly/exception-handling return false - case "avr", "riscv64", "xtensa": + case "riscv64", "xtensa": // TODO: add support for these architectures return false default: @@ -116,8 +116,8 @@ func (b *builder) createInvokeCheckpoint() { // * The return value (eax, rax, r0, etc) is set to zero in the inline // assembly but set to an unspecified non-zero value when jumping using // a longjmp. - asmType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.deferFrame.Type()}, false) var asmString, constraints string + resultType := b.uintptrType switch b.archFamily() { case "i386": asmString = ` @@ -163,6 +163,21 @@ mov x0, #0 ` constraints = "={x0},{x1},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x18},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{fp},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{q16},~{q17},~{q18},~{q19},~{q20},~{q21},~{q22},~{q23},~{q24},~{q25},~{q26},~{q27},~{q28},~{q29},~{q30},~{nzcv},~{ffr},~{vg},~{memory}" // TODO: SVE registers, which we don't use in TinyGo at the moment. + case "avr": + // Note: the Y register (R28:R29) is a fixed register and therefore + // needs to be saved manually. TODO: do this only once per function with + // a defer frame, not for every call. + resultType = b.ctx.Int8Type() + asmString = ` +ldi r24, pm_lo8(1f) +ldi r25, pm_hi8(1f) +std z+2, r24 +std z+3, r25 +std z+4, r28 +std z+5, r29 +ldi r24, 0 +1:` + constraints = "={r24},z,~{r0},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{r13},~{r14},~{r15},~{r16},~{r17},~{r18},~{r19},~{r20},~{r21},~{r22},~{r23},~{r25},~{r26},~{r27}" case "riscv32": asmString = ` la a2, 1f @@ -174,10 +189,11 @@ li a0, 0 // This case should have been handled by b.supportsRecover(). b.addError(b.fn.Pos(), "unknown architecture for defer: "+b.archFamily()) } + asmType := llvm.FunctionType(resultType, []llvm.Type{b.deferFrame.Type()}, false) asm := llvm.InlineAsm(asmType, asmString, constraints, false, false, 0, false) result := b.CreateCall(asm, []llvm.Value{b.deferFrame}, "setjmp") result.AddCallSiteAttribute(-1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("returns_twice"), 0)) - isZero := b.CreateICmp(llvm.IntEQ, result, llvm.ConstInt(b.uintptrType, 0, false), "setjmp.result") + isZero := b.CreateICmp(llvm.IntEQ, result, llvm.ConstInt(resultType, 0, false), "setjmp.result") continueBB := b.insertBasicBlock("") b.CreateCondBr(isZero, continueBB, b.landingpad) b.SetInsertPointAtEnd(continueBB) diff --git a/compiler/testdata/defer-cortex-m-qemu.ll b/compiler/testdata/defer-cortex-m-qemu.ll index 9c6ddab3..f604f377 100644 --- a/compiler/testdata/defer-cortex-m-qemu.ll +++ b/compiler/testdata/defer-cortex-m-qemu.ll @@ -4,7 +4,7 @@ target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" target triple = "thumbv7m-unknown-unknown-eabi" %runtime._defer = type { i32, %runtime._defer* } -%runtime.deferFrame = type { i8*, i8*, %runtime.deferFrame*, i1, %runtime._interface } +%runtime.deferFrame = type { i8*, i8*, [0 x i8*], %runtime.deferFrame*, i1, %runtime._interface } %runtime._interface = type { i32, i8* } declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) #0 diff --git a/main_test.go b/main_test.go index c069290d..f620c77d 100644 --- a/main_test.go +++ b/main_test.go @@ -349,6 +349,7 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c actual = bytes.Replace(actual, []byte{0x1b, '[', '3', '2', 'm'}, nil, -1) actual = bytes.Replace(actual, []byte{0x1b, '[', '0', 'm'}, nil, -1) actual = bytes.Replace(actual, []byte{'.', '.', '\n'}, []byte{'\n'}, -1) + actual = bytes.Replace(actual, []byte{'\n', '.', '\n'}, []byte{'\n', '\n'}, -1) } if name == "testing.go" { // Strip actual time. diff --git a/src/runtime/arch_386.go b/src/runtime/arch_386.go index d0593549..14eda3c5 100644 --- a/src/runtime/arch_386.go +++ b/src/runtime/arch_386.go @@ -5,6 +5,8 @@ const GOARCH = "386" // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 32 +const deferExtraRegs = 0 + // 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 11685dc5..b9ac5104 100644 --- a/src/runtime/arch_amd64.go +++ b/src/runtime/arch_amd64.go @@ -5,6 +5,8 @@ const GOARCH = "amd64" // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 64 +const deferExtraRegs = 0 + // 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 679840f4..3b0ff679 100644 --- a/src/runtime/arch_arm.go +++ b/src/runtime/arch_arm.go @@ -8,6 +8,8 @@ const GOARCH = "arm" // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 32 +const deferExtraRegs = 0 + // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 3) &^ 3 diff --git a/src/runtime/arch_arm64.go b/src/runtime/arch_arm64.go index 500aa844..373be884 100644 --- a/src/runtime/arch_arm64.go +++ b/src/runtime/arch_arm64.go @@ -5,6 +5,8 @@ const GOARCH = "arm64" // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 64 +const deferExtraRegs = 0 + // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 7) &^ 7 diff --git a/src/runtime/arch_avr.go b/src/runtime/arch_avr.go index 499db796..a9388ce9 100644 --- a/src/runtime/arch_avr.go +++ b/src/runtime/arch_avr.go @@ -10,6 +10,8 @@ const GOARCH = "arm" // avr pretends to be arm // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 8 +const deferExtraRegs = 1 // the frame pointer (Y register) also needs to be stored + // 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 fbc4427c..de0844da 100644 --- a/src/runtime/arch_cortexm.go +++ b/src/runtime/arch_cortexm.go @@ -12,6 +12,8 @@ const GOARCH = "arm" // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 32 +const deferExtraRegs = 0 + // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 3) &^ 3 diff --git a/src/runtime/arch_tinygoriscv.go b/src/runtime/arch_tinygoriscv.go index d4ec961b..c20e0fdf 100644 --- a/src/runtime/arch_tinygoriscv.go +++ b/src/runtime/arch_tinygoriscv.go @@ -5,6 +5,8 @@ package runtime import "device/riscv" +const deferExtraRegs = 0 + func getCurrentStackPointer() uintptr { return uintptr(stacksave()) } diff --git a/src/runtime/arch_tinygowasm.go b/src/runtime/arch_tinygowasm.go index e370b629..e744b799 100644 --- a/src/runtime/arch_tinygowasm.go +++ b/src/runtime/arch_tinygowasm.go @@ -12,6 +12,8 @@ const GOARCH = "wasm" // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 32 +const deferExtraRegs = 0 + //go:extern __heap_base var heapStartSymbol [0]byte diff --git a/src/runtime/arch_xtensa.go b/src/runtime/arch_xtensa.go index c9598922..b3926286 100644 --- a/src/runtime/arch_xtensa.go +++ b/src/runtime/arch_xtensa.go @@ -8,6 +8,8 @@ const GOARCH = "arm" // xtensa pretends to be arm // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 32 +const deferExtraRegs = 0 + // Align on a word boundary. func align(ptr uintptr) uintptr { return (ptr + 3) &^ 3 diff --git a/src/runtime/asm_avr.S b/src/runtime/asm_avr.S index d0fcd3f5..23a3d496 100644 --- a/src/runtime/asm_avr.S +++ b/src/runtime/asm_avr.S @@ -50,3 +50,33 @@ tinygo_scanCurrentStack: pop r17 pop r28 // Y pop r29 // Y + + +.section .text.tinygo_longjmp +.global tinygo_longjmp +tinygo_longjmp: + ; Move the *DeferFrame pointer to the X register. + mov r26, r24 + mov r27, r25 + + ; Load the stack pointer + ld r24, X+ + ld r25, X+ + + ; Switch to the given stack pointer. + in r0, 0x3f ; SREG + cli ; disable interrupts + out 0x3d, r24 ; SPL + out 0x3f, r0 ; re-enable interrupts (with one instruction delay) + out 0x3e, r25 ; SPH + + ; Load the new PC + ld r30, X+ + ld r31, X+ + + ; Load the new Y register + ld r28, X+ + ld r29, X+ + + ; Jump to the PC (stored in the Z register) + icall diff --git a/src/runtime/panic.go b/src/runtime/panic.go index c9914527..622026ff 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -25,11 +25,12 @@ func supportsRecover() bool // The compiler knows about the JumpPC struct offset, so it should not be moved // without also updating compiler/defer.go. type deferFrame struct { - JumpSP unsafe.Pointer // stack pointer to return to - JumpPC unsafe.Pointer // pc to return to - Previous *deferFrame // previous recover buffer pointer - Panicking bool // true iff this defer frame is panicking - PanicValue interface{} // panic value, might be nil for panic(nil) for example + JumpSP unsafe.Pointer // stack pointer to return to + JumpPC unsafe.Pointer // pc to return to + ExtraRegs [deferExtraRegs]unsafe.Pointer // extra registers (depending on the architecture) + Previous *deferFrame // previous recover buffer pointer + Panicking bool // true iff this defer frame is panicking + PanicValue interface{} // panic value, might be nil for panic(nil) for example } // Builtin function panic(msg), used as a compiler intrinsic.