Skip to content
Closed
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
3 changes: 2 additions & 1 deletion compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const tinygoPath = "github.com/tinygo-org/tinygo"
// during TinyGo optimization passes so they have to be marked as external
// linkage until all TinyGo passes have finished.
var functionsUsedInTransforms = []string{
"runtime.wrapMain",
"runtime.alloc",
"runtime.free",
"runtime.scheduler",
Expand Down Expand Up @@ -267,7 +268,7 @@ func (c *Compiler) Compile(mainPath string) []error {
path = path[len(tinygoPath+"/src/"):]
}
switch path {
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync", "testing", "internal/reflectlite":
case "machine", "machine/sync", "os", "reflect", "runtime", "runtime/volatile", "sync", "testing", "internal/reflectlite":
return path
default:
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
Expand Down
14 changes: 9 additions & 5 deletions compiler/goroutine-lowering.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ type asyncFunc struct {
// coroutine or the tasks implementation of goroutines, and whether goroutines
// are necessary at all.
func (c *Compiler) LowerGoroutines() error {
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
c.mod.NamedFunction("runtime.mainFunc").ReplaceAllUsesWith(realMain)
switch c.selectScheduler() {
case "coroutines":
return c.lowerCoroutines()
Expand All @@ -146,10 +148,11 @@ func (c *Compiler) lowerTasks() error {
mainCall := uses[0]

realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
wrapMain := c.mod.NamedFunction("runtime.wrapMain")
if len(getUses(c.mod.NamedFunction("runtime.startGoroutine"))) != 0 || len(getUses(c.mod.NamedFunction("runtime.yield"))) != 0 {
// Program needs a scheduler. Start main.main as a goroutine and start
// the scheduler.
realMainWrapper := c.createGoroutineStartWrapper(realMain)
realMainWrapper := c.createGoroutineStartWrapper(wrapMain)
c.builder.SetInsertPointBefore(mainCall)
zero := llvm.ConstInt(c.uintptrType, 0, false)
c.createRuntimeCall("startGoroutine", []llvm.Value{realMainWrapper, zero}, "")
Expand Down Expand Up @@ -192,13 +195,14 @@ func (c *Compiler) lowerCoroutines() error {
// optionally followed by a call to runtime.scheduler().
c.builder.SetInsertPointBefore(mainCall)
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
var ph llvm.Value
wrapMain := c.mod.NamedFunction("runtime.wrapMain")
var ph, mainCallFn llvm.Value
if needsScheduler {
ph = c.createRuntimeCall("getFakeCoroutine", []llvm.Value{}, "")
ph, mainCallFn = c.createRuntimeCall("getFakeCoroutine", []llvm.Value{}, ""), wrapMain
} else {
ph = llvm.Undef(c.i8ptrType)
ph, mainCallFn = llvm.Undef(c.i8ptrType), realMain
}
c.builder.CreateCall(realMain, []llvm.Value{llvm.Undef(c.i8ptrType), ph}, "")
c.builder.CreateCall(mainCallFn, []llvm.Value{llvm.Undef(c.i8ptrType), ph}, "")
if needsScheduler {
c.createRuntimeCall("scheduler", nil, "")
}
Expand Down
63 changes: 63 additions & 0 deletions src/examples/echoAsync/echo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// This is a echo console running on the device UART.
// Connect using default baudrate for this hardware, 8-N-1 with your terminal program.
package main

import (
"machine"
"time"
)

// change these to test a different UART or pins if available
var (
uart = machine.UART0
tx = machine.UART_TX_PIN
rx = machine.UART_RX_PIN
)

func keepAlive() {
for {
time.Sleep(10 * time.Millisecond)
}
}

func main() {
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
time.Sleep(time.Second)
time.Sleep(time.Second)
time.Sleep(time.Second)
time.Sleep(time.Second)
time.Sleep(time.Second)
time.Sleep(time.Second)
time.Sleep(time.Second)
time.Sleep(time.Second)

//uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
Copy link
Member Author

Choose a reason for hiding this comment

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

I do not quite understand why this prevented it from working. Can someone explain what is going on?

Copy link
Member

Choose a reason for hiding this comment

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

Probably because UART0 is already configured as part of the runtime setup on most platforms. Otherwise we cannot do any println() statements on panic etc.

uart.Write([]byte("Echo console enabled. Type something then press enter:\r\n"))

go keepAlive()

input := make([]byte, 64)
i := 0
for {
uart.Cond.Wait()
uart.Cond.Clear()
for uart.Buffered() > 0 {
data, _ := uart.ReadByte()

switch data {
case 13:
// return key
uart.Write([]byte("\r\n"))
uart.Write([]byte("You typed: "))
uart.Write(input[:i])
uart.Write([]byte("\r\n"))
i = 0
default:
// just echo the character
uart.WriteByte(data)
input[i] = data
i++
}
}
}
}
7 changes: 6 additions & 1 deletion src/machine/machine_atsamd21.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package machine
import (
"device/arm"
"device/sam"
"machine/sync"
"errors"
"unsafe"
)
Expand Down Expand Up @@ -369,7 +370,7 @@ type UART struct {

var (
// UART0 is actually a USB CDC interface.
UART0 = USBCDC{Buffer: NewRingBuffer()}
UART0 = USBCDC{Buffer: NewRingBuffer(), Cond: &sync.Cond{}}
)

const (
Expand Down Expand Up @@ -1328,6 +1329,7 @@ func (pwm PWM) setChannel(val uint32) {
// USBCDC is the USB CDC aka serial over USB interface on the SAMD21.
type USBCDC struct {
Buffer *RingBuffer
Cond *sync.Cond
}

// WriteByte writes a byte of data to the USB CDC interface.
Expand Down Expand Up @@ -1985,6 +1987,9 @@ func handleEndpoint(ep uint32) {
for i := 0; i < count; i++ {
UART0.Receive(byte((udd_ep_out_cache_buffer[ep][i] & 0xFF)))
}
if UART0.Cond.Notify() {
LED.High()
}

// set byte count to zero
usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
Expand Down
112 changes: 112 additions & 0 deletions src/machine/sync/cond.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package sync

import "unsafe"

//go:linkname pushInt runtime.pushInt
func pushInt(t unsafe.Pointer)

//go:linkname blockTask runtime.blockTask
func blockTask(t unsafe.Pointer, chain *unsafe.Pointer)

//go:linkname getCoroutine runtime.getCoroutine
func getCoroutine() unsafe.Pointer

//go:linkname yield runtime.yield
func yield()

//go:linkname unblock runtime.unblock
func unblock(t unsafe.Pointer) unsafe.Pointer

// Cond is a condition variable that can be used by interrupts to notify goroutines.
type Cond struct {
state condState
t unsafe.Pointer
chain unsafe.Pointer
}

type condState uint8

const (
condStateReady condState = iota
condStateFired
condStateFiredAck
)

// Notify marks the Cond as completed, and unblocks all blockers.
// An interruptor of this call must not try to notify.
// Returns true if and only if the condition had not previously been notified.
func (c *Cond) Notify() bool {
switch c.state {
case condStateReady:
// condition variable has not been previously notified
if c.t != nil {
// there is a task blocked on this Cond, push it into the wakeup queue and acknowledge that we noticed it
pushInt(c.t)
c.state = condStateFiredAck
} else {
// the Cond has not yet been blocked on, mark it as notified
c.state = condStateFired
}

// we think we did something
return true
default:
// already fired, we did nothing
return false
}
}

// Wait blocks until the Cond is notified.
// If the Cond is notified before this call, this will unblock immediately.
// This does not clear the notification, so subsequent calls to Wait will not block.
func (c *Cond) Wait() {
if c.t != nil {
// task already blocked on Cond
// enqueue ourselves after it
blockTask(getCoroutine(), &c.chain)

// wait for the first blocker to wake us up
yield()
return
}

// place ourselves as a blocker in the Cond
// we need to do this before checking c.fired, in case an interrupt fires here
c.t = getCoroutine()

switch c.state {
case condStateFiredAck:
// an interrupt called notify and saw our coroutine
// we are on the wakeup queue
// we need to yield, and will be immediately awoken
yield()
case condStateFired:
// an interrupt called notify but did not see our coroutine
// we are not on the wakeup queue
// we do not need to wait
c.t = nil
default:
// nothing has been notified yet
// wait for notification
yield()
}

// detatch ourself
c.t = nil

// unblock all other things that are blocked on the cond
for t := c.chain; t != nil; t = unblock(t) {}
c.chain = nil

// finalize state
c.state = condStateFired
}

// Clear resets the condition variable.
// Subsequent calls to Wait will block until Notify is called again.
func (c *Cond) Clear() {
if c.t != nil {
panic("cannot clear a blocked condition variable")
}
c.state = condStateReady
}
72 changes: 72 additions & 0 deletions src/runtime/intq.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package runtime

// This file implements functionality that acts as a bridge between interrupts and the scheduler.

// a double-buffered linked queue used to transfer awoken tasks from the interrupt handler to the scheduler
var wakeupQueue [2]*task
var wakeupQueueSelect bool

// previous interrupt; used to handle nesting of interrupts
var prevInt *task

// pushInt pushes a task onto the scheduler's interrupt wakeup queue.
// This is meant to be called from an interrupt to wake up a task.
// In order for this to work properly, a reference to the task must be held elsewhere.
func pushInt(t *task) {
// interrupts before reading prevInt do not matter

prev := prevInt // this load has to be atomic for this to work correctly

// interrupt point A: if something interrupts here then prev.state().next will be non-nil

prevInt = t // this store has to be atomic for this to work correctly

if prev != nil {
// if we were interrupted at point A, then prev.next will have a chain - so find the tail of this chain
// nothing past prev will be modified if this is interrupted
tail := prev
for tail.state().next != nil {
tail = tail.state().next
}
tail.state().next = t
} else {
// we are at the lowest level - store the base of this chain into the wakeup queue
// we can safely be interrupted during this - any interuptors will simply place their tasks underneath t

// select the wakeup queue not currently being accessed by the scheduler
var wq **task
if wakeupQueueSelect {
wq = &wakeupQueue[1]
} else {
wq = &wakeupQueue[0]
}

// store interrupt chain into queue
if *wq == nil {
*wq = t
} else {
(*wq).state().next, *wq = *wq, t
}
}

prevInt = prev
}

// popInt pops a chain of tasks off of the interrupt wakeup queue.
// This must be called twice before letting the CPU go to sleep.
func popInt() *task {
// toggle buffers, so any interrupts that fire now write into the opposite buffer
wakeupQueueSelect = !wakeupQueueSelect

// get task chain
var wq **task
if wakeupQueueSelect {
wq = &wakeupQueue[0]
} else {
wq = &wakeupQueue[1]
}
chain := *wq
*wq = nil

return chain
}
17 changes: 15 additions & 2 deletions src/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,27 @@ func initAll()
//
// Without scheduler:
//
// main.main()
// mainFunc()
//
// With scheduler:
//
// main.main()
// go wrapMain()
// scheduler()
func callMain()

// mainFunc is a temporary value that will be later replaced with the actual user-provided main function
func mainFunc()

// wrapMain is a wrapper which is used for invoking main.
// When main completes, this allows the program to return.
func wrapMain() {
// run main
mainFunc()

// when main is done, let the scheduler exit
schedDone = true
}

func GOMAXPROCS(n int) int {
// Note: setting GOMAXPROCS is ignored.
return 1
Expand Down
Loading