Add goroutines and function pointers

Этот коммит содержится в:
Ayke van Laethem 2018-06-07 14:48:24 +02:00
родитель 8df220a53b
коммит 0168bf7797
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: E97FF5335DFDFDED
10 изменённых файлов: 796 добавлений и 59 удалений

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

@ -10,6 +10,7 @@ LLVM := $(shell go env GOPATH)/src/github.com/aykevl/llvm/bindings/go/llvm/workd
LINK = $(LLVM)llvm-link LINK = $(LLVM)llvm-link
LLC = $(LLVM)llc LLC = $(LLVM)llc
LLAS = $(LLVM)llvm-as LLAS = $(LLVM)llvm-as
OPT = $(LLVM)opt
CFLAGS = -Wall -Werror -Os -g -fno-exceptions -flto -ffunction-sections -fdata-sections $(LLFLAGS) CFLAGS = -Wall -Werror -Os -g -fno-exceptions -flto -ffunction-sections -fdata-sections $(LLFLAGS)
@ -72,10 +73,15 @@ build/tgo: *.go
@mkdir -p build @mkdir -p build
go build -o build/tgo -i . go build -o build/tgo -i .
# Build textual IR with the Go compiler. # Build IR with the Go compiler.
build/%.o: src/examples/% src/examples/%/*.go build/tgo src/runtime/*.go build/runtime-$(TARGET)-combined.bc build/%.bc: src/examples/% src/examples/%/*.go build/tgo src/runtime/*.go build/runtime-$(TARGET)-combined.bc
./build/tgo $(TGOFLAGS) -printir -runtime build/runtime-$(TARGET)-combined.bc -o $@ $(subst src/,,$<) ./build/tgo $(TGOFLAGS) -printir -runtime build/runtime-$(TARGET)-combined.bc -o $@ $(subst src/,,$<)
# Compile and optimize bitcode file.
build/%.o: build/%.bc
$(OPT) -coro-early -coro-split -coro-elide -O1 -coro-cleanup -o $< $<
$(LLC) -filetype=obj -o $@ $<
# Compile C sources for the runtime. # Compile C sources for the runtime.
build/%.bc: src/runtime/%.c src/runtime/*.h build/%.bc: src/runtime/%.c src/runtime/*.h
@mkdir -p build @mkdir -p build

161
analysis.go Обычный файл
Просмотреть файл

@ -0,0 +1,161 @@
package main
import (
"golang.org/x/tools/go/ssa"
)
// Analysis results over a whole program.
type Analysis struct {
functions map[*ssa.Function]*FuncMeta
needsScheduler bool
goCalls []*ssa.Go
}
// Some analysis results of a single function.
type FuncMeta struct {
f *ssa.Function
blocking bool
parents []*ssa.Function // calculated by AnalyseCallgraph
children []*ssa.Function
}
// Return a new Analysis object.
func NewAnalysis() *Analysis {
return &Analysis{
functions: make(map[*ssa.Function]*FuncMeta),
}
}
// Add a given package to the analyzer, to be analyzed later.
func (a *Analysis) AddPackage(pkg *ssa.Package) {
for _, member := range pkg.Members {
switch member := member.(type) {
case *ssa.Function:
a.addFunction(member)
case *ssa.Type:
ms := pkg.Prog.MethodSets.MethodSet(member.Type())
for i := 0; i < ms.Len(); i++ {
a.addFunction(pkg.Prog.MethodValue(ms.At(i)))
}
}
}
}
// Analyze the given function quickly without any recursion, and add it to the
// list of functions in the analyzer.
func (a *Analysis) addFunction(f *ssa.Function) {
fm := &FuncMeta{}
for _, block := range f.Blocks {
for _, instr := range block.Instrs {
switch instr := instr.(type) {
case *ssa.Call:
switch call := instr.Call.Value.(type) {
case *ssa.Function:
name := getFunctionName(call, false)
if name == "runtime.Sleep" {
fm.blocking = true
}
fm.children = append(fm.children, call)
}
case *ssa.Go:
a.goCalls = append(a.goCalls, instr)
}
}
}
a.functions[f] = fm
for _, child := range f.AnonFuncs {
a.addFunction(child)
}
}
// Fill in parents of all functions.
//
// All packages need to be added before this pass can run, or it will produce
// incorrect results.
func (a *Analysis) AnalyseCallgraph() {
for f, fm := range a.functions {
for _, child := range fm.children {
childRes, ok := a.functions[child]
if !ok {
print("child not found: " + child.Pkg.Pkg.Path() + "." + child.Name() + ", function: " + f.Name())
continue
}
childRes.parents = append(childRes.parents, f)
}
}
}
// Analyse which functions are recursively blocking.
//
// Depends on AnalyseCallgraph.
func (a *Analysis) AnalyseBlockingRecursive() {
worklist := make([]*FuncMeta, 0)
// Fill worklist with directly blocking functions.
for _, fm := range a.functions {
if fm.blocking {
worklist = append(worklist, fm)
}
}
// 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.
fm := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, parent := range fm.parents {
parentfm := a.functions[parent]
if !parentfm.blocking {
parentfm.blocking = true
worklist = append(worklist, parentfm)
}
}
}
}
// Check whether we need a scheduler. This 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 (a *Analysis) AnalyseGoCalls() {
for _, instr := range a.goCalls {
if a.isBlocking(instr.Call.Value) {
a.needsScheduler = true
}
}
}
// Whether this function needs a scheduler.
//
// Depends on AnalyseGoCalls.
func (a *Analysis) NeedsScheduler() bool {
return a.needsScheduler
}
// Whether this function blocks. Builtins are also accepted for convenience.
// They will always be non-blocking.
//
// Depends on AnalyseBlockingRecursive.
func (a *Analysis) IsBlocking(f ssa.Value) bool {
if !a.needsScheduler {
return false
}
return a.isBlocking(f)
}
func (a *Analysis) isBlocking(f ssa.Value) bool {
switch f := f.(type) {
case *ssa.Builtin:
return false
case *ssa.Function:
return a.functions[f].blocking
default:
panic("Analysis.IsBlocking on unknown type")
}
}

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

@ -7,15 +7,34 @@ import (
) )
func main() { func main() {
led := machine.GPIO{17} // LED 1 on the PCA10040 go led1()
led2()
}
func led1() {
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT}) led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
for { for {
println("LED on") println("+")
led.Set(false) led.Low()
runtime.Sleep(runtime.Millisecond * 500) runtime.Sleep(runtime.Millisecond * 1000)
println("LED off") println("-")
led.Set(true) led.High()
runtime.Sleep(runtime.Millisecond * 500) runtime.Sleep(runtime.Millisecond * 1000)
}
}
func led2() {
led := machine.GPIO{machine.LED2}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
for {
println(" +")
led.Low()
runtime.Sleep(runtime.Millisecond * 420)
println(" -")
led.High()
runtime.Sleep(runtime.Millisecond * 420)
} }
} }

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

@ -25,6 +25,16 @@ func main() {
printItf(5) printItf(5)
printItf(byte('x')) printItf(byte('x'))
printItf("foo") printItf("foo")
runFunc(hello) // must be indirect to avoid obvious inlining
}
func runFunc(f func()) {
f()
}
func hello() {
println("hello from function pointer!")
} }
func strlen(s string) int { func strlen(s string) int {

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

@ -6,6 +6,13 @@ const Compiler = "tgo"
// The bitness of the CPU (e.g. 8, 32, 64). Set by the compiler as a constant. // The bitness of the CPU (e.g. 8, 32, 64). Set by the compiler as a constant.
var TargetBits uint8 var TargetBits uint8
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 _panic(message interface{}) { func _panic(message interface{}) {
printstring("panic: ") printstring("panic: ")
printitf(message) printitf(message)

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

@ -2,9 +2,26 @@ source_filename = "runtime/runtime.ll"
declare void @runtime.initAll() declare void @runtime.initAll()
declare void @main.main() declare void @main.main()
declare i8* @main.main$async(i8*)
declare void @runtime.scheduler(i8*)
; Will be changed to true if there are 'go' statements in the compiled program.
@.has_scheduler = private unnamed_addr constant i1 false
define i32 @main() { define i32 @main() {
call void @runtime.initAll() call void @runtime.initAll()
%has_scheduler = load i1, i1* @.has_scheduler
; This branch will be optimized away. Only one of the targets will remain.
br i1 %has_scheduler, label %with_scheduler, label %without_scheduler
with_scheduler:
; Initialize main and run the scheduler.
%main = call i8* @main.main$async(i8* null)
call void @runtime.scheduler(i8* %main)
ret i32 0
without_scheduler:
; No scheduler is necessary. Call main directly.
call void @main.main() call void @main.main()
ret i32 0 ret i32 0
} }

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

@ -45,8 +45,32 @@ func putchar(c byte) {
nrf.UART0.EVENTS_TXDRDY = 0 nrf.UART0.EVENTS_TXDRDY = 0
} }
func Sleep(d Duration) { func sleep(d Duration) {
C.rtc_sleep(C.uint32_t(d / 32)) // TODO: not accurate (must be d / 30.5175...) ticks64 := d / 32
for ticks64 != 0 {
monotime() // update timestamp
ticks := uint32(ticks64) & 0x7fffff // 23 bits (to be on the safe side)
C.rtc_sleep(C.uint32_t(ticks)) // TODO: not accurate (must be d / 30.5175...)
ticks64 -= Duration(ticks)
}
}
var (
timestamp uint64 // microseconds since boottime
rtcLastCounter uint32 // 24 bits ticks
)
// Monotonically increasing numer of microseconds 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 {
rtcCounter := uint32(nrf.RTC0.COUNTER)
offset := (rtcCounter - rtcLastCounter) % 0xffffff // change since last measurement
rtcLastCounter = rtcCounter
timestamp += uint64(offset * 32) // TODO: not precise
return timestamp
} }
func abort() { func abort() {

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

@ -10,6 +10,7 @@ import (
// #include <stdio.h> // #include <stdio.h>
// #include <stdlib.h> // #include <stdlib.h>
// #include <unistd.h> // #include <unistd.h>
// #include <time.h>
import "C" import "C"
const Microsecond = 1 const Microsecond = 1
@ -18,10 +19,20 @@ func putchar(c byte) {
C.putchar(C.int(c)) C.putchar(C.int(c))
} }
func Sleep(d Duration) { func sleep(d Duration) {
C.usleep(C.useconds_t(d)) C.usleep(C.useconds_t(d))
} }
// Return monotonic time in microseconds.
//
// TODO: use nanoseconds?
// TODO: noescape
func monotime() uint64 {
var ts C.struct_timespec
C.clock_gettime(C.CLOCK_MONOTONIC, &ts)
return uint64(ts.tv_sec) * 1000 * 1000 + uint64(ts.tv_nsec) / 1000
}
func abort() { func abort() {
C.abort() C.abort()
} }
@ -35,5 +46,5 @@ func alloc(size uintptr) unsafe.Pointer {
} }
func free(ptr unsafe.Pointer) { func free(ptr unsafe.Pointer) {
C.free(ptr) //C.free(ptr) // TODO
} }

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

@ -0,0 +1,249 @@
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 and will make sure it's
// own coroutine is removed from the scheduler. 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 isn't
// aware of the difference.
//
// For more background on coroutines in LLVM:
// https://llvm.org/docs/Coroutines.html
import (
"unsafe"
)
// State/promise of a task. Internally represented as:
//
// {i8 state, i32 data, i8* next}
type taskState struct {
state uint8
data uint32
next taskInstance
}
// Pointer to a task. Wrap unsafe.Pointer to provide some sort of type safety.
type taskInstance unsafe.Pointer
// Various states a task can be in. Not always updated (especially
// TASK_STATE_RUNNABLE).
const (
TASK_STATE_RUNNABLE = iota
TASK_STATE_SLEEP
TASK_STATE_CALL // waiting for a sub-coroutine
)
// Queues used by the scheduler.
//
// 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 taskInstance
runqueueBack taskInstance
sleepQueue taskInstance
sleepQueueBaseTime uint64
)
// Translated to void @llvm.coro.resume(i8*).
func _llvm_coro_resume(taskInstance)
// Translated to void @llvm.coro.destroy(i8*).
func _llvm_coro_destroy(taskInstance)
// Translated to i1 @llvm.coro.done(i8*).
func _llvm_coro_done(taskInstance) bool
// Translated to i8* @llvm.coro.promise(i8*, i32, i1).
func _llvm_coro_promise(taskInstance, int32, bool) unsafe.Pointer
// Get the promise belonging to a task.
func taskPromise(t taskInstance) *taskState {
return (*taskState)(_llvm_coro_promise(t, 4, false))
}
// Simple logging, for debugging.
func scheduleLog(msg string) {
//println(msg)
}
// Simple logging with a task pointer, for debugging.
func scheduleLogTask(msg string, t taskInstance) {
//println(msg, t)
}
// Set the task state to sleep for a given time.
//
// This is a compiler intrinsic.
func sleepTask(caller taskInstance, duration Duration) {
promise := taskPromise(caller)
promise.state = TASK_STATE_SLEEP
promise.data = uint32(duration) // TODO: longer durations
}
// Wait for the result of an async call. This means that the parent goroutine
// will be removed from the runqueue and be rescheduled by the callee.
//
// This is a compiler intrinsic.
func waitForAsyncCall(caller taskInstance) {
promise := taskPromise(caller)
promise.state = TASK_STATE_CALL
}
// Add a task to the runnable or sleep queue, depending on the state.
//
// This is a compiler intrinsic.
func scheduleTask(t taskInstance) {
if t == nil {
return
}
scheduleLogTask(" schedule task:", t)
// See what we should do with this task: try to execute it directly
// again or let it sleep for a bit.
promise := taskPromise(t)
if promise.state == TASK_STATE_CALL {
return // calling an async task, the subroutine will re-active the parent
} else if promise.state == TASK_STATE_SLEEP && promise.data != 0 {
addSleepTask(t)
} else {
pushTask(t)
}
}
// Add this task to the end of the run queue. May also destroy the task if it's
// done.
func pushTask(t taskInstance) {
if _llvm_coro_done(t) {
scheduleLogTask(" destroy task:", t)
_llvm_coro_destroy(t)
return
}
if runqueueBack == nil { // empty runqueue
runqueueBack = t
runqueueFront = t
} else {
lastTaskPromise := taskPromise(runqueueBack)
lastTaskPromise.next = t
runqueueBack = t
}
}
// Get a task from the front of the run queue. May return nil if there is none.
func popTask() taskInstance {
t := runqueueFront
if t == nil {
return nil
}
scheduleLogTask(" popTask:", t)
promise := taskPromise(t)
runqueueFront = promise.next
if runqueueFront == nil {
runqueueBack = nil
}
promise.next = nil
return t
}
// Add this task to the sleep queue, assuming its state is set to sleeping.
func addSleepTask(t taskInstance) {
now := monotime()
if sleepQueue == nil {
scheduleLog(" -> sleep new queue")
// Create new linked list for the sleep queue.
sleepQueue = t
sleepQueueBaseTime = now
return
}
// Make sure promise.data is relative to the queue time base.
promise := taskPromise(t)
// Insert at front of sleep queue.
if promise.data < taskPromise(sleepQueue).data {
scheduleLog(" -> sleep at start")
taskPromise(sleepQueue).data -= promise.data
promise.next = sleepQueue
sleepQueue = t
return
}
// Add to sleep queue (in the middle or at the end).
queueIndex := sleepQueue
for {
promise.data -= taskPromise(queueIndex).data
if taskPromise(queueIndex).next == nil || taskPromise(queueIndex).data > promise.data {
if taskPromise(queueIndex).next == nil {
scheduleLog(" -> sleep at end")
promise.next = nil
} else {
scheduleLog(" -> sleep in middle")
promise.next = taskPromise(queueIndex).next
taskPromise(promise.next).data -= promise.data
}
taskPromise(queueIndex).next = t
break
}
queueIndex = taskPromise(queueIndex).next
}
}
// Run the scheduler until all tasks have finished.
// It takes an initial task (main.main) to bootstrap.
func scheduler(main taskInstance) {
// Initial task.
scheduleTask(main)
// Main scheduler loop.
for {
scheduleLog("\n schedule")
now := monotime()
// Add tasks that are done sleeping to the end of the runqueue so they
// will be executed soon.
if sleepQueue != nil && now - sleepQueueBaseTime >= uint64(taskPromise(sleepQueue).data) {
scheduleLog(" run <- sleep")
t := sleepQueue
promise := taskPromise(t)
sleepQueueBaseTime += uint64(promise.data)
sleepQueue = promise.next
promise.next = nil
pushTask(t)
}
scheduleLog(" <- popTask")
t := popTask()
if t == nil {
if sleepQueue == nil {
// No more tasks to execute.
// It would be nice if we could detect deadlocks here, because
// there might still be functions waiting on each other in a
// deadlock.
scheduleLog(" no tasks left!")
return
}
scheduleLog(" sleeping...")
timeLeft := uint64(taskPromise(sleepQueue).data) - (now - sleepQueueBaseTime)
sleep(Duration(timeLeft))
continue
}
// Run the given task.
scheduleLogTask(" run:", t)
_llvm_coro_resume(t)
// Add the just resumed task to the run queue or the sleep queue.
scheduleTask(t)
}
}

289
tgo.go
Просмотреть файл

@ -41,19 +41,33 @@ type Compiler struct {
stringType llvm.Type stringType llvm.Type
interfaceType llvm.Type interfaceType llvm.Type
typeassertType llvm.Type typeassertType llvm.Type
taskDataType llvm.Type
allocFunc llvm.Value allocFunc llvm.Value
freeFunc llvm.Value freeFunc llvm.Value
coroIdFunc llvm.Value
coroSizeFunc llvm.Value
coroBeginFunc llvm.Value
coroSuspendFunc llvm.Value
coroEndFunc llvm.Value
coroFreeFunc llvm.Value
itfTypeNumbers map[types.Type]uint64 itfTypeNumbers map[types.Type]uint64
itfTypes []types.Type itfTypes []types.Type
initFuncs []llvm.Value initFuncs []llvm.Value
analysis *Analysis
} }
type Frame struct { type Frame struct {
fn *ssa.Function
llvmFn llvm.Value llvmFn llvm.Value
params map[*ssa.Parameter]int // arguments to the function params map[*ssa.Parameter]int // arguments to the function
locals map[ssa.Value]llvm.Value // local variables locals map[ssa.Value]llvm.Value // local variables
blocks map[*ssa.BasicBlock]llvm.BasicBlock blocks map[*ssa.BasicBlock]llvm.BasicBlock
phis []Phi phis []Phi
blocking bool
taskState llvm.Value
taskHandle llvm.Value
cleanupBlock llvm.BasicBlock
suspendBlock llvm.BasicBlock
} }
func pkgPrefix(pkg *ssa.Package) string { func pkgPrefix(pkg *ssa.Package) string {
@ -72,6 +86,7 @@ func NewCompiler(pkgName, triple string) (*Compiler, error) {
c := &Compiler{ c := &Compiler{
triple: triple, triple: triple,
itfTypeNumbers: make(map[types.Type]uint64), itfTypeNumbers: make(map[types.Type]uint64),
analysis: NewAnalysis(),
} }
target, err := llvm.GetTargetFromTriple(triple) target, err := llvm.GetTargetFromTriple(triple)
@ -100,12 +115,33 @@ func NewCompiler(pkgName, triple string) (*Compiler, error) {
// Go typeassert result: tuple of (ptr, bool) // Go typeassert result: tuple of (ptr, bool)
c.typeassertType = llvm.StructType([]llvm.Type{c.i8ptrType, llvm.Int1Type()}, false) c.typeassertType = llvm.StructType([]llvm.Type{c.i8ptrType, llvm.Int1Type()}, false)
// Goroutine / task data: {i8 state, i32 data, i8* next}
c.taskDataType = llvm.StructType([]llvm.Type{llvm.Int8Type(), llvm.Int32Type(), c.i8ptrType}, false)
allocType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.uintptrType}, false) allocType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.uintptrType}, false)
c.allocFunc = llvm.AddFunction(c.mod, "runtime.alloc", allocType) c.allocFunc = llvm.AddFunction(c.mod, "runtime.alloc", allocType)
freeType := llvm.FunctionType(llvm.VoidType(), []llvm.Type{c.i8ptrType}, false) freeType := llvm.FunctionType(llvm.VoidType(), []llvm.Type{c.i8ptrType}, false)
c.freeFunc = llvm.AddFunction(c.mod, "runtime.free", freeType) c.freeFunc = llvm.AddFunction(c.mod, "runtime.free", freeType)
coroIdType := llvm.FunctionType(c.ctx.TokenType(), []llvm.Type{llvm.Int32Type(), c.i8ptrType, c.i8ptrType, c.i8ptrType}, false)
c.coroIdFunc = llvm.AddFunction(c.mod, "llvm.coro.id", coroIdType)
coroSizeType := llvm.FunctionType(llvm.Int32Type(), nil, false)
c.coroSizeFunc = llvm.AddFunction(c.mod, "llvm.coro.size.i32", coroSizeType)
coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
c.coroBeginFunc = llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType)
coroSuspendType := llvm.FunctionType(llvm.Int8Type(), []llvm.Type{c.ctx.TokenType(), llvm.Int1Type()}, false)
c.coroSuspendFunc = llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType)
coroEndType := llvm.FunctionType(llvm.Int1Type(), []llvm.Type{c.i8ptrType, llvm.Int1Type()}, false)
c.coroEndFunc = llvm.AddFunction(c.mod, "llvm.coro.end", coroEndType)
coroFreeType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
c.coroFreeFunc = llvm.AddFunction(c.mod, "llvm.coro.free", coroFreeType)
return c, nil return c, nil
} }
@ -188,6 +224,13 @@ func (c *Compiler) Parse(mainPath string, buildTags []string) error {
} }
} }
for _, pkg := range packageList {
c.analysis.AddPackage(pkg)
}
c.analysis.AnalyseCallgraph() // set up callgraph
c.analysis.AnalyseBlockingRecursive() // make all parents of blocking calls blocking (transitively)
c.analysis.AnalyseGoCalls() // check whether we need a scheduler
// Transform each package into LLVM IR. // Transform each package into LLVM IR.
for _, pkg := range packageList { for _, pkg := range packageList {
err := c.parsePackage(program, pkg) err := c.parsePackage(program, pkg)
@ -214,7 +257,19 @@ func (c *Compiler) Parse(mainPath string, buildTags []string) error {
// Set functions referenced in runtime.ll to internal linkage, to improve // Set functions referenced in runtime.ll to internal linkage, to improve
// optimization (hopefully). // optimization (hopefully).
main := c.mod.NamedFunction("main.main") main := c.mod.NamedFunction("main.main")
if !main.IsDeclaration() {
main.SetLinkage(llvm.PrivateLinkage) main.SetLinkage(llvm.PrivateLinkage)
}
mainAsync := c.mod.NamedFunction("main.main$async")
if !mainAsync.IsDeclaration() {
mainAsync.SetLinkage(llvm.PrivateLinkage)
}
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.PrivateLinkage)
if c.analysis.NeedsScheduler() {
// Enable the scheduler.
c.mod.NamedGlobal(".has_scheduler").SetInitializer(llvm.ConstInt(llvm.Int1Type(), 1, false))
}
return nil return nil
} }
@ -260,6 +315,32 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) {
return llvm.Type{}, err return llvm.Type{}, err
} }
return llvm.PointerType(ptrTo, 0), nil return llvm.PointerType(ptrTo, 0), nil
case *types.Signature: // function pointer
// return value
var err error
var returnType llvm.Type
if typ.Results().Len() == 0 {
returnType = llvm.VoidType()
} else if typ.Results().Len() == 1 {
returnType, err = c.getLLVMType(typ.Results().At(0).Type())
if err != nil {
return llvm.Type{}, err
}
} else {
return llvm.Type{}, errors.New("todo: multiple return values in function pointer")
}
// param values
var paramTypes []llvm.Type
params := typ.Params()
for i := 0; i < params.Len(); i++ {
subType, err := c.getLLVMType(params.At(i).Type())
if err != nil {
return llvm.Type{}, err
}
paramTypes = append(paramTypes, subType)
}
// make a function pointer of it
return llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), 0), nil
case *types.Struct: case *types.Struct:
members := make([]llvm.Type, typ.NumFields()) members := make([]llvm.Type, typ.NumFields())
for i := 0; i < typ.NumFields(); i++ { for i := 0; i < typ.NumFields(); i++ {
@ -327,18 +408,22 @@ func (c *Compiler) isPointer(typ types.Type) bool {
} }
} }
func getFunctionName(fn *ssa.Function) string { func getFunctionName(fn *ssa.Function, blocking bool) string {
suffix := ""
if blocking {
suffix = "$async"
}
if fn.Signature.Recv() != nil { if fn.Signature.Recv() != nil {
// Method on a defined type. // Method on a defined type.
typeName := fn.Params[0].Type().(*types.Named).Obj().Name() typeName := fn.Params[0].Type().(*types.Named).Obj().Name()
return pkgPrefix(fn.Pkg) + "." + typeName + "." + fn.Name() return pkgPrefix(fn.Pkg) + "." + typeName + "." + fn.Name() + suffix
} else { } else {
// Bare function. // Bare function.
if strings.HasPrefix(fn.Name(), "_Cfunc_") { if strings.HasPrefix(fn.Name(), "_Cfunc_") {
// Name CGo functions directly. // Name CGo functions directly.
return fn.Name()[len("_Cfunc_"):] return fn.Name()[len("_Cfunc_"):]
} else { } else {
name := pkgPrefix(fn.Pkg) + "." + fn.Name() name := pkgPrefix(fn.Pkg) + "." + fn.Name() + suffix
if fn.Pkg.Pkg.Path() == "runtime" && strings.HasPrefix(fn.Name(), "_llvm_") { if fn.Pkg.Pkg.Path() == "runtime" && strings.HasPrefix(fn.Name(), "_llvm_") {
// Special case for LLVM intrinsics in the runtime. // Special case for LLVM intrinsics in the runtime.
name = "llvm." + strings.Replace(fn.Name()[len("_llvm_"):], "_", ".", -1) name = "llvm." + strings.Replace(fn.Name()[len("_llvm_"):], "_", ".", -1)
@ -488,13 +573,20 @@ func (c *Compiler) parseFuncDecl(f *ssa.Function) (*Frame, error) {
f.WriteTo(os.Stdout) f.WriteTo(os.Stdout)
frame := &Frame{ frame := &Frame{
fn: f,
params: make(map[*ssa.Parameter]int), params: make(map[*ssa.Parameter]int),
locals: make(map[ssa.Value]llvm.Value), locals: make(map[ssa.Value]llvm.Value),
blocks: make(map[*ssa.BasicBlock]llvm.BasicBlock), blocks: make(map[*ssa.BasicBlock]llvm.BasicBlock),
blocking: c.analysis.IsBlocking(f),
} }
var retType llvm.Type var retType llvm.Type
if f.Signature.Results() == nil { if frame.blocking {
if f.Signature.Results() != nil {
return nil, errors.New("todo: return values in blocking function")
}
retType = c.i8ptrType
} else if f.Signature.Results() == nil {
retType = llvm.VoidType() retType = llvm.VoidType()
} else if f.Signature.Results().Len() == 1 { } else if f.Signature.Results().Len() == 1 {
var err error var err error
@ -507,6 +599,9 @@ func (c *Compiler) parseFuncDecl(f *ssa.Function) (*Frame, error) {
} }
var paramTypes []llvm.Type var paramTypes []llvm.Type
if frame.blocking {
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
}
for i, param := range f.Params { for i, param := range f.Params {
paramType, err := c.getLLVMType(param.Type()) paramType, err := c.getLLVMType(param.Type())
if err != nil { if err != nil {
@ -518,7 +613,7 @@ func (c *Compiler) parseFuncDecl(f *ssa.Function) (*Frame, error) {
fnType := llvm.FunctionType(retType, paramTypes, false) fnType := llvm.FunctionType(retType, paramTypes, false)
name := getFunctionName(f) name := getFunctionName(f, frame.blocking)
frame.llvmFn = c.mod.NamedFunction(name) frame.llvmFn = c.mod.NamedFunction(name)
if frame.llvmFn.IsNil() { if frame.llvmFn.IsNil() {
frame.llvmFn = llvm.AddFunction(c.mod, name, fnType) frame.llvmFn = llvm.AddFunction(c.mod, name, fnType)
@ -624,6 +719,10 @@ func (c *Compiler) parseFunc(frame *Frame, f *ssa.Function) error {
llvmBlock := c.ctx.AddBasicBlock(frame.llvmFn, block.Comment) llvmBlock := c.ctx.AddBasicBlock(frame.llvmFn, block.Comment)
frame.blocks[block] = llvmBlock frame.blocks[block] = llvmBlock
} }
if frame.blocking {
frame.cleanupBlock = c.ctx.AddBasicBlock(frame.llvmFn, "task.cleanup")
frame.suspendBlock = c.ctx.AddBasicBlock(frame.llvmFn, "task.suspend")
}
// Load function parameters // Load function parameters
for _, param := range f.Params { for _, param := range f.Params {
@ -631,7 +730,41 @@ func (c *Compiler) parseFunc(frame *Frame, f *ssa.Function) error {
frame.locals[param] = llvmParam frame.locals[param] = llvmParam
} }
// Fill those blocks with instructions. if frame.blocking {
// Coroutine initialization.
c.builder.SetInsertPointAtEnd(frame.blocks[f.Blocks[0]])
frame.taskState = c.builder.CreateAlloca(c.taskDataType, "task.state")
stateI8 := c.builder.CreateBitCast(frame.taskState, c.i8ptrType, "task.state.i8")
id := c.builder.CreateCall(c.coroIdFunc, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
stateI8,
llvm.ConstNull(c.i8ptrType),
llvm.ConstNull(c.i8ptrType),
}, "task.token")
size := c.builder.CreateCall(c.coroSizeFunc, nil, "task.size")
if c.targetData.TypeAllocSize(size.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
size = c.builder.CreateTrunc(size, c.uintptrType, "task.size.uintptr")
} else if c.targetData.TypeAllocSize(size.Type()) < c.targetData.TypeAllocSize(c.uintptrType) {
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
}
data := c.builder.CreateCall(c.allocFunc, []llvm.Value{size}, "task.data")
frame.taskHandle = c.builder.CreateCall(c.coroBeginFunc, []llvm.Value{id, data}, "task.handle")
// Coroutine cleanup. Free resources associated with this coroutine.
c.builder.SetInsertPointAtEnd(frame.cleanupBlock)
mem := c.builder.CreateCall(c.coroFreeFunc, []llvm.Value{id, frame.taskHandle}, "task.data.free")
c.builder.CreateCall(c.freeFunc, []llvm.Value{mem}, "")
// re-insert parent coroutine
c.builder.CreateCall(c.mod.NamedFunction("runtime.scheduleTask"), []llvm.Value{frame.llvmFn.FirstParam()}, "")
c.builder.CreateBr(frame.suspendBlock)
// Coroutine suspend. A call to llvm.coro.suspend() will branch here.
c.builder.SetInsertPointAtEnd(frame.suspendBlock)
c.builder.CreateCall(c.coroEndFunc, []llvm.Value{frame.taskHandle, llvm.ConstInt(llvm.Int1Type(), 0, false)}, "unused")
c.builder.CreateRet(frame.taskHandle)
}
// Fill blocks with instructions.
for _, block := range f.DomPreorder() { for _, block := range f.DomPreorder() {
c.builder.SetInsertPointAtEnd(frame.blocks[block]) c.builder.SetInsertPointAtEnd(frame.blocks[block])
for _, instr := range block.Instrs { for _, instr := range block.Instrs {
@ -664,6 +797,27 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error {
value, err := c.parseExpr(frame, instr) value, err := c.parseExpr(frame, instr)
frame.locals[instr] = value frame.locals[instr] = value
return err return err
case *ssa.Go:
if instr.Common().Method != nil {
return errors.New("todo: go on method receiver")
}
// Execute non-blocking calls (including builtins) directly.
// parentHandle param is ignored.
if !c.analysis.IsBlocking(instr.Common().Value) {
_, err := c.parseCall(frame, instr.Common(), llvm.Value{})
return err // probably nil
}
// Start this goroutine.
// parentHandle is nil, as the goroutine has no parent frame (it's a new
// stack).
handle, err := c.parseCall(frame, instr.Common(), llvm.Value{})
if err != nil {
return err
}
c.builder.CreateCall(c.mod.NamedFunction("runtime.scheduleTask"), []llvm.Value{handle}, "")
return nil
case *ssa.If: case *ssa.If:
cond, err := c.parseExpr(frame, instr.Cond) cond, err := c.parseExpr(frame, instr.Cond)
if err != nil { if err != nil {
@ -687,6 +841,19 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error {
c.builder.CreateUnreachable() c.builder.CreateUnreachable()
return nil return nil
case *ssa.Return: case *ssa.Return:
if frame.blocking {
if len(instr.Results) != 0 {
return errors.New("todo: return values from blocking function")
}
// Final suspend.
continuePoint := c.builder.CreateCall(c.coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(llvm.Int1Type(), 1, false), // final=true
}, "")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(llvm.Int8Type(), 1, false), frame.cleanupBlock)
return nil
} else {
if len(instr.Results) == 0 { if len(instr.Results) == 0 {
c.builder.CreateRetVoid() c.builder.CreateRetVoid()
return nil return nil
@ -700,6 +867,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error {
} else { } else {
return errors.New("todo: return value") return errors.New("todo: return value")
} }
}
case *ssa.Store: case *ssa.Store:
llvmAddr, err := c.parseExpr(frame, instr.Addr) llvmAddr, err := c.parseExpr(frame, instr.Addr)
if err != nil { if err != nil {
@ -797,16 +965,17 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string)
} }
} }
func (c *Compiler) parseFunctionCall(frame *Frame, call *ssa.CallCommon, fn *ssa.Function) (llvm.Value, error) { func (c *Compiler) parseFunctionCall(frame *Frame, call *ssa.CallCommon, llvmFn llvm.Value, blocking bool, parentHandle llvm.Value) (llvm.Value, error) {
fmt.Printf(" function: %s\n", fn)
name := getFunctionName(fn)
target := c.mod.NamedFunction(name)
if target.IsNil() {
return llvm.Value{}, errors.New("undefined function: " + name)
}
var params []llvm.Value var params []llvm.Value
if blocking {
if parentHandle.IsNil() {
// Started from 'go' statement.
params = append(params, llvm.ConstNull(c.i8ptrType))
} else {
// Blocking function calls another blocking function.
params = append(params, parentHandle)
}
}
for _, param := range call.Args { for _, param := range call.Args {
val, err := c.parseExpr(frame, param) val, err := c.parseExpr(frame, param)
if err != nil { if err != nil {
@ -815,19 +984,75 @@ func (c *Compiler) parseFunctionCall(frame *Frame, call *ssa.CallCommon, fn *ssa
params = append(params, val) params = append(params, val)
} }
return c.builder.CreateCall(target, params, ""), nil if frame.blocking && llvmFn.Name() == "runtime.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]}, "")
// Yield to scheduler.
continuePoint := c.builder.CreateCall(c.coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(llvm.Int1Type(), 0, false),
}, "")
wakeup := c.ctx.InsertBasicBlock(llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(llvm.Int8Type(), 0, false), wakeup)
sw.AddCase(llvm.ConstInt(llvm.Int8Type(), 1, false), frame.cleanupBlock)
c.builder.SetInsertPointAtEnd(wakeup)
return llvm.Value{}, nil
}
result := c.builder.CreateCall(llvmFn, params, "")
if blocking && !parentHandle.IsNil() {
// Calling a blocking function as a regular function call.
// This is done by passing the current coroutine as a parameter to the
// new coroutine and dropping the current coroutine from the scheduler
// (with the TASK_STATE_CALL state). When the subroutine is finished, it
// will reactivate the parent (this frame) in it's destroy function.
c.builder.CreateCall(c.mod.NamedFunction("runtime.scheduleTask"), []llvm.Value{result}, "")
// Set task state to TASK_STATE_CALL.
c.builder.CreateCall(c.mod.NamedFunction("runtime.waitForAsyncCall"), []llvm.Value{frame.taskHandle}, "")
// Yield to the scheduler.
continuePoint := c.builder.CreateCall(c.coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(llvm.Int1Type(), 0, false),
}, "")
resume := c.ctx.InsertBasicBlock(llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.callComplete")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(llvm.Int8Type(), 0, false), resume)
sw.AddCase(llvm.ConstInt(llvm.Int8Type(), 1, false), frame.cleanupBlock)
c.builder.SetInsertPointAtEnd(resume)
}
return result, nil
} }
func (c *Compiler) parseCall(frame *Frame, instr *ssa.Call) (llvm.Value, error) { func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon, parentHandle llvm.Value) (llvm.Value, error) {
fmt.Printf(" call: %s\n", instr) switch call := instr.Value.(type) {
switch call := instr.Common().Value.(type) {
case *ssa.Builtin: case *ssa.Builtin:
return c.parseBuiltin(frame, instr.Common().Args, call.Name()) return c.parseBuiltin(frame, instr.Args, call.Name())
case *ssa.Function: case *ssa.Function:
return c.parseFunctionCall(frame, instr.Common(), call) targetBlocks := false
default: name := getFunctionName(call, targetBlocks)
return llvm.Value{}, errors.New("todo: unknown call type: " + fmt.Sprintf("%#v", call)) llvmFn := c.mod.NamedFunction(name)
if llvmFn.IsNil() {
targetBlocks = true
nameAsync := getFunctionName(call, targetBlocks)
llvmFn = c.mod.NamedFunction(nameAsync)
if llvmFn.IsNil() {
return llvm.Value{}, errors.New("undefined function: " + name)
}
}
return c.parseFunctionCall(frame, instr, llvmFn, targetBlocks, parentHandle)
default: // function pointer
value, err := c.parseExpr(frame, instr.Value)
if err != nil {
return llvm.Value{}, err
}
// TODO: blocking function pointers (needs analysis)
return c.parseFunctionCall(frame, instr, value, false, parentHandle)
} }
} }
@ -866,7 +1091,9 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
case *ssa.BinOp: case *ssa.BinOp:
return c.parseBinOp(frame, expr) return c.parseBinOp(frame, expr)
case *ssa.Call: case *ssa.Call:
return c.parseCall(frame, expr) // Passing the current task here to the subroutine. It is only used when
// the subroutine is blocking.
return c.parseCall(frame, expr.Common(), frame.taskHandle)
case *ssa.ChangeType: case *ssa.ChangeType:
return c.parseConvert(frame, expr.Type(), expr.X) return c.parseConvert(frame, expr.Type(), expr.X)
case *ssa.Const: case *ssa.Const:
@ -890,6 +1117,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
llvm.ConstInt(llvm.Int32Type(), uint64(expr.Field), false), llvm.ConstInt(llvm.Int32Type(), uint64(expr.Field), false),
} }
return c.builder.CreateGEP(val, indices, ""), nil return c.builder.CreateGEP(val, indices, ""), nil
case *ssa.Function:
return c.mod.NamedFunction(getFunctionName(expr, false)), nil
case *ssa.Global: case *ssa.Global:
fullName := getGlobalName(expr) fullName := getGlobalName(expr)
value := c.mod.NamedGlobal(fullName) value := c.mod.NamedGlobal(fullName)
@ -1274,11 +1503,15 @@ func (c *Compiler) LinkModule(mod llvm.Module) error {
func (c *Compiler) ApplyFunctionSections() { func (c *Compiler) ApplyFunctionSections() {
// Put every function in a separate section. This makes it possible for the // Put every function in a separate section. This makes it possible for the
// linker to remove dead code (--gc-sections). // linker to remove dead code (-ffunction-sections).
llvmFn := c.mod.FirstFunction() llvmFn := c.mod.FirstFunction()
for !llvmFn.IsNil() { for !llvmFn.IsNil() {
if !llvmFn.IsDeclaration() { if !llvmFn.IsDeclaration() {
llvmFn.SetSection(".text." + llvmFn.Name()) name := llvmFn.Name()
if strings.HasSuffix(name, "$async") {
name = name[:len(name)-len("$async")]
}
llvmFn.SetSection(".text." + name)
} }
llvmFn = llvm.NextFunction(llvmFn) llvmFn = llvm.NextFunction(llvmFn)
} }
@ -1367,7 +1600,7 @@ func Compile(pkgName, runtimePath, outpath, target string, printIR bool) error {
if err := c.Verify(); err != nil { if err := c.Verify(); err != nil {
return err return err
} }
c.Optimize(2, 1) // -O2 -Os //c.Optimize(2, 1) // -O2 -Os
if err := c.Verify(); err != nil { if err := c.Verify(); err != nil {
return err return err
} }