all: rewrite goroutine lowering

Before this commit, goroutine support was spread through the compiler.
This commit changes this support, so that the compiler itself only
generates simple intrinsics and leaves the real support to a compiler
pass that runs as one of the TinyGo-specific optimization passes.

The biggest change, that was done together with the rewrite, was support
for goroutines in WebAssembly for JavaScript. The challenge in
JavaScript is that in general no blocking operations are allowed, which
means that programs that call time.Sleep() but do not start goroutines
also have to be scheduled by the scheduler.
This commit is contained in:
Ayke van Laethem
2019-01-10 16:54:09 +01:00
parent 072ef603fe
commit 602c264749
24 changed files with 807 additions and 483 deletions
-122
View File
@@ -56,110 +56,6 @@ func signature(sig *types.Signature) string {
return s
}
// Fill in parents of all functions.
//
// All packages need to be added before this pass can run, or it will produce
// incorrect results.
func (p *Program) AnalyseCallgraph() {
for _, f := range p.Functions {
// Clear, if AnalyseCallgraph has been called before.
f.children = nil
f.parents = nil
for _, block := range f.Blocks {
for _, instr := range block.Instrs {
switch instr := instr.(type) {
case *ssa.Call:
if instr.Common().IsInvoke() {
continue
}
switch call := instr.Call.Value.(type) {
case *ssa.Builtin:
// ignore
case *ssa.Function:
if isCGoInternal(call.Name()) {
continue
}
child := p.GetFunction(call)
if child.CName() != "" {
continue // assume non-blocking
}
if child.RelString(nil) == "time.Sleep" {
f.blocking = true
}
f.children = append(f.children, child)
}
}
}
}
}
for _, f := range p.Functions {
for _, child := range f.children {
child.parents = append(child.parents, f)
}
}
}
// Analyse which functions are recursively blocking.
//
// Depends on AnalyseCallgraph.
func (p *Program) AnalyseBlockingRecursive() {
worklist := make([]*Function, 0)
// Fill worklist with directly blocking functions.
for _, f := range p.Functions {
if f.blocking {
worklist = append(worklist, f)
}
}
// Keep reducing this worklist by marking a function as recursively blocking
// from the worklist and pushing all its parents that are non-blocking.
// This is somewhat similar to a worklist in a mark-sweep garbage collector.
// The work items are then grey objects.
for len(worklist) != 0 {
// Pick the topmost.
f := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, parent := range f.parents {
if !parent.blocking {
parent.blocking = true
worklist = append(worklist, parent)
}
}
}
}
// Check whether we need a scheduler. A scheduler is only necessary when there
// are go calls that start blocking functions (if they're not blocking, the go
// function can be turned into a regular function call).
//
// Depends on AnalyseBlockingRecursive.
func (p *Program) AnalyseGoCalls() {
p.goCalls = nil
for _, f := range p.Functions {
for _, block := range f.Blocks {
for _, instr := range block.Instrs {
switch instr := instr.(type) {
case *ssa.Go:
p.goCalls = append(p.goCalls, instr)
}
}
}
}
for _, instr := range p.goCalls {
switch instr := instr.Call.Value.(type) {
case *ssa.Builtin:
case *ssa.Function:
if p.functionMap[instr].blocking {
p.needsScheduler = true
}
default:
panic("unknown go call function type")
}
}
}
// Simple pass that removes dead code. This pass makes later analysis passes
// more useful.
func (p *Program) SimpleDCE() {
@@ -239,21 +135,3 @@ func (p *Program) SimpleDCE() {
}
p.Functions = livefunctions
}
// Whether this function needs a scheduler.
//
// Depends on AnalyseGoCalls.
func (p *Program) NeedsScheduler() bool {
return p.needsScheduler
}
// Whether this function blocks. Builtins are also accepted for convenience.
// They will always be non-blocking.
//
// Depends on AnalyseBlockingRecursive.
func (p *Program) IsBlocking(f *Function) bool {
if !p.needsScheduler {
return false
}
return f.blocking
}