Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 19 additions & 14 deletions src/internal/task/task_stack_esp32.S
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//go:build tinygo

// See Xtensa ISA Reference Manual for details.
// https://www.cadence.com/content/dam/cadence-www/global/en_US/documents/tools/silicon-solutions/compute-ip/isa-summary.pdf

.section .text.tinygo_startTask,"ax",@progbits
.global tinygo_startTask
.type tinygo_startTask, %function
Expand Down Expand Up @@ -68,14 +71,12 @@ tinygo_swapTask:

// After the recursive spill returns, the physical register file still
// has WindowStart bits set for the spill helper frames.
// We will clear WindowStart completely (to 0) right before the retw.n
// below, after the stack switch is done. This prevents stale overflow
// when the new goroutine's calls rotate back into these panes.
// These are dropped below, once the stack switch is done.

// Restore interrupts.
l32i a4, sp, 4 // reload saved PS
wsr.ps a4
rsync
// Mask interrupts until WindowStart matches the new stack, or one lands in
// the gap and clobbers the a0-a3 that retw.n reloads.
// Xtensa ISA Reference Manual, RETW, section 8.3.253, p.565.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having a hard time finding this reference. the ISA ref manual I have comes from here:https://0x04.net/~mwk/doc/xtensa.pdf#page=622.08. There are not 253 subsubsections. Page 565 talks of XORB instruction which does not seem to be the same thing this references. Please provide a source for the edition of the manual you used

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:https://0x04.

Next time try the official docs maybe? I am not sure where that website obtained its PDF from

The ESP32 processor architecture (Xtensa) is from Tensilica (now called Cadence).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a link to the official ISA docs and updated the PR accordingly.

l32i a4, sp, 4 // reload saved PS, restored below

// At this point, the following is true:
// WindowStart == 1 << WindowBase
Expand All @@ -97,16 +98,20 @@ tinygo_swapTask:
// register also stores the parent register window.
l32i.n a0, sp, 0

// Clear ALL WindowStart bits. With all windows spilled to the stack,
// we must ensure no stale WS bits remain: the retw.n below will trigger
// underflow4 to load the new goroutine's registers from the new stack
// (which sets the appropriate WS bit via rfwu). Any stale WS bits
// (from spill helpers or the old goroutine) would cause spurious
// overflows of garbage register values into memory.
movi a5, 0
// Drop every WindowStart bit but this window's: stale bits overflow garbage,
// and an all-zero WindowStart marks the running frame dead.
// Xtensa ISA Reference Manual, WINDOWSTART (SR 73), Table 5-148.
rsr a6, WINDOWBASE
movi a5, 1
ssl a6
sll a5, a5 // a5 = 1 << WindowBase
wsr a5, WINDOWSTART
rsync

// Window state now matches the new stack, so unmask interrupts.
wsr.ps a4
rsync

// Return into the new stack. This instruction will trigger a window
// underflow, reloading the saved registers from the stack.
retw.n
Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/task_stack_esp32.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ package task
// a7: stack frame pointer (optional, normally unused in TinyGo)
// Sources:
// http://cholla.mmto.org/esp8266/xtensa.html
// https://0x04.net/~mwk/doc/xtensa.pdf
// https://www.cadence.com/content/dam/cadence-www/global/en_US/documents/tools/silicon-solutions/compute-ip/isa-summary.pdf

import (
"unsafe"
Expand Down
61 changes: 58 additions & 3 deletions targets/esp32s3-interrupts.S
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@

// Xtensa interrupt/exception vector table for the ESP32-S3.
//
// See Xtensa ISA Reference Manual for details on the vector table layout and exception handling.
// https://www.cadence.com/content/dam/cadence-www/global/en_US/documents/tools/silicon-solutions/compute-ip/isa-summary.pdf
//
// The ESP32-S3 uses an Xtensa LX7 core with the windowed register ABI.
// Interrupt vectors are placed at fixed offsets from the VECBASE special
// register. We only handle level-1 (user) interrupts for now.
Expand Down Expand Up @@ -299,13 +302,26 @@ _handle_kernel_exc:

.global _handle_level1
_handle_level1:
// --- allocate 96-byte exception frame on the interrupted stack ---
// EXCCAUSE 5 (AllocaCause) is a MOVSP window-spill request, not a fault, so
// it is checked before any state is touched: a0 is still in EXCSAVE1.
// Xtensa ISA Reference Manual, Table 4-64 "Exception Causes".
rsr a0, EXCCAUSE
bnei a0, 5, 1f
j _xt_alloca_exc
1:
rsr a0, EXCSAVE1 // restore a0 clobbered by the EXCCAUSE read

// --- allocate the exception frame on the interrupted stack ---
// Layout (offsets from a1 after adjustment):
// 0: a0 4: a1(orig) 8: a2 12: a3 16: a4 20: a5
// 24: a6 28: a7 32: a8 36: a9 40: a10 44: a11
// 48: a12 52: a13 56: a14 60: a15
// 64: SAR 68: EPC1 72: PS
addi a0, a1, -96 // a0 = new frame pointer
// 64: SAR 68: EPC1 72: PS 76: WINDOWBASE 80: WINDOWSTART
//
// 128, not the 84 the layout needs: the low 16 bytes are the interruptee's
// windowed-ABI base save area, as in ESP-IDF's XT_STK_FRMSZ (+0x20).
// https://github.com/espressif/esp-idf/blob/master/components/xtensa/include/xtensa_context.h
addi a0, a1, -128 // a0 = new frame pointer
s32i a1, a0, 4 // save original a1 (SP)
mov a1, a0 // a1 = frame pointer

Expand Down Expand Up @@ -339,6 +355,14 @@ _handle_level1:
// level-1 interrupts.
rsr a2, PS
s32i a2, a1, 72 // save PS (with EXCM=1 set by hardware)

// Captured here, not in the C handler: getting there costs a callx4 and the
// printfs rotate and spill windows, so they would report their own state.
rsr a3, WINDOWBASE
s32i a3, a1, 76
rsr a3, WINDOWSTART
s32i a3, a1, 80

movi a3, ~0x1F // mask: clear INTLEVEL (bits 0-3) + EXCM (bit 4)
and a2, a2, a3
movi a3, 1 // INTLEVEL = 1
Expand Down Expand Up @@ -412,6 +436,37 @@ _handle_level1:

rfe

// Alloca (MOVSP) exception: rotate back to the MOVSP window, fix PS.OWB, then
// fall into the matching window underflow handler, which spills and rfwu's back.
// Ported from ESP-IDF _xt_alloca_exc:
// https://github.com/espressif/esp-idf/blob/master/components/xtensa/xtensa_vectors.S
.balign 4
.global _xt_alloca_exc
_xt_alloca_exc:
rsr a0, WINDOWBASE // grab WINDOWBASE before rotw changes it
rotw -1 // WINDOWBASE goes to a4, new a0-a3 are scratch
rsr a2, PS
extui a3, a2, 8, 4 // a3 = PS.OWB (shift 8, 4 bits)
xor a3, a3, a4 // bits that changed from old to current WB
rsr a4, EXCSAVE1 // restore the interruptee's a0 (now in a4)
slli a3, a3, 8
xor a2, a2, a3 // flip those bits in PS.OWB
wsr a2, PS // PS.OWB now matches the new WINDOWBASE
rsync

// Dispatch on the call size in bits 31:30 of the interruptee's a0; plain `j`
// because _bbci.l's 8-bit range cannot reach the underflow vectors.
// Xtensa ISA Reference Manual, RETW, section 8.3.253, p.565 (a0 encoding).
_bbsi.l a4, 31, 1f
j _window_underflow4
1:
rotw -1 // interruptee's a0 moves to a8
_bbsi.l a8, 30, 2f
j _window_underflow8
2:
rotw -1
j _window_underflow12

// -----------------------------------------------------------------------
// Default weak espradio_user_exception: infinite loop halt.
// Overridden by the strong definition in espradio's isr.c when linked.
Expand Down
Loading
Loading