compiler,runtime: implement stack-based scheduler

This scheduler is intended to live along the (stackless) coroutine based
scheduler which is needed for WebAssembly and unsupported platforms. The
stack based scheduler is somewhat simpler in implementation as it does
not require full program transform passes and supports things like
function pointers and interface methods out of the box with no changes.

Code size is reduced in most cases, even in the case where no scheduler
scheduler is used at all. I'm not exactly sure why but these changes
likely allowed some further optimizations somewhere. Even RAM is
slightly reduced, perhaps some global was elminated in the process as
well.
Этот коммит содержится в:
Ayke van Laethem 2019-08-13 12:35:46 +02:00 коммит произвёл Ron Evans
родитель 61f711ef26
коммит 542135c357
17 изменённых файлов: 702 добавлений и 224 удалений

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

@ -69,7 +69,7 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk {
commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide")
commaOk := c.createRuntimeCall("getTaskStateData", []llvm.Value{coroutine}, "chan.commaOk.wide")
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
@ -95,7 +95,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
if expr.Blocking {
// Blocks forever:
// select {}
c.createRuntimeCall("deadlockStub", nil, "")
c.createRuntimeCall("deadlock", nil, "")
return llvm.Undef(llvmType)
} else {
// No-op:

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

@ -30,6 +30,20 @@ func init() {
// The TinyGo import path.
const tinygoPath = "github.com/tinygo-org/tinygo"
// functionsUsedInTransform is a list of function symbols that may be used
// during TinyGo optimization passes so they have to be marked as external
// linkage until all TinyGo passes have finished.
var functionsUsedInTransforms = []string{
"runtime.alloc",
"runtime.free",
"runtime.sleepTask",
"runtime.setTaskStatePtr",
"runtime.getTaskStatePtr",
"runtime.activateTask",
"runtime.scheduler",
"runtime.startGoroutine",
}
// Configure the compiler.
type Config struct {
Triple string // LLVM target triple, e.g. x86_64-unknown-linux-gnu (empty string means default)
@ -38,6 +52,7 @@ type Config struct {
GOOS string //
GOARCH string //
GC string // garbage collection strategy
Scheduler string // scheduler implementation ("coroutines" or "tasks")
PanicStrategy string // panic strategy ("print" or "trap")
CFlags []string // cflags to pass to cgo
LDFlags []string // ldflags to pass to cgo
@ -173,6 +188,17 @@ func (c *Compiler) selectGC() string {
return "conservative"
}
// selectScheduler picks an appropriate scheduler for the target if none was
// given.
func (c *Compiler) selectScheduler() string {
if c.Scheduler != "" {
// A scheduler was specified in the target description.
return c.Scheduler
}
// Fall back to coroutines, which are supported everywhere.
return "coroutines"
}
// Compile the given package path or .go file path. Return an error when this
// fails (in any stage).
func (c *Compiler) Compile(mainPath string) []error {
@ -189,6 +215,7 @@ func (c *Compiler) Compile(mainPath string) []error {
if err != nil {
return []error{err}
}
buildTags := append([]string{"tinygo", "gc." + c.selectGC(), "scheduler." + c.selectScheduler()}, c.BuildTags...)
lprogram := &loader.Program{
Build: &build.Context{
GOARCH: c.GOARCH,
@ -198,7 +225,7 @@ func (c *Compiler) Compile(mainPath string) []error {
CgoEnabled: true,
UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers
BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
BuildTags: buildTags,
},
OverlayBuild: &build.Context{
GOARCH: c.GOARCH,
@ -208,7 +235,7 @@ func (c *Compiler) Compile(mainPath string) []error {
CgoEnabled: true,
UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers
BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
BuildTags: buildTags,
},
OverlayPath: func(path string) string {
// Return the (overlay) import path when it should be overlaid, and
@ -335,13 +362,15 @@ func (c *Compiler) Compile(mainPath string) []error {
// would be optimized away.
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.ExternalLinkage)
// Make sure these functions are kept in tact during TinyGo transformation passes.
for _, name := range functionsUsedInTransforms {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
continue
}
fn.SetLinkage(llvm.ExternalLinkage)
}
// Load some attributes
getAttr := func(attrName string) llvm.Attribute {
@ -1041,25 +1070,21 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
}
calleeFn := c.ir.GetFunction(callee)
// Mark this function as a 'go' invocation and break invalid
// interprocedural optimizations. For example, heap-to-stack
// transformations are not sound as goroutines can outlive their parent.
calleeType := calleeFn.LLVMFn.Type()
calleeValue := c.builder.CreatePtrToInt(calleeFn.LLVMFn, c.uintptrType, "")
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
calleeValue = c.builder.CreateIntToPtr(calleeValue, calleeType, "")
// Get all function parameters to pass to the goroutine.
var params []llvm.Value
for _, param := range instr.Call.Args {
params = append(params, c.getValue(frame, param))
}
if !calleeFn.IsExported() {
if !calleeFn.IsExported() && c.selectScheduler() != "tasks" {
// For coroutine scheduling, this is only required when calling an
// external function.
// For tasks, because all params are stored in a single object, no
// unnecessary parameters should be stored anyway.
params = append(params, llvm.Undef(c.i8ptrType)) // context parameter
params = append(params, llvm.Undef(c.i8ptrType)) // parent coroutine handle
}
c.createCall(calleeValue, params, "")
c.emitStartGoroutine(calleeFn.LLVMFn, params)
case *ssa.If:
cond := c.getValue(frame, instr.Cond)
block := instr.Block()

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

@ -1,5 +1,16 @@
package compiler
// This file implements lowering for the goroutine scheduler. There are two
// scheduler implementations, one based on tasks (like RTOSes and the main Go
// runtime) and one based on a coroutine compiler transformation. The task based
// implementation requires very little work from the compiler but is not very
// portable (in particular, it is very hard if not impossible to support on
// WebAssembly). The coroutine based one requires a lot of work by the compiler
// to implement, but can run virtually anywhere with a single scheduler
// implementation.
//
// The below description is for the coroutine based scheduler.
//
// This file lowers goroutine pseudo-functions into coroutines scheduled by a
// scheduler at runtime. It uses coroutine support in LLVM for this
// transformation: https://llvm.org/docs/Coroutines.html
@ -62,7 +73,7 @@ package compiler
// llvm.suspend(hdl) // suspend point
// println("some other operation")
// var i *int // allocate space on the stack for the return value
// runtime.setTaskPromisePtr(hdl, &i) // store return value alloca in our coroutine promise
// runtime.setTaskStatePtr(hdl, &i) // store return value alloca in our coroutine promise
// bar(hdl) // await, pass a continuation (hdl) to bar
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
// println("done", *i)
@ -106,10 +117,65 @@ type asyncFunc struct {
unreachableBlock llvm.BasicBlock
}
// LowerGoroutines is a pass called during optimization that transforms the IR
// into one where all blocking functions are turned into goroutines and blocking
// calls into await calls.
// LowerGoroutines performs some IR transformations necessary to support
// goroutines. It does something different based on whether it uses the
// coroutine or the tasks implementation of goroutines, and whether goroutines
// are necessary at all.
func (c *Compiler) LowerGoroutines() error {
switch c.selectScheduler() {
case "coroutines":
return c.lowerCoroutines()
case "tasks":
return c.lowerTasks()
default:
panic("unknown scheduler type")
}
}
// lowerTasks starts the main goroutine and then runs the scheduler.
// This is enough compiler-level transformation for the task-based scheduler.
func (c *Compiler) lowerTasks() error {
uses := getUses(c.mod.NamedFunction("runtime.callMain"))
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
panic("expected exactly 1 call of runtime.callMain, check the entry point")
}
mainCall := uses[0]
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
if len(getUses(c.mod.NamedFunction("runtime.startGoroutine"))) != 0 {
// Program needs a scheduler. Start main.main as a goroutine and start
// the scheduler.
realMainWrapper := c.createGoroutineStartWrapper(realMain)
c.builder.SetInsertPointBefore(mainCall)
zero := llvm.ConstInt(c.uintptrType, 0, false)
c.createRuntimeCall("startGoroutine", []llvm.Value{realMainWrapper, zero}, "")
c.createRuntimeCall("scheduler", nil, "")
} else {
// Program doesn't need a scheduler. Call main.main directly.
c.builder.SetInsertPointBefore(mainCall)
params := []llvm.Value{
llvm.Undef(c.i8ptrType), // unused context parameter
llvm.Undef(c.i8ptrType), // unused coroutine handle
}
c.createCall(realMain, params, "")
// runtime.Goexit isn't needed so let it be optimized away by
// globalopt.
c.mod.NamedFunction("runtime.Goexit").SetLinkage(llvm.InternalLinkage)
}
mainCall.EraseFromParentAsInstruction()
// main.main was set to external linkage during IR construction. Set it to
// internal linkage to enable interprocedural optimizations.
realMain.SetLinkage(llvm.InternalLinkage)
return nil
}
// lowerCoroutines transforms the IR into one where all blocking functions are
// turned into goroutines and blocking calls into await calls. It also makes
// sure that the first coroutine is started and the coroutine scheduler will be
// run.
func (c *Compiler) lowerCoroutines() error {
needsScheduler, err := c.markAsyncFunctions()
if err != nil {
return err
@ -144,12 +210,6 @@ func (c *Compiler) LowerGoroutines() error {
// main.main was set to external linkage during IR construction. Set it to
// internal linkage to enable interprocedural optimizations.
realMain.SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
return nil
}
@ -173,9 +233,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
if !sleep.IsNil() {
worklist = append(worklist, sleep)
}
deadlockStub := c.mod.NamedFunction("runtime.deadlockStub")
if !deadlockStub.IsNil() {
worklist = append(worklist, deadlockStub)
deadlock := c.mod.NamedFunction("runtime.deadlock")
if !deadlock.IsNil() {
worklist = append(worklist, deadlock)
}
chanSend := c.mod.NamedFunction("runtime.chanSend")
if !chanSend.IsNil() {
@ -300,7 +360,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Transform all async functions into coroutines.
for _, f := range asyncList {
if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv {
if f == sleep || f == deadlock || f == chanSend || f == chanRecv {
continue
}
@ -317,7 +377,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if !inst.IsACallInst().IsNil() {
callee := inst.CalledValue()
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlockStub || callee == chanSend || callee == chanRecv {
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlock || callee == chanSend || callee == chanRecv {
continue
}
asyncCalls = append(asyncCalls, inst)
@ -365,7 +425,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca")
c.builder.SetInsertPointBefore(inst)
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
c.createRuntimeCall("setTaskPromisePtr", []llvm.Value{frame.taskHandle, data}, "")
c.createRuntimeCall("setTaskStatePtr", []llvm.Value{frame.taskHandle, data}, "")
}
// Suspend.
@ -403,7 +463,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
var parentHandle llvm.Value
if f.Linkage() == llvm.ExternalLinkage {
// Exported function.
// Note that getTaskPromisePtr will panic if it is called with
// Note that getTaskStatePtr will panic if it is called with
// a nil pointer, so blocking exported functions that try to
// return anything will not work.
parentHandle = llvm.ConstPointerNull(c.i8ptrType)
@ -423,7 +483,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Return this value by writing to the pointer stored in the
// parent handle. The parent coroutine has made an alloca that
// we can write to to store our return value.
returnValuePtr := c.createRuntimeCall("getTaskPromisePtr", []llvm.Value{parentHandle}, "coro.parentData")
returnValuePtr := c.createRuntimeCall("getTaskStatePtr", []llvm.Value{parentHandle}, "coro.parentData")
alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca")
c.builder.CreateStore(inst.Operand(0), alloca)
default:
@ -502,9 +562,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
sleepCall.EraseFromParentAsInstruction()
}
// Transform calls to runtime.deadlockStub into coroutine suspends (without
// Transform calls to runtime.deadlock into coroutine suspends (without
// resume).
for _, deadlockCall := range getUses(deadlockStub) {
for _, deadlockCall := range getUses(deadlock) {
// deadlockCall must be a call instruction.
frame := asyncFuncs[deadlockCall.InstructionParent().Parent()]

83
compiler/goroutine.go Обычный файл
Просмотреть файл

@ -0,0 +1,83 @@
package compiler
// This file implements the 'go' keyword to start a new goroutine. See
// goroutine-lowering.go for more details.
import "tinygo.org/x/go-llvm"
// emitStartGoroutine starts a new goroutine with the provided function pointer
// and parameters.
//
// Because a go statement doesn't return anything, return undef.
func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
switch c.selectScheduler() {
case "tasks":
paramBundle := c.emitPointerPack(params)
paramBundle = c.builder.CreatePtrToInt(paramBundle, c.uintptrType, "")
calleeValue := c.createGoroutineStartWrapper(funcPtr)
c.createRuntimeCall("startGoroutine", []llvm.Value{calleeValue, paramBundle}, "")
case "coroutines":
// Mark this function as a 'go' invocation and break invalid
// interprocedural optimizations. For example, heap-to-stack
// transformations are not sound as goroutines can outlive their parent.
calleeType := funcPtr.Type()
calleeValue := c.builder.CreatePtrToInt(funcPtr, c.uintptrType, "")
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
calleeValue = c.builder.CreateIntToPtr(calleeValue, calleeType, "")
c.createCall(calleeValue, params, "")
default:
panic("unreachable")
}
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
}
// createGoroutineStartWrapper creates a wrapper for the task-based
// implementation of goroutines. For example, to call a function like this:
//
// func add(x, y int) int { ... }
//
// It creates a wrapper like this:
//
// func add$gowrapper(ptr *unsafe.Pointer) {
// args := (*struct{
// x, y int
// })(ptr)
// add(args.x, args.y)
// }
//
// This is useful because the task-based goroutine start implementation only
// allows a single (pointer) argument to the newly started goroutine. Also, it
// ignores the return value because newly started goroutines do not have a
// return value.
func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
if fn.IsAFunction().IsNil() {
panic("todo: goroutine start wrapper for func value")
}
// See whether this wrapper has already been created. If so, return it.
name := fn.Name()
wrapper := c.mod.NamedFunction(name + "$gowrapper")
if !wrapper.IsNil() {
return c.builder.CreateIntToPtr(wrapper, c.uintptrType, "")
}
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
wrapper.SetLinkage(llvm.PrivateLinkage)
wrapper.SetUnnamedAddr(true)
entry := llvm.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(entry)
paramTypes := fn.Type().ElementType().ParamTypes()
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes[:len(paramTypes)-2])
params = append(params, llvm.Undef(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType))
c.builder.CreateCall(fn, params, "")
c.builder.CreateRetVoid()
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}

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

@ -101,6 +101,15 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
}
}
// After TinyGo-specific transforms have finished, undo exporting these functions.
for _, name := range functionsUsedInTransforms {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
continue
}
fn.SetLinkage(llvm.InternalLinkage)
}
// Run function passes again, because without it, llvm.coro.size.i32()
// doesn't get lowered.
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {

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

@ -47,6 +47,7 @@ type BuildConfig struct {
opt string
gc string
panicStrategy string
scheduler string
printIR bool
dumpSSA bool
debug bool
@ -97,6 +98,10 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
if extraTags := strings.Fields(config.tags); len(extraTags) != 0 {
tags = append(tags, extraTags...)
}
scheduler := spec.Scheduler
if config.scheduler != "" {
scheduler = config.scheduler
}
compilerConfig := compiler.Config{
Triple: spec.Triple,
CPU: spec.CPU,
@ -105,6 +110,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
GOARCH: spec.GOARCH,
GC: config.gc,
PanicStrategy: config.panicStrategy,
Scheduler: scheduler,
CFlags: cflags,
LDFlags: ldflags,
ClangHeaders: getClangHeaderPath(root),
@ -618,6 +624,7 @@ func main() {
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
scheduler := flag.String("scheduler", "", "which scheduler to use (coroutines, tasks)")
printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
tags := flag.String("tags", "", "a space-separated list of extra build tags")
@ -643,6 +650,7 @@ func main() {
opt: *opt,
gc: *gc,
panicStrategy: *panicStrategy,
scheduler: *scheduler,
printIR: *printIR,
dumpSSA: *dumpSSA,
debug: !*nodebug,

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

@ -30,7 +30,7 @@ import (
type channel struct {
elementSize uint16 // the size of one value in this channel
state chanState
blocked *coroutine
blocked *task
}
type chanState uint8
@ -50,40 +50,44 @@ type chanSelectState struct {
value unsafe.Pointer
}
func deadlockStub()
// chanSend sends a single value over the channel. If this operation can
// complete immediately (there is a goroutine waiting for a value), it sends the
// value and re-activates both goroutines. If not, it sets itself as waiting on
// a value.
func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer) {
func chanSend(sender *task, ch *channel, value unsafe.Pointer) {
if ch == nil {
// A nil channel blocks forever. Do not scheduler this goroutine again.
chanYield()
return
}
switch ch.state {
case chanStateEmpty:
sender.promise().ptr = value
scheduleLogChan(" send: chan is empty ", ch, sender)
sender.state().ptr = value
ch.state = chanStateSend
ch.blocked = sender
chanYield()
case chanStateRecv:
scheduleLogChan(" send: chan in recv mode", ch, sender)
receiver := ch.blocked
receiverPromise := receiver.promise()
memcpy(receiverPromise.ptr, value, uintptr(ch.elementSize))
receiverPromise.data = 1 // commaOk = true
ch.blocked = receiverPromise.next
receiverPromise.next = nil
receiverState := receiver.state()
memcpy(receiverState.ptr, value, uintptr(ch.elementSize))
receiverState.data = 1 // commaOk = true
ch.blocked = receiverState.next
receiverState.next = nil
activateTask(receiver)
activateTask(sender)
reactivateParent(sender)
if ch.blocked == nil {
ch.state = chanStateEmpty
}
case chanStateClosed:
runtimePanic("send on closed channel")
case chanStateSend:
sender.promise().ptr = value
sender.promise().next = ch.blocked
scheduleLogChan(" send: chan in send mode", ch, sender)
sender.state().ptr = value
sender.state().next = ch.blocked
ch.blocked = sender
chanYield()
}
}
@ -91,36 +95,43 @@ func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer) {
// sender, it receives the value immediately and re-activates both coroutines.
// If not, it sets itself as available for receiving. If the channel is closed,
// it immediately activates itself with a zero value as the result.
func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer) {
func chanRecv(receiver *task, ch *channel, value unsafe.Pointer) {
if ch == nil {
// A nil channel blocks forever. Do not scheduler this goroutine again.
chanYield()
return
}
switch ch.state {
case chanStateSend:
scheduleLogChan(" recv: chan in send mode", ch, receiver)
sender := ch.blocked
senderPromise := sender.promise()
memcpy(value, senderPromise.ptr, uintptr(ch.elementSize))
receiver.promise().data = 1 // commaOk = true
ch.blocked = senderPromise.next
senderPromise.next = nil
activateTask(receiver)
senderState := sender.state()
memcpy(value, senderState.ptr, uintptr(ch.elementSize))
receiver.state().data = 1 // commaOk = true
ch.blocked = senderState.next
senderState.next = nil
reactivateParent(receiver)
activateTask(sender)
if ch.blocked == nil {
ch.state = chanStateEmpty
}
case chanStateEmpty:
receiver.promise().ptr = value
scheduleLogChan(" recv: chan is empty ", ch, receiver)
receiver.state().ptr = value
ch.state = chanStateRecv
ch.blocked = receiver
chanYield()
case chanStateClosed:
scheduleLogChan(" recv: chan is closed ", ch, receiver)
memzero(value, uintptr(ch.elementSize))
receiver.promise().data = 0 // commaOk = false
activateTask(receiver)
receiver.state().data = 0 // commaOk = false
reactivateParent(receiver)
case chanStateRecv:
receiver.promise().ptr = value
receiver.promise().next = ch.blocked
scheduleLogChan(" recv: chan in recv mode", ch, receiver)
receiver.state().ptr = value
receiver.state().next = ch.blocked
ch.blocked = receiver
chanYield()
}
}
@ -143,9 +154,9 @@ func chanClose(ch *channel) {
runtimePanic("close channel during send")
case chanStateRecv:
// The receiver must be re-activated with a zero value.
receiverPromise := ch.blocked.promise()
memzero(receiverPromise.ptr, uintptr(ch.elementSize))
receiverPromise.data = 0 // commaOk = false
receiverState := ch.blocked.state()
memzero(receiverState.ptr, uintptr(ch.elementSize))
receiverState.data = 0 // commaOk = false
activateTask(ch.blocked)
ch.state = chanStateClosed
ch.blocked = nil
@ -174,10 +185,10 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, blocking bool)
case chanStateSend:
// We can receive immediately.
sender := state.ch.blocked
senderPromise := sender.promise()
memcpy(recvbuf, senderPromise.ptr, uintptr(state.ch.elementSize))
state.ch.blocked = senderPromise.next
senderPromise.next = nil
senderState := sender.state()
memcpy(recvbuf, senderState.ptr, uintptr(state.ch.elementSize))
state.ch.blocked = senderState.next
senderState.next = nil
activateTask(sender)
if state.ch.blocked == nil {
state.ch.state = chanStateEmpty
@ -193,11 +204,11 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, blocking bool)
switch state.ch.state {
case chanStateRecv:
receiver := state.ch.blocked
receiverPromise := receiver.promise()
memcpy(receiverPromise.ptr, state.value, uintptr(state.ch.elementSize))
receiverPromise.data = 1 // commaOk = true
state.ch.blocked = receiverPromise.next
receiverPromise.next = nil
receiverState := receiver.state()
memcpy(receiverState.ptr, state.value, uintptr(state.ch.elementSize))
receiverState.data = 1 // commaOk = true
state.ch.blocked = receiverState.next
receiverState.next = nil
activateTask(receiver)
if state.ch.blocked == nil {
state.ch.state = chanStateEmpty

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

@ -79,11 +79,6 @@ func memequal(x, y unsafe.Pointer, n uintptr) bool {
return true
}
//go:linkname sleep time.Sleep
func sleep(d int64) {
sleepTicks(timeUnit(d / tickMicros))
}
func nanotime() int64 {
return int64(ticks()) * tickMicros
}

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

@ -40,6 +40,29 @@ func preinit() {
}
}
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see scheduler_cortexm.S that relies on the
// exact layout of this struct.
type calleeSavedRegs struct {
r4 uintptr
r5 uintptr
r6 uintptr
r7 uintptr
r8 uintptr
r9 uintptr
r10 uintptr
r11 uintptr
}
// prepareStartTask stores fn and args in some callee-saved registers that can
// then be used by the startTask function (implemented in assembly) to set up
// the initial stack pointer and initial argument with the pointer to the object
// with the goroutine start arguments.
func (r *calleeSavedRegs) prepareStartTask(fn, args uintptr) {
r.r4 = fn
r.r5 = args
}
func abort() {
// disable all interrupts
arm.DisableInterrupts()

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

@ -1,63 +1,28 @@
package runtime
// This file implements the Go scheduler using coroutines.
// A goroutine contains a whole stack. A coroutine is just a single function.
// How do we use coroutines for goroutines, then?
// * Every function that contains a blocking call (like sleep) is marked
// blocking, and all it's parents (callers) are marked blocking as well
// transitively until the root (main.main or a go statement).
// * A blocking function that calls a non-blocking function is called as
// usual.
// * A blocking function that calls a blocking function passes its own
// coroutine handle as a parameter to the subroutine. When the subroutine
// returns, it will re-insert the parent into the scheduler.
// Note that a goroutine is generally called a 'task' for brevity and because
// that's the more common term among RTOSes. But a goroutine and a task are
// basically the same thing. Although, the code often uses the word 'task' to
// refer to both a coroutine and a goroutine, as most of the scheduler doesn't
// care about the difference.
// This file implements the TinyGo scheduler. This scheduler is a very simple
// cooperative round robin scheduler, with a runqueue that contains a linked
// list of goroutines (tasks) that should be run next, in order of when they
// were added to the queue (first-in, first-out). It also contains a sleep queue
// with sleeping goroutines in order of when they should be re-activated.
//
// For more background on coroutines in LLVM:
// https://llvm.org/docs/Coroutines.html
// The scheduler is used both for the coroutine based scheduler and for the task
// based scheduler (see compiler/goroutine-lowering.go for a description). In
// both cases, the 'task' type is used to represent one goroutine. In the case
// of the task based scheduler, it literally is the goroutine itself: a pointer
// to the bottom of the stack where some important fields are kept. In the case
// of the coroutine-based scheduler, it is the coroutine pointer (a *i8 in
// LLVM).
import (
"unsafe"
)
import "unsafe"
const schedulerDebug = false
// A coroutine instance, wrapped here to provide some type safety. The value
// must not be used directly, it is meant to be used as an opaque *i8 in LLVM.
type coroutine uint8
//go:export llvm.coro.resume
func (t *coroutine) resume()
//go:export llvm.coro.destroy
func (t *coroutine) destroy()
//go:export llvm.coro.done
func (t *coroutine) done() bool
//go:export llvm.coro.promise
func (t *coroutine) _promise(alignment int32, from bool) unsafe.Pointer
// Get the promise belonging to a task.
func (t *coroutine) promise() *taskState {
return (*taskState)(t._promise(int32(unsafe.Alignof(taskState{})), false))
}
func makeGoroutine(uintptr) uintptr
// Compiler stub to get the current goroutine. Calls to this function are
// removed in the goroutine lowering pass.
func getCoroutine() *coroutine
// State/promise of a task. Internally represented as:
// State of a task. Internally represented as:
//
// {i8* next, i1 commaOk, i32/i64 data}
// {i8* next, i8* ptr, i32/i64 data}
type taskState struct {
next *coroutine
next *task
ptr unsafe.Pointer
data uint
}
@ -67,35 +32,42 @@ type taskState struct {
// TODO: runqueueFront can be removed by making the run queue a circular linked
// list. The runqueueBack will simply refer to the front in the 'next' pointer.
var (
runqueueFront *coroutine
runqueueBack *coroutine
sleepQueue *coroutine
runqueueFront *task
runqueueBack *task
sleepQueue *task
sleepQueueBaseTime timeUnit
)
// Simple logging, for debugging.
func scheduleLog(msg string) {
if schedulerDebug {
println(msg)
println("---", msg)
}
}
// Simple logging with a task pointer, for debugging.
func scheduleLogTask(msg string, t *coroutine) {
func scheduleLogTask(msg string, t *task) {
if schedulerDebug {
println(msg, t)
println("---", msg, t)
}
}
// Simple logging with a channel and task pointer.
func scheduleLogChan(msg string, ch *channel, t *task) {
if schedulerDebug {
println("---", msg, ch, t)
}
}
// Set the task to sleep for a given time.
//
// This is a compiler intrinsic.
func sleepTask(caller *coroutine, duration int64) {
func sleepTask(caller *task, duration int64) {
if schedulerDebug {
println(" set sleep:", caller, uint(duration/tickMicros))
}
promise := caller.promise()
promise.data = uint(duration / tickMicros) // TODO: longer durations
state := caller.state()
state.data = uint(duration / tickMicros) // TODO: longer durations
addSleepTask(caller)
}
@ -103,83 +75,58 @@ func sleepTask(caller *coroutine, duration int64) {
//
// This is a compiler intrinsic, and is called from a callee to reactivate the
// caller.
func activateTask(task *coroutine) {
if task == nil {
func activateTask(t *task) {
if t == nil {
return
}
scheduleLogTask(" set runnable:", task)
runqueuePushBack(task)
scheduleLogTask(" set runnable:", t)
runqueuePushBack(t)
}
// getTaskPromisePtr is a helper function to set the current .ptr field of a
// coroutine promise.
func setTaskPromisePtr(task *coroutine, value unsafe.Pointer) {
task.promise().ptr = value
}
// getTaskPromisePtr is a helper function to get the current .ptr field from a
// coroutine promise.
func getTaskPromisePtr(task *coroutine) unsafe.Pointer {
if task == nil {
blockingPanic()
}
return task.promise().ptr
}
// getTaskPromiseData is a helper function to get the current .data field of a
// coroutine promise.
func getTaskPromiseData(task *coroutine) uint {
return task.promise().data
// getTaskStateData is a helper function to get the current .data field of the
// goroutine state.
func getTaskStateData(t *task) uint {
return t.state().data
}
// Add this task to the end of the run queue. May also destroy the task if it's
// done.
func runqueuePushBack(t *coroutine) {
if t.done() {
scheduleLogTask(" destroy task:", t)
t.destroy()
return
}
func runqueuePushBack(t *task) {
if schedulerDebug {
if t.promise().next != nil {
if t.state().next != nil {
panic("runtime: runqueuePushBack: expected next task to be nil")
}
}
if runqueueBack == nil { // empty runqueue
scheduleLogTask(" add to runqueue front:", t)
runqueueBack = t
runqueueFront = t
} else {
scheduleLogTask(" add to runqueue back:", t)
lastTaskPromise := runqueueBack.promise()
lastTaskPromise.next = t
lastTaskState := runqueueBack.state()
lastTaskState.next = t
runqueueBack = t
}
}
// Get a task from the front of the run queue. Returns nil if there is none.
func runqueuePopFront() *coroutine {
func runqueuePopFront() *task {
t := runqueueFront
if t == nil {
return nil
}
if schedulerDebug {
println(" runqueuePopFront:", t)
}
promise := t.promise()
runqueueFront = promise.next
state := t.state()
runqueueFront = state.next
if runqueueFront == nil {
// Runqueue is empty now.
runqueueBack = nil
}
promise.next = nil
state.next = nil
return t
}
// Add this task to the sleep queue, assuming its state is set to sleeping.
func addSleepTask(t *coroutine) {
func addSleepTask(t *task) {
if schedulerDebug {
if t.promise().next != nil {
if t.state().next != nil {
panic("runtime: addSleepTask: expected next task to be nil")
}
}
@ -192,14 +139,14 @@ func addSleepTask(t *coroutine) {
return
}
// Make sure promise.data is relative to the queue time base.
promise := t.promise()
// Make sure state.data is relative to the queue time base.
state := t.state()
// Insert at front of sleep queue.
if promise.data < sleepQueue.promise().data {
if state.data < sleepQueue.state().data {
scheduleLog(" -> sleep at start")
sleepQueue.promise().data -= promise.data
promise.next = sleepQueue
sleepQueue.state().data -= state.data
state.next = sleepQueue
sleepQueue = t
return
}
@ -207,20 +154,20 @@ func addSleepTask(t *coroutine) {
// Add to sleep queue (in the middle or at the end).
queueIndex := sleepQueue
for {
promise.data -= queueIndex.promise().data
if queueIndex.promise().next == nil || queueIndex.promise().data > promise.data {
if queueIndex.promise().next == nil {
state.data -= queueIndex.state().data
if queueIndex.state().next == nil || queueIndex.state().data > state.data {
if queueIndex.state().next == nil {
scheduleLog(" -> sleep at end")
promise.next = nil
state.next = nil
} else {
scheduleLog(" -> sleep in middle")
promise.next = queueIndex.promise().next
promise.next.promise().data -= promise.data
state.next = queueIndex.state().next
state.next.state().data -= state.data
}
queueIndex.promise().next = t
queueIndex.state().next = t
break
}
queueIndex = queueIndex.promise().next
queueIndex = queueIndex.state().next
}
}
@ -228,18 +175,19 @@ func addSleepTask(t *coroutine) {
func scheduler() {
// Main scheduler loop.
for {
scheduleLog("\n schedule")
scheduleLog("")
scheduleLog(" schedule")
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 >= timeUnit(sleepQueue.promise().data) {
if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.state().data) {
t := sleepQueue
scheduleLogTask(" awake:", t)
promise := t.promise()
sleepQueueBaseTime += timeUnit(promise.data)
sleepQueue = promise.next
promise.next = nil
state := t.state()
sleepQueueBaseTime += timeUnit(state.data)
sleepQueue = state.next
state.next = nil
runqueuePushBack(t)
}
@ -253,7 +201,7 @@ func scheduler() {
scheduleLog(" no tasks left!")
return
}
timeLeft := timeUnit(sleepQueue.promise().data) - (now - sleepQueueBaseTime)
timeLeft := timeUnit(sleepQueue.state().data) - (now - sleepQueueBaseTime)
if schedulerDebug {
println(" sleeping...", sleepQueue, uint(timeLeft))
}
@ -268,7 +216,6 @@ func scheduler() {
}
// Run the given task.
scheduleLog(" <- runqueuePopFront")
scheduleLogTask(" run:", t)
t.resume()
}

95
src/runtime/scheduler_coroutines.go Обычный файл
Просмотреть файл

@ -0,0 +1,95 @@
// +build scheduler.coroutines
package runtime
// This file implements the Go scheduler using coroutines.
// A goroutine contains a whole stack. A coroutine is just a single function.
// How do we use coroutines for goroutines, then?
// * Every function that contains a blocking call (like sleep) is marked
// blocking, and all it's parents (callers) are marked blocking as well
// transitively until the root (main.main or a go statement).
// * A blocking function that calls a non-blocking function is called as
// usual.
// * A blocking function that calls a blocking function passes its own
// coroutine handle as a parameter to the subroutine. When the subroutine
// returns, it will re-insert the parent into the scheduler.
// Note that we use the type 'task' to refer to a coroutine, for compatibility
// with the task-based scheduler. A task type here does not represent the whole
// task, but just the topmost coroutine. For most of the scheduler, this
// difference doesn't matter.
//
// For more background on coroutines in LLVM:
// https://llvm.org/docs/Coroutines.html
import "unsafe"
// A coroutine instance, wrapped here to provide some type safety. The value
// must not be used directly, it is meant to be used as an opaque *i8 in LLVM.
type task uint8
//go:export llvm.coro.resume
func (t *task) resume()
//go:export llvm.coro.destroy
func (t *task) destroy()
//go:export llvm.coro.done
func (t *task) done() bool
//go:export llvm.coro.promise
func (t *task) _promise(alignment int32, from bool) unsafe.Pointer
// Get the state belonging to a task.
func (t *task) state() *taskState {
return (*taskState)(t._promise(int32(unsafe.Alignof(taskState{})), false))
}
func makeGoroutine(uintptr) uintptr
// Compiler stub to get the current goroutine. Calls to this function are
// removed in the goroutine lowering pass.
func getCoroutine() *task
// getTaskStatePtr is a helper function to set the current .ptr field of a
// coroutine promise.
func setTaskStatePtr(t *task, value unsafe.Pointer) {
t.state().ptr = value
}
// getTaskStatePtr is a helper function to get the current .ptr field from a
// coroutine promise.
func getTaskStatePtr(t *task) unsafe.Pointer {
if t == nil {
blockingPanic()
}
return t.state().ptr
}
//go:linkname sleep time.Sleep
func sleep(d int64) {
sleepTicks(timeUnit(d / tickMicros))
}
// deadlock is called when a goroutine cannot proceed any more, but is in theory
// not exited (so deferred calls won't run). This can happen for example in code
// like this, that blocks forever:
//
// select{}
//
// The coroutine version is implemented directly in the compiler but it needs
// this definition to work.
func deadlock()
// reactivateParent reactivates the parent goroutine. It is necessary in case of
// the coroutine-based scheduler.
func reactivateParent(t *task) {
activateTask(t)
}
// chanYield exits the current goroutine. Used in the channel implementation, to
// suspend the current goroutine until it is reactivated by a channel operation
// of a different goroutine. It is a no-op in the coroutine implementation.
func chanYield() {
// Nothing to do here, simply returning from the channel operation also exits
// the goroutine temporarily.
}

94
src/runtime/scheduler_cortexm.S Обычный файл
Просмотреть файл

@ -0,0 +1,94 @@
.section .text.tinygo_startTask
.global tinygo_startTask
.type tinygo_startTask, %function
tinygo_startTask:
// Small assembly stub for starting a goroutine. This is already run on the
// new stack, with the callee-saved registers already loaded.
// Most importantly, r4 contains the pc of the to-be-started function and r5
// contains the only argument it is given. Multiple arguments are packed
// into one by storing them in a new allocation.
// Set the first argument of the goroutine start wrapper, which contains all
// the arguments.
mov r0, r5
// Branch to the "goroutine start" function. By using blx instead of bx,
// we'll return here instead of tail calling.
blx r4
// After return, exit this goroutine. This is a tail call.
bl runtime.Goexit
.section .text.tinygo_swapTask
.global tinygo_swapTask
.type tinygo_swapTask, %function
tinygo_swapTask:
// r0 = oldTask *task
// r1 = newTask *task
// This function stores the current register state to a task struct and
// loads the state of another task to replace the current state. Apart from
// saving and restoring all relevant callee-saved registers, it also ends
// with branching to the last program counter (saved as the lr register, to
// follow the ARM calling convention).
// On pre-Thumb2 CPUs (Cortex-M0 in particular), registers r8-r15 cannot be
// used directly. Only very few operations work on them, such as mov. That's
// why the higher register values are first stored in the temporary register
// r3 when loading/storing them.
// Store state to old task. It saves the lr instead of the pc, because that
// will be the pc after returning back to the old task (in a different
// invocation of swapTask).
str r4, [r0, #0]
str r5, [r0, #4]
str r6, [r0, #8]
str r7, [r0, #12]
#if defined(__thumb2__)
str r8, [r0, #16]
str r9, [r0, #20]
str r10, [r0, #24]
str r11, [r0, #28]
str sp, [r0, #32]
str lr, [r0, #36]
#else
mov r3, r8
str r3, [r0, #16]
mov r3, r9
str r3, [r0, #20]
mov r3, r10
str r3, [r0, #24]
mov r3, r11
str r3, [r0, #28]
mov r3, sp
str r3, [r0, #32]
mov r3, lr
str r3, [r0, #36]
#endif
// Load state from new task and branch to the previous position in the
// program.
ldr r4, [r1, #0]
ldr r5, [r1, #4]
ldr r6, [r1, #8]
ldr r7, [r1, #12]
#if defined(__thumb2__)
ldr r8, [r1, #16]
ldr r9, [r1, #20]
ldr r10, [r1, #24]
ldr r11, [r1, #28]
ldr sp, [r1, #32]
#else
ldr r3, [r1, #16]
mov r8, r3
ldr r3, [r1, #20]
mov r9, r3
ldr r3, [r1, #24]
mov r10, r3
ldr r3, [r1, #28]
mov r11, r3
ldr r3, [r1, #32]
mov sp, r3
#endif
ldr r3, [r1, #36]
bx r3

125
src/runtime/scheduler_tasks.go Обычный файл
Просмотреть файл

@ -0,0 +1,125 @@
// +build scheduler.tasks
package runtime
import "unsafe"
const stackSize = 1024
// Stack canary, to detect a stack overflow. The number is a random number
// generated by random.org. The bit fiddling dance is necessary because
// otherwise Go wouldn't allow the cast to a smaller integer size.
const stackCanary = uintptr(uint64(0x670c1333b83bf575) & uint64(^uintptr(0)))
var (
schedulerState = task{canary: stackCanary}
currentTask *task // currently running goroutine, or nil
)
// This type points to the bottom of the goroutine stack and contains some state
// that must be kept with the task. The last field is a canary, which is
// necessary to make sure that no stack overflow occured when switching tasks.
type task struct {
// The order of fields in this structs must be kept in sync with assembly!
calleeSavedRegs
sp uintptr
pc uintptr
taskState
canary uintptr // used to detect stack overflows
}
// getCoroutine returns the currently executing goroutine. It is used as an
// intrinsic when compiling channel operations, but is not necessary with the
// task-based scheduler.
func getCoroutine() *task {
return currentTask
}
// state is a small helper that returns the task state, and is provided for
// compatibility with the coroutine implementation.
//go:inline
func (t *task) state() *taskState {
return &t.taskState
}
// resume is a small helper that resumes this task until this task switches back
// to the scheduler.
func (t *task) resume() {
currentTask = t
swapTask(&schedulerState, t)
currentTask = nil
}
// swapTask saves the current state to oldTask (which must contain the current
// task state) and switches to newTask. Note that this function usually does
// return, when another task (perhaps newTask) switches back to the current
// task.
//
// As an additional protection, before switching tasks, it checks whether this
// goroutine has overflowed the stack.
func swapTask(oldTask, newTask *task) {
if oldTask.canary != stackCanary {
runtimePanic("goroutine stack overflow")
}
swapTaskLower(oldTask, newTask)
}
//go:linkname swapTaskLower tinygo_swapTask
func swapTaskLower(oldTask, newTask *task)
// Goexit terminates the currently running goroutine. No other goroutines are affected.
//
// Unlike the main Go implementation, no deffered calls will be run.
//export runtime.Goexit
func Goexit() {
// Swap without rescheduling first, effectively exiting the goroutine.
swapTask(currentTask, &schedulerState)
}
// startTask is a small wrapper function that sets up the first (and only)
// argument to the new goroutine and makes sure it is exited when the goroutine
// finishes.
//go:extern tinygo_startTask
var startTask [0]uint8
// startGoroutine starts a new goroutine with the given function pointer and
// argument. It creates a new goroutine stack, prepares it for execution, and
// adds it to the runqueue.
func startGoroutine(fn, args uintptr) {
stack := alloc(stackSize)
t := (*task)(stack)
t.sp = uintptr(stack) + stackSize
t.pc = uintptr(unsafe.Pointer(&startTask))
t.prepareStartTask(fn, args)
t.canary = stackCanary
scheduleLogTask(" start goroutine:", t)
runqueuePushBack(t)
}
//go:linkname sleep time.Sleep
func sleep(d int64) {
sleepTask(currentTask, d)
swapTask(currentTask, &schedulerState)
}
// deadlock is called when a goroutine cannot proceed any more, but is in theory
// not exited (so deferred calls won't run). This can happen for example in code
// like this, that blocks forever:
//
// select{}
func deadlock() {
Goexit()
}
// reactivateParent reactivates the parent goroutine. It is a no-op for the task
// based scheduler.
func reactivateParent(t *task) {
// Nothing to do here, tasks don't stop automatically.
}
// chanYield exits the current goroutine. Used in the channel implementation, to
// suspend the current goroutine until it is reactivated by a channel operation
// of a different goroutine.
func chanYield() {
Goexit()
}

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

@ -29,6 +29,7 @@ type TargetSpec struct {
GOARCH string `json:"goarch"`
BuildTags []string `json:"build-tags"`
GC string `json:"gc"`
Scheduler string `json:"scheduler"`
Compiler string `json:"compiler"`
Linker string `json:"linker"`
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
@ -64,6 +65,9 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
if spec2.GC != "" {
spec.GC = spec2.GC
}
if spec2.Scheduler != "" {
spec.Scheduler = spec2.Scheduler
}
if spec2.Compiler != "" {
spec.Compiler = spec2.Compiler
}

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

@ -4,6 +4,7 @@
"goarch": "arm",
"compiler": "clang",
"gc": "conservative",
"scheduler": "tasks",
"linker": "ld.lld",
"rtlib": "compiler-rt",
"cflags": [
@ -20,7 +21,8 @@
"--gc-sections"
],
"extra-files": [
"src/device/arm/cortexm.s"
"src/device/arm/cortexm.s",
"src/runtime/scheduler_cortexm.S"
],
"gdb": "arm-none-eabi-gdb"
}

20
testdata/channel.go предоставленный
Просмотреть файл

@ -27,9 +27,9 @@ func main() {
// Test multi-sender.
ch = make(chan int)
go fastsender(ch)
go fastsender(ch)
go fastsender(ch)
go fastsender(ch, 10)
go fastsender(ch, 23)
go fastsender(ch, 40)
slowreceiver(ch)
// Test multi-receiver.
@ -57,9 +57,10 @@ func main() {
go fastreceiver(ch)
select {
case ch <- 5:
println("select one sent")
}
close(ch)
time.Sleep(time.Millisecond)
println("did send one")
// Test select with a single recv operation (transformed into chan recv).
select {
@ -124,17 +125,18 @@ func sendComplex(ch chan complex128) {
ch <- 7 + 10.5i
}
func fastsender(ch chan int) {
ch <- 10
ch <- 11
func fastsender(ch chan int, n int) {
ch <- n
ch <- n + 1
}
func slowreceiver(ch chan int) {
sum := 0
for i := 0; i < 6; i++ {
n := <-ch
println("got n:", n)
sum += <-ch
time.Sleep(time.Microsecond)
}
println("sum of n:", sum)
}
func slowsender(ch chan int) {

9
testdata/channel.txt предоставленный
Просмотреть файл

@ -10,12 +10,7 @@ received num: 7
received num: 8
recv from closed channel: 0 false
complex128: (+7.000000e+000+1.050000e+001i)
got n: 10
got n: 11
got n: 10
got n: 11
got n: 10
got n: 11
sum of n: 149
sum: 25
sum: 29
sum: 33
@ -23,8 +18,8 @@ sum(100): 4950
deadlocking
select no-op
after no-op
select one sent
sum: 5
did send one
select one n: 0
select n from chan: 55
select n from closed chan: 0