From 2a20c0c7f0c13c9b5c81422ea1c24ddd4eb765e7 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 15 Sep 2018 01:08:31 +0200 Subject: [PATCH] all: rewrite sleep function time.Sleep now compiles on all systems, so lets use that. Additionally, do a few improvements in time unit handling for the scheduler. This should lead to somewhat longer sleep durations without wrapping (on some platforms). Some examples got smaller, some got bigger. In particular, code using the scheduler got bigger and the blinky1 example got smaller (especially on Arduino: 380 -> 314 bytes). --- README.markdown | 9 ++++++--- compiler.go | 2 +- passes.go | 2 +- src/examples/blinky1/blinky1.go | 6 +++--- src/examples/blinky2/blinky2.go | 10 +++++----- src/examples/button/button.go | 4 ++-- src/runtime/runtime.go | 7 ------- src/runtime/runtime_avr.go | 20 +++++++++++--------- src/runtime/runtime_nrf.go | 32 +++++++++++++++++++------------- src/runtime/runtime_unix.go | 24 +++++++++++++++++------- src/runtime/scheduler.go | 20 ++++++++++---------- src/runtime/time.go | 11 ----------- 12 files changed, 75 insertions(+), 72 deletions(-) delete mode 100644 src/runtime/time.go diff --git a/README.markdown b/README.markdown index 9b2fda63..09b604f8 100644 --- a/README.markdown +++ b/README.markdown @@ -19,17 +19,20 @@ run on even lower level micros. Example program (blinky): ```go -import "machine" +import ( + "machine" + "time" +) func main() { led := machine.GPIO{machine.LED} led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT}) for { led.Low() - runtime.Sleep(runtime.Millisecond * 1000) + time.Sleep(time.Millisecond * 1000) led.High() - runtime.Sleep(runtime.Millisecond * 1000) + time.Sleep(time.Millisecond * 1000) } } ``` diff --git a/compiler.go b/compiler.go index 7d52ef06..503bf832 100644 --- a/compiler.go +++ b/compiler.go @@ -1736,7 +1736,7 @@ func (c *Compiler) parseFunctionCall(frame *Frame, args []ssa.Value, llvmFn, con params = append(params, context) } - if frame.blocking && llvmFn.Name() == "runtime.Sleep" { + if frame.blocking && llvmFn.Name() == "time.Sleep" { // Set task state to TASK_STATE_SLEEP and set the duration. c.builder.CreateCall(c.mod.NamedFunction("runtime.sleepTask"), []llvm.Value{frame.taskHandle, params[0]}, "") diff --git a/passes.go b/passes.go index 82962bf3..cedfce3c 100644 --- a/passes.go +++ b/passes.go @@ -99,7 +99,7 @@ func (p *Program) AnalyseCallgraph() { if child.CName() != "" { continue // assume non-blocking } - if child.LinkName() == "runtime.Sleep" { + if child.fn.RelString(nil) == "time.Sleep" { f.blocking = true } f.children = append(f.children, child) diff --git a/src/examples/blinky1/blinky1.go b/src/examples/blinky1/blinky1.go index 8889cb69..9e74bd17 100644 --- a/src/examples/blinky1/blinky1.go +++ b/src/examples/blinky1/blinky1.go @@ -4,7 +4,7 @@ package main import ( "machine" - "runtime" + "time" ) func main() { @@ -12,9 +12,9 @@ func main() { led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT}) for { led.Low() - runtime.Sleep(runtime.Millisecond * 500) + time.Sleep(time.Millisecond * 500) led.High() - runtime.Sleep(runtime.Millisecond * 500) + time.Sleep(time.Millisecond * 500) } } diff --git a/src/examples/blinky2/blinky2.go b/src/examples/blinky2/blinky2.go index 417ca604..b470e923 100644 --- a/src/examples/blinky2/blinky2.go +++ b/src/examples/blinky2/blinky2.go @@ -7,7 +7,7 @@ package main import ( "machine" - "runtime" + "time" ) func main() { @@ -21,11 +21,11 @@ func led1() { for { println("+") led.Low() - runtime.Sleep(runtime.Millisecond * 1000) + time.Sleep(time.Millisecond * 1000) println("-") led.High() - runtime.Sleep(runtime.Millisecond * 1000) + time.Sleep(time.Millisecond * 1000) } } @@ -35,10 +35,10 @@ func led2() { for { println(" +") led.Low() - runtime.Sleep(runtime.Millisecond * 420) + time.Sleep(time.Millisecond * 420) println(" -") led.High() - runtime.Sleep(runtime.Millisecond * 420) + time.Sleep(time.Millisecond * 420) } } diff --git a/src/examples/button/button.go b/src/examples/button/button.go index 3be226c4..30261b8c 100644 --- a/src/examples/button/button.go +++ b/src/examples/button/button.go @@ -2,7 +2,7 @@ package main import ( "machine" - "runtime" + "time" ) // This example assumes that the button is connected to pin 8. Change the value @@ -23,6 +23,6 @@ func main() { led.High() } - runtime.Sleep(runtime.Millisecond * 10) + time.Sleep(time.Millisecond * 10) } } diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index c6e892d6..159e65fa 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -39,13 +39,6 @@ func main() int { } } -func Sleep(d Duration) { - // This function is treated specially by the compiler: when goroutines are - // used, it is transformed into a llvm.coro.suspend() call. - // When goroutines are not used this function behaves as normal. - sleep(d) -} - func GOMAXPROCS(n int) int { // Note: setting GOMAXPROCS is ignored. return 1 diff --git a/src/runtime/runtime_avr.go b/src/runtime/runtime_avr.go index 48e433c8..b94e86d2 100644 --- a/src/runtime/runtime_avr.go +++ b/src/runtime/runtime_avr.go @@ -8,13 +8,11 @@ import ( const BOARD = "arduino" -const Microsecond = 1 +type timeUnit uint32 -var currentTime uint64 +var currentTime timeUnit -func init() { - currentTime = 0 -} +const tickMicros = 1024 * 16384 // Watchdog timer periods. These can be off by a large margin (hence the jump // between 64ms and 125ms which is not an exact double), so don't rely on this @@ -53,9 +51,13 @@ func putchar(c byte) { // // TODO: not very accurate. Improve accuracy by calibrating on startup and every // once in a while. -func sleep(d Duration) { - currentTime += uint64(d) - d /= 16384 // about 16ms +//go:linkname sleep time.Sleep +func sleep(d int64) { + sleepTicks(timeUnit(d / tickMicros)) +} + +func sleepTicks(d timeUnit) { + currentTime += d for d != 0 { sleepWDT(WDT_PERIOD_16MS) d -= 1 @@ -90,7 +92,7 @@ func sleepWDT(period uint8) { *avr.SMCR = 0 } -func monotime() uint64 { +func ticks() timeUnit { return currentTime } diff --git a/src/runtime/runtime_nrf.go b/src/runtime/runtime_nrf.go index 66418e8a..a359cef4 100644 --- a/src/runtime/runtime_nrf.go +++ b/src/runtime/runtime_nrf.go @@ -7,7 +7,9 @@ import ( "device/nrf" ) -const Microsecond = 1 +type timeUnit int64 + +const tickMicros = 1024 * 32 //go:export _start func _start() { @@ -48,31 +50,35 @@ func putchar(c byte) { nrf.UART0.EVENTS_TXDRDY = 0 } -func sleep(d Duration) { - ticks64 := d / 32 - for ticks64 != 0 { - monotime() // update timestamp - ticks := uint32(ticks64) & 0x7fffff // 23 bits (to be on the safe side) - rtc_sleep(ticks) // TODO: not accurate (must be d / 30.5175...) - ticks64 -= Duration(ticks) +//go:linkname sleep time.Sleep +func sleep(d timeUnit) { + sleepTicks(d / tickMicros) +} + +func sleepTicks(d timeUnit) { + for d != 0 { + ticks() // update timestamp + ticks := uint32(d) & 0x7fffff // 23 bits (to be on the safe side) + rtc_sleep(ticks) // TODO: not accurate (must be d / 30.5175...) + d -= timeUnit(ticks) } } var ( - timestamp uint64 // microseconds since boottime - rtcLastCounter uint32 // 24 bits ticks + timestamp timeUnit // nanoseconds since boottime + rtcLastCounter uint32 // 24 bits ticks ) -// Monotonically increasing numer of microseconds since start. +// Monotonically increasing numer of ticks since start. // // Note: very long pauses between measurements (more than 8 minutes) may // overflow the counter, leading to incorrect results. This might be fixed by // handling the overflow event. -func monotime() uint64 { +func ticks() timeUnit { rtcCounter := uint32(nrf.RTC0.COUNTER) offset := (rtcCounter - rtcLastCounter) % 0xffffff // change since last measurement rtcLastCounter = rtcCounter - timestamp += uint64(offset * 32) // TODO: not precise + timestamp += timeUnit(offset) // TODO: not precise return timestamp } diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index fb59175c..9baf66c4 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -6,14 +6,16 @@ import ( "unsafe" ) -const Microsecond = 1 - func _Cfunc_putchar(c int) int func _Cfunc_usleep(usec uint) int func _Cfunc_calloc(nmemb, size uintptr) unsafe.Pointer func _Cfunc_exit(status int) func _Cfunc_clock_gettime(clk_id uint, ts *timespec) +type timeUnit int64 + +const tickMicros = 1 + // TODO: Linux/amd64-specific type timespec struct { tv_sec int64 @@ -26,18 +28,26 @@ func putchar(c byte) { _Cfunc_putchar(int(c)) } -func sleep(d Duration) { - _Cfunc_usleep(uint(d)) +//go:linkname sleep time.Sleep +func sleep(d int64) { + _Cfunc_usleep(uint(d) / 1000) } -// Return monotonic time in microseconds. +func sleepTicks(d timeUnit) { + sleep(int64(d)) +} + +// Return monotonic time in nanoseconds. // -// TODO: use nanoseconds? // TODO: noescape func monotime() uint64 { ts := timespec{} _Cfunc_clock_gettime(CLOCK_MONOTONIC_RAW, &ts) - return uint64(ts.tv_sec)*1000*1000 + uint64(ts.tv_nsec)/1000 + return uint64(ts.tv_sec)*1000*1000*1000 + uint64(ts.tv_nsec) +} + +func ticks() timeUnit { + return timeUnit(monotime()) } func abort() { diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index 8d073230..f31a601b 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -72,7 +72,7 @@ var ( runqueueFront *coroutine runqueueBack *coroutine sleepQueue *coroutine - sleepQueueBaseTime uint64 + sleepQueueBaseTime timeUnit ) // Simple logging, for debugging. @@ -92,13 +92,13 @@ func scheduleLogTask(msg string, t *coroutine) { // Set the task state to sleep for a given time. // // This is a compiler intrinsic. -func sleepTask(caller *coroutine, duration Duration) { +func sleepTask(caller *coroutine, duration int64) { if schedulerDebug { - println(" set state sleep:", caller, uint32(duration)) + println(" set state sleep:", caller, uint32(duration/tickMicros)) } promise := caller.promise() promise.state = TASK_STATE_SLEEP - promise.data = uint32(duration) // TODO: longer durations + promise.data = uint32(duration / tickMicros) // TODO: longer durations } // Wait for the result of an async call. This means that the parent goroutine @@ -194,7 +194,7 @@ func addSleepTask(t *coroutine) { panic("runtime: addSleepTask: task not sleeping") } } - now := monotime() + now := ticks() if sleepQueue == nil { scheduleLog(" -> sleep new queue") // Create new linked list for the sleep queue. @@ -244,15 +244,15 @@ func scheduler(main *coroutine) { // Main scheduler loop. for { scheduleLog("\n schedule") - now := monotime() + now := ticks() // Add tasks that are done sleeping to the end of the runqueue so they // will be executed soon. - if sleepQueue != nil && now-sleepQueueBaseTime >= uint64(sleepQueue.promise().data) { + if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.promise().data) { t := sleepQueue scheduleLogTask(" awake:", t) promise := t.promise() - sleepQueueBaseTime += uint64(promise.data) + sleepQueueBaseTime += timeUnit(promise.data) sleepQueue = promise.next promise.state = TASK_STATE_RUNNABLE promise.next = nil @@ -269,11 +269,11 @@ func scheduler(main *coroutine) { scheduleLog(" no tasks left!") return } - timeLeft := uint64(sleepQueue.promise().data) - (now - sleepQueueBaseTime) + timeLeft := timeUnit(sleepQueue.promise().data) - (now - sleepQueueBaseTime) if schedulerDebug { println(" sleeping...", sleepQueue, uint32(timeLeft)) } - sleep(Duration(timeLeft)) + sleepTicks(timeUnit(timeLeft)) continue } diff --git a/src/runtime/time.go b/src/runtime/time.go deleted file mode 100644 index 3bdddaf3..00000000 --- a/src/runtime/time.go +++ /dev/null @@ -1,11 +0,0 @@ -package runtime - -// TODO: use the time package for this. - -type Duration uint64 - -// Use microseconds as the smallest time unit -const ( - Millisecond = Microsecond * 1000 - Second = Millisecond * 1000 -)