compiler: make runtime.makeGoroutine AVR compatible

Previously it would use a bitcast, which cannot directly be used on AVR
because functions live in a different address space on AVR. To fix this,
use a ptrtoint/inttoptr pair.

This allows testdata/coroutines.go to be compiled, but due to what
appears to be an LLVM bug cannot be optimized and codegen'ed:

    tinygo: /home/ayke/src/github.com/tinygo-org/tinygo/llvm-project/llvm/lib/IR/Constants.cpp:1776: static llvm::Constant *llvm::ConstantExpr::getBitCast(llvm::Constant *, llvm::Type *, bool): Assertion `CastInst::castIsValid(Instruction::BitCast, C, DstTy) && "Invalid constantexpr bitcast!"' failed.

This happens as one of the function passes after the TinyGo passes and
after the module has been verified so most likely it is a bug somewhere
in LLVM.
Этот коммит содержится в:
Ayke van Laethem 2019-08-09 18:13:47 +02:00 коммит произвёл Ron Evans
родитель 92206558fb
коммит 562ad740da
3 изменённых файлов: 27 добавлений и 19 удалений

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

@ -1045,9 +1045,9 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
// interprocedural optimizations. For example, heap-to-stack // interprocedural optimizations. For example, heap-to-stack
// transformations are not sound as goroutines can outlive their parent. // transformations are not sound as goroutines can outlive their parent.
calleeType := calleeFn.LLVMFn.Type() calleeType := calleeFn.LLVMFn.Type()
calleeValue := c.builder.CreateBitCast(calleeFn.LLVMFn, c.i8ptrType, "") calleeValue := c.builder.CreatePtrToInt(calleeFn.LLVMFn, c.uintptrType, "")
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "") calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
calleeValue = c.builder.CreateBitCast(calleeValue, calleeType, "") calleeValue = c.builder.CreateIntToPtr(calleeValue, calleeType, "")
// Get all function parameters to pass to the goroutine. // Get all function parameters to pass to the goroutine.
var params []llvm.Value var params []llvm.Value

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

@ -211,17 +211,25 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Add all callees to the worklist. // Add all callees to the worklist.
for _, use := range getUses(f) { for _, use := range getUses(f) {
if use.IsConstant() && use.Opcode() == llvm.BitCast { if use.IsConstant() && use.Opcode() == llvm.PtrToInt {
bitcastUses := getUses(use) for _, call := range getUses(use) {
for _, call := range bitcastUses {
if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" { if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" {
return false, errors.New("async function " + f.Name() + " incorrectly used in bitcast, expected runtime.makeGoroutine") return false, errors.New("async function " + f.Name() + " incorrectly used in ptrtoint, expected runtime.makeGoroutine")
} }
} }
// This is a go statement. Do not mark the parent as async, as // This is a go statement. Do not mark the parent as async, as
// starting a goroutine is not a blocking operation. // starting a goroutine is not a blocking operation.
continue continue
} }
if use.IsConstant() && use.Opcode() == llvm.BitCast {
// Not sure why this const bitcast is here but as long as it
// has no uses it can be ignored, I guess?
// I think it was created for the runtime.isnil check but
// somehow wasn't removed when all these checks are removed.
if len(getUses(use)) == 0 {
continue
}
}
if use.IsACallInst().IsNil() { if use.IsACallInst().IsNil() {
// Not a call instruction. Maybe a store to a global? In any // Not a call instruction. Maybe a store to a global? In any
// case, this requires support for async calls across function // case, this requires support for async calls across function
@ -250,12 +258,12 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// goroutine is not async (does not do any blocking operation), no // goroutine is not async (does not do any blocking operation), no
// scheduler is necessary as it can be called directly. // scheduler is necessary as it can be called directly.
for _, use := range getUses(makeGoroutine) { for _, use := range getUses(makeGoroutine) {
// Input param must be const bitcast of function. // Input param must be const ptrtoint of function.
bitcast := use.Operand(0) ptrtoint := use.Operand(0)
if !bitcast.IsConstant() || bitcast.Opcode() != llvm.BitCast { if !ptrtoint.IsConstant() || ptrtoint.Opcode() != llvm.PtrToInt {
panic("expected const bitcast operand of runtime.makeGoroutine") panic("expected const ptrtoint operand of runtime.makeGoroutine")
} }
goroutine := bitcast.Operand(0) goroutine := ptrtoint.Operand(0)
if _, ok := asyncFuncs[goroutine]; ok { if _, ok := asyncFuncs[goroutine]; ok {
needsScheduler = true needsScheduler = true
break break
@ -571,14 +579,14 @@ func (c *Compiler) lowerMakeGoroutineCalls() error {
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine") makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
for _, goroutine := range getUses(makeGoroutine) { for _, goroutine := range getUses(makeGoroutine) {
bitcastIn := goroutine.Operand(0) ptrtointIn := goroutine.Operand(0)
origFunc := bitcastIn.Operand(0) origFunc := ptrtointIn.Operand(0)
uses := getUses(goroutine) uses := getUses(goroutine)
if len(uses) != 1 || uses[0].IsABitCastInst().IsNil() { if len(uses) != 1 || uses[0].IsAIntToPtrInst().IsNil() {
return errors.New("expected exactly 1 bitcast use of runtime.makeGoroutine") return errors.New("expected exactly 1 inttoptr use of runtime.makeGoroutine")
} }
bitcastOut := uses[0] inttoptrOut := uses[0]
uses = getUses(bitcastOut) uses = getUses(inttoptrOut)
if len(uses) != 1 || uses[0].IsACallInst().IsNil() { if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast") return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast")
} }
@ -593,7 +601,7 @@ func (c *Compiler) lowerMakeGoroutineCalls() error {
c.builder.SetInsertPointBefore(realCall) c.builder.SetInsertPointBefore(realCall)
c.builder.CreateCall(origFunc, params, "") c.builder.CreateCall(origFunc, params, "")
realCall.EraseFromParentAsInstruction() realCall.EraseFromParentAsInstruction()
bitcastOut.EraseFromParentAsInstruction() inttoptrOut.EraseFromParentAsInstruction()
goroutine.EraseFromParentAsInstruction() goroutine.EraseFromParentAsInstruction()
} }

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

@ -47,7 +47,7 @@ func (t *coroutine) promise() *taskState {
return (*taskState)(t._promise(int32(unsafe.Alignof(taskState{})), false)) return (*taskState)(t._promise(int32(unsafe.Alignof(taskState{})), false))
} }
func makeGoroutine(*uint8) *uint8 func makeGoroutine(uintptr) uintptr
// Compiler stub to get the current goroutine. Calls to this function are // Compiler stub to get the current goroutine. Calls to this function are
// removed in the goroutine lowering pass. // removed in the goroutine lowering pass.