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.
Этот коммит содержится в:
Ayke van Laethem 2022-06-17 16:55:18 +02:00 коммит произвёл Ron Evans
родитель 159f0051bb
коммит 9af535bf98
14 изменённых файлов: 75 добавлений и 9 удалений

Просмотреть файл

@ -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)

2
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

Просмотреть файл

@ -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.

Просмотреть файл

@ -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

Просмотреть файл

@ -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.

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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.

Просмотреть файл

@ -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

Просмотреть файл

@ -5,6 +5,8 @@ package runtime
import "device/riscv"
const deferExtraRegs = 0
func getCurrentStackPointer() uintptr {
return uintptr(stacksave())
}

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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.