diff --git a/builder/picolibc.go b/builder/picolibc.go index f6712a5cd5..5ceda5c294 100644 --- a/builder/picolibc.go +++ b/builder/picolibc.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" + "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" ) @@ -19,8 +20,9 @@ var libPicolibc = Library{ return f.Close() }, cflags: func(target, headerPath string) []string { - picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc") - return []string{ + root := goenv.Get("TINYGOROOT") + picolibcDir := filepath.Join(root, "lib/picolibc") + flags := []string{ "-Werror", "-Wall", "-std=gnu11", @@ -39,10 +41,22 @@ var libPicolibc = Library{ "-I" + picolibcDir + "/libm/common", "-I" + headerPath, } + if compileopts.CanonicalArchName(target) == "xtensa" { + flags = append(flags, + "-I"+filepath.Join(root, "lib/xtensa/include"), + "-I"+picolibcDir+"/libc/machine/xtensa", + "-D_XTENSA_HAVE_CONFIG_CORE_ISA_H", + ) + } + return flags }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc") }, librarySources: func(target string, _ bool) ([]string, error) { - return append([]string(nil), picolibcSources...), nil + sources := append([]string(nil), picolibcSources...) + if compileopts.CanonicalArchName(target) == "xtensa" { + sources = append(sources, "libc/machine/xtensa/setjmp.S") + } + return sources, nil }, } diff --git a/compileopts/config.go b/compileopts/config.go index d9a8ce2c00..777cb782ea 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -25,7 +25,7 @@ import ( var libVersions = map[string]int{ "musl": 3, "bdwgc": 2, - "picolibc": 1, + "picolibc": 2, "wasmbuiltins": 1, } diff --git a/compiler/defer.go b/compiler/defer.go index f8078f6b52..d263720af4 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -32,9 +32,6 @@ func (b *builder) supportsRecover() bool { // proposal of WebAssembly: // https://github.com/WebAssembly/exception-handling return false - case "xtensa": - // TODO: add support for these architectures - return false default: return true } @@ -109,11 +106,21 @@ func (b *builder) createLandingPad() { b.CreateBr(b.blockInfo[b.fn.Recover.Index].entry) } -// Create a checkpoint (similar to setjmp). This emits inline assembly that -// stores the current program counter inside the ptr address (actually -// ptr+sizeof(ptr)) and then returns a boolean indicating whether this is the -// normal flow (false) or we jumped here from somewhere else (true). +// Create a checkpoint (similar to setjmp). It returns whether execution is +// continuing normally instead of resuming after a longjmp. func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value { + if b.archFamily() == "xtensa" { + fnType := llvm.FunctionType(b.ctx.Int32Type(), []llvm.Type{b.dataPtrType}, false) + fn := b.mod.NamedFunction("setjmp") + if fn.IsNil() { + fn = llvm.AddFunction(b.mod, "setjmp", fnType) + fn.AddFunctionAttr(b.ctx.CreateEnumAttribute(llvm.AttributeKindID("returns_twice"), 0)) + } + result := b.CreateCall(fnType, fn, []llvm.Value{ptr}, "setjmp") + result.AddCallSiteAttribute(-1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("returns_twice"), 0)) + return b.CreateICmp(llvm.IntEQ, result, llvm.ConstInt(b.ctx.Int32Type(), 0, false), "setjmp.result") + } + // Construct inline assembly equivalents of setjmp. // The assembly works as follows: // * Registers are either clobbered or, on 386, saved for longjmp to diff --git a/lib/xtensa/include/xtensa/config/core-isa.h b/lib/xtensa/include/xtensa/config/core-isa.h new file mode 100644 index 0000000000..ceed83ed62 --- /dev/null +++ b/lib/xtensa/include/xtensa/config/core-isa.h @@ -0,0 +1,18 @@ +#ifndef TINYGO_XTENSA_CONFIG_CORE_ISA_H +#define TINYGO_XTENSA_CONFIG_CORE_ISA_H + +#ifdef __XTENSA_WINDOWED_ABI__ +#define XCHAL_HAVE_WINDOWED 1 +#else +#define XCHAL_HAVE_WINDOWED 0 +#endif + +#ifdef __XTENSA_EB__ +#define XCHAL_HAVE_BE 1 +#else +#define XCHAL_HAVE_BE 0 +#endif + +#define XCHAL_MAYHAVE_ERRATUM_XEA1KWIN 1 + +#endif diff --git a/main_test.go b/main_test.go index 07c531a392..02c7da5199 100644 --- a/main_test.go +++ b/main_test.go @@ -295,7 +295,12 @@ func TestESP32QEMU(t *testing.T) { if !regexp.MustCompile(`(?m)^esp32\s`).Match(machines) { t.Skip("qemu-system-xtensa does not support the ESP32 machine") } - runTest("print.go", options, t, nil, nil) + for _, name := range []string{"print.go", "recover.go"} { + t.Run(name, func(t *testing.T) { + t.Parallel() + runTest(name, options, t, nil, nil) + }) + } } func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { diff --git a/make/release.mk b/make/release.mk index 23724e1d98..bf2e0f09af 100644 --- a/make/release.mk +++ b/make/release.mk @@ -15,12 +15,14 @@ build/release: tinygo gen-device $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binary @mkdir -p build/release/tinygo/lib/musl/src @mkdir -p build/release/tinygo/lib/nrfx @mkdir -p build/release/tinygo/lib/picolibc/libc + @mkdir -p build/release/tinygo/lib/picolibc/libc/machine @mkdir -p build/release/tinygo/lib/picolibc/libm @mkdir -p build/release/tinygo/lib/wasi-libc/dlmalloc @mkdir -p build/release/tinygo/lib/wasi-libc/libc-bottom-half @mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch @mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @mkdir -p build/release/tinygo/lib/wasi-cli/ + @mkdir -p build/release/tinygo/lib/xtensa @echo copying source files @cp -p build/tinygo$(EXE) build/release/tinygo/bin ifneq ($(USE_SYSTEM_BINARYEN),1) @@ -80,15 +82,16 @@ endif @cp -rp lib/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults @cp -rp lib/mingw-w64/mingw-w64-headers/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers @cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx - @cp -rp lib/picolibc/libc/ctype build/release/tinygo/lib/picolibc/libc - @cp -rp lib/picolibc/libc/include build/release/tinygo/lib/picolibc/libc - @cp -rp lib/picolibc/libc/locale build/release/tinygo/lib/picolibc/libc - @cp -rp lib/picolibc/libc/stdlib build/release/tinygo/lib/picolibc/libc - @cp -rp lib/picolibc/libc/string build/release/tinygo/lib/picolibc/libc - @cp -rp lib/picolibc/libc/stdio build/release/tinygo/lib/picolibc/libc - @cp -rp lib/picolibc/libm/common build/release/tinygo/lib/picolibc/libm - @cp -rp lib/picolibc/libm/math build/release/tinygo/lib/picolibc/libm - @cp -rp lib/picolibc-stdio.c build/release/tinygo/lib + @cp -rp lib/picolibc/libc/ctype build/release/tinygo/lib/picolibc/libc + @cp -rp lib/picolibc/libc/include build/release/tinygo/lib/picolibc/libc + @cp -rp lib/picolibc/libc/locale build/release/tinygo/lib/picolibc/libc + @cp -rp lib/picolibc/libc/machine/xtensa build/release/tinygo/lib/picolibc/libc/machine + @cp -rp lib/picolibc/libc/stdlib build/release/tinygo/lib/picolibc/libc + @cp -rp lib/picolibc/libc/string build/release/tinygo/lib/picolibc/libc + @cp -rp lib/picolibc/libc/stdio build/release/tinygo/lib/picolibc/libc + @cp -rp lib/picolibc/libm/common build/release/tinygo/lib/picolibc/libm + @cp -rp lib/picolibc/libm/math build/release/tinygo/lib/picolibc/libm + @cp -rp lib/picolibc-stdio.c build/release/tinygo/lib @cp -rp lib/wasi-libc/dlmalloc/src build/release/tinygo/lib/wasi-libc/dlmalloc @cp -rp lib/wasi-libc/libc-bottom-half/cloudlibc build/release/tinygo/lib/wasi-libc/libc-bottom-half @cp -rp lib/wasi-libc/libc-bottom-half/headers build/release/tinygo/lib/wasi-libc/libc-bottom-half @@ -121,6 +124,7 @@ endif @cp -rp lib/wasi-libc/libc-top-half/musl/src/unistd build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/sources build/release/tinygo/lib/wasi-libc/libc-top-half @cp -rp lib/wasi-cli/wit build/release/tinygo/lib/wasi-cli/wit + @cp -rp lib/xtensa/include build/release/tinygo/lib/xtensa @cp -rp ${LLVM_PROJECTDIR}/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins @cp -rp ${LLVM_PROJECTDIR}/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins @cp -rp src build/release/tinygo/src diff --git a/src/machine/machine_esp32.go b/src/machine/machine_esp32.go index 8f6ddcc018..f9415fa3a7 100644 --- a/src/machine/machine_esp32.go +++ b/src/machine/machine_esp32.go @@ -478,7 +478,7 @@ var ( rtsctsSignal: 199, } - onceUart = sync.Once{} + uartInterruptConfigured bool ) // CPU interrupt line used for all UART peripherals. @@ -588,11 +588,13 @@ func (uart *UART) configureInterrupt() { } // Register the ISR only once (shared across all UARTs on the same CPU int). - // interrupt.New is a compiler intrinsic and requires a plain (non-capturing) - // handler function, so we use a named package-level function. - onceUart.Do(func() { + // Avoid sync.Once here because serial is initialized before a task exists. + state := interrupt.Disable() + if !uartInterruptConfigured { _ = interrupt.New(cpuInterruptFromUART, handleUARTInterrupt).Enable() - }) + uartInterruptConfigured = true + } + interrupt.Restore(state) } // handleUARTInterrupt is the shared UART interrupt handler. It must be a plain diff --git a/src/runtime/arch_xtensa.go b/src/runtime/arch_xtensa.go index f3192e0000..dec6afe8fc 100644 --- a/src/runtime/arch_xtensa.go +++ b/src/runtime/arch_xtensa.go @@ -11,7 +11,9 @@ const zeroSizeAllocPtr uintptr = 16 // part of early flash: partition table, etc // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 32 -const deferExtraRegs = 0 +// Xtensa's windowed ABI uses a 17-word jmp_buf. JumpSP and JumpPC provide the +// first two words. +const deferExtraRegs = 15 const callInstSize = 3 // "callx0 someFunction" (and similar) is 3 bytes diff --git a/src/runtime/asm_xtensa.S b/src/runtime/asm_xtensa.S new file mode 100644 index 0000000000..352fb32a44 --- /dev/null +++ b/src/runtime/asm_xtensa.S @@ -0,0 +1,34 @@ +#ifdef __XTENSA_WINDOWED_ABI__ +.section .text.__xtensa_libgcc_window_spill,"ax",@progbits +.global __xtensa_libgcc_window_spill +.type __xtensa_libgcc_window_spill, %function +__xtensa_libgcc_window_spill: + entry sp, 16 + movi a6, 15 + call4 .Lspill_windows + retw +.size __xtensa_libgcc_window_spill, .-__xtensa_libgcc_window_spill + +.balign 4 +.Lspill_windows: + entry sp, 16 + beqz a2, .Lspill_done + addi a2, a2, -1 + mov a6, a2 + call4 .Lspill_windows +.Lspill_done: + retw +#endif + +.section .text.tinygo_longjmp,"ax",@progbits +.global tinygo_longjmp +.type tinygo_longjmp, %function +tinygo_longjmp: +#ifdef __XTENSA_WINDOWED_ABI__ + // longjmp's entry rotates a10/a11 into its a2/a3 arguments. + movi a11, 1 +#else + movi a3, 1 +#endif + j longjmp +.size tinygo_longjmp, .-tinygo_longjmp diff --git a/targets/xtensa.json b/targets/xtensa.json index 893a3eb759..4e9f330b11 100644 --- a/targets/xtensa.json +++ b/targets/xtensa.json @@ -14,5 +14,6 @@ ], "ldflags": [ "--gc-sections" - ] + ], + "extra-files": ["src/runtime/asm_xtensa.S"] }