diff --git a/compiler/compiler.go b/compiler/compiler.go index 546359dce..e729ca1e7 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -62,6 +62,7 @@ type Compiler struct { initFuncs []llvm.Value interfaceInvokeWrappers []interfaceInvokeWrapper ir *ir.Program + diagnostics []error } type Frame struct { @@ -159,7 +160,7 @@ func (c *Compiler) selectGC() string { // 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 { +func (c *Compiler) Compile(mainPath string) []error { // Prefix the GOPATH with the system GOROOT, as GOROOT is already set to // the TinyGo root. overlayGopath := c.GOPATH @@ -171,7 +172,7 @@ func (c *Compiler) Compile(mainPath string) error { wd, err := os.Getwd() if err != nil { - return err + return []error{err} } lprogram := &loader.Program{ Build: &build.Context{ @@ -225,22 +226,22 @@ func (c *Compiler) Compile(mainPath string) error { if strings.HasSuffix(mainPath, ".go") { _, err = lprogram.ImportFile(mainPath) if err != nil { - return err + return []error{err} } } else { _, err = lprogram.Import(mainPath, wd) if err != nil { - return err + return []error{err} } } _, err = lprogram.Import("runtime", "") if err != nil { - return err + return []error{err} } err = lprogram.Parse() if err != nil { - return err + return []error{err} } c.ir = ir.NewProgram(lprogram, mainPath) @@ -311,10 +312,7 @@ func (c *Compiler) Compile(mainPath string) error { if frame.fn.Blocks == nil { continue // external function } - err := c.parseFunc(frame) - if err != nil { - return err - } + c.parseFunc(frame) } // Define the already declared functions that wrap methods for use in @@ -392,7 +390,7 @@ func (c *Compiler) Compile(mainPath string) error { c.dibuilder.Finalize() } - return nil + return c.diagnostics } func (c *Compiler) getLLVMType(goType types.Type) llvm.Type { @@ -674,7 +672,7 @@ func (c *Compiler) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix, return difunc } -func (c *Compiler) parseFunc(frame *Frame) error { +func (c *Compiler) parseFunc(frame *Frame) { if c.DumpSSA { fmt.Printf("\nfunc %s:\n", frame.fn.Function) } @@ -804,10 +802,7 @@ func (c *Compiler) parseFunc(frame *Frame) error { fmt.Printf("\t%s\n", instr.String()) } } - err := c.parseInstr(frame, instr) - if err != nil { - return err - } + c.parseInstr(frame, instr) } if frame.fn.Name() == "init" && len(block.Instrs) == 0 { c.builder.CreateRetVoid() @@ -823,11 +818,9 @@ func (c *Compiler) parseFunc(frame *Frame) error { phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock}) } } - - return nil } -func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error { +func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { if c.Debug { pos := c.ir.Program.Fset.Position(instr.Pos()) c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), frame.difunc, llvm.Metadata{}) @@ -835,20 +828,31 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error { switch instr := instr.(type) { case ssa.Value: - value, err := c.parseExpr(frame, instr) - frame.locals[instr] = value - return err + if value, err := c.parseExpr(frame, instr); err != nil { + // This expression could not be parsed. Add the error to the list + // of diagnostics and continue with an undef value. + // The resulting IR will be incorrect (but valid). However, + // compilation can proceed which is useful because there may be + // more compilation errors which can then all be shown together to + // the user. + c.diagnostics = append(c.diagnostics, err) + frame.locals[instr] = llvm.Undef(c.getLLVMType(instr.Type())) + } else { + frame.locals[instr] = value + } case *ssa.DebugRef: - return nil // ignore + // ignore case *ssa.Defer: - return c.emitDefer(frame, instr) + c.emitDefer(frame, instr) case *ssa.Go: if instr.Call.IsInvoke() { - return c.makeError(instr.Pos(), "todo: go on method receiver") + c.addError(instr.Pos(), "todo: go on method receiver") + return } callee := instr.Call.StaticCallee() if callee == nil { - return c.makeError(instr.Pos(), "todo: go on non-direct function (function pointer, etc.)") + c.addError(instr.Pos(), "todo: go on non-direct function (function pointer, etc.)") + return } calleeFn := c.ir.GetFunction(callee) @@ -871,36 +875,30 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error { } c.createCall(calleeValue, params, "") - return nil case *ssa.If: cond := c.getValue(frame, instr.Cond) block := instr.Block() blockThen := frame.blockEntries[block.Succs[0]] blockElse := frame.blockEntries[block.Succs[1]] c.builder.CreateCondBr(cond, blockThen, blockElse) - return nil case *ssa.Jump: blockJump := frame.blockEntries[instr.Block().Succs[0]] c.builder.CreateBr(blockJump) - return nil case *ssa.MapUpdate: m := c.getValue(frame, instr.Map) key := c.getValue(frame, instr.Key) value := c.getValue(frame, instr.Value) mapType := instr.Map.Type().Underlying().(*types.Map) - return c.emitMapUpdate(mapType.Key(), m, key, value, instr.Pos()) + c.emitMapUpdate(mapType.Key(), m, key, value, instr.Pos()) case *ssa.Panic: value := c.getValue(frame, instr.X) c.createRuntimeCall("_panic", []llvm.Value{value}, "") c.builder.CreateUnreachable() - return nil case *ssa.Return: if len(instr.Results) == 0 { c.builder.CreateRetVoid() - return nil } else if len(instr.Results) == 1 { c.builder.CreateRet(c.getValue(frame, instr.Results[0])) - return nil } else { // Multiple return values. Put them all in a struct. retVal := c.getZeroValue(frame.fn.LLVMFn.Type().ElementType().ReturnType()) @@ -909,19 +907,17 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error { retVal = c.builder.CreateInsertValue(retVal, val, i, "") } c.builder.CreateRet(retVal) - return nil } case *ssa.RunDefers: - return c.emitRunDefers(frame) + c.emitRunDefers(frame) case *ssa.Send: c.emitChanSend(frame, instr) - return nil case *ssa.Store: llvmAddr := c.getValue(frame, instr.Addr) llvmVal := c.getValue(frame, instr.Val) if c.targetData.TypeAllocSize(llvmVal.Type()) == 0 { // nothing to store - return nil + return } store := c.builder.CreateStore(llvmVal, llvmAddr) valType := instr.Addr.Type().Underlying().(*types.Pointer).Elem() @@ -929,9 +925,8 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error { // Volatile store, for memory-mapped registers. store.SetVolatile(true) } - return nil default: - return c.makeError(instr.Pos(), "unknown instruction: "+instr.String()) + c.addError(instr.Pos(), "unknown instruction: "+instr.String()) } } @@ -1189,15 +1184,15 @@ func (c *Compiler) getValue(frame *Frame, expr ssa.Value) llvm.Value { case *ssa.Function: fn := c.ir.GetFunction(expr) if fn.IsExported() { - // TODO: report this as a compiler diagnostic - panic("cannot use an exported function as value: " + expr.String()) + c.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String()) + return llvm.Undef(c.getLLVMType(expr.Type())) } return c.createFuncValue(fn.LLVMFn, llvm.Undef(c.i8ptrType), fn.Signature) case *ssa.Global: value := c.ir.GetGlobal(expr).LLVMGlobal if value.IsNil() { - // TODO: report this as a compiler diagnostic - panic("global not found: " + c.ir.GetGlobal(expr).LinkName()) + c.addError(expr.Pos(), "global not found: "+c.ir.GetGlobal(expr).LinkName()) + return llvm.Undef(c.getLLVMType(expr.Type())) } return value default: diff --git a/compiler/defer.go b/compiler/defer.go index 9a7f3717a..016c1c73f 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -36,7 +36,7 @@ func (c *Compiler) deferInitFunc(frame *Frame) { // emitDefer emits a single defer instruction, to be run when this function // returns. -func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error { +func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) { // The pointer to the previous defer struct, which we will replace to // make a linked list. next := c.builder.CreateLoad(frame.deferPtr, "defer.next") @@ -116,7 +116,8 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error { valueTypes = append(valueTypes, context.Type()) } else { - return c.makeError(instr.Pos(), "todo: defer on uncommon function call type") + c.addError(instr.Pos(), "todo: defer on uncommon function call type") + return } // Make a struct out of the collected values to put in the defer frame. @@ -133,11 +134,10 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error { // Push it on top of the linked list by replacing deferPtr. allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast") c.builder.CreateStore(allocaCast, frame.deferPtr) - return nil } // emitRunDefers emits code to run all deferred functions. -func (c *Compiler) emitRunDefers(frame *Frame) error { +func (c *Compiler) emitRunDefers(frame *Frame) { // Add a loop like the following: // for stack != nil { // _stack := stack @@ -301,5 +301,4 @@ func (c *Compiler) emitRunDefers(frame *Frame) error { // End of loop. c.builder.SetInsertPointAtEnd(end) - return nil } diff --git a/compiler/errors.go b/compiler/errors.go index 1d76b4231..e00304643 100644 --- a/compiler/errors.go +++ b/compiler/errors.go @@ -12,3 +12,7 @@ func (c *Compiler) makeError(pos token.Pos, msg string) types.Error { Msg: msg, } } + +func (c *Compiler) addError(pos token.Pos, msg string) { + c.diagnostics = append(c.diagnostics, c.makeError(pos, msg)) +} diff --git a/compiler/map.go b/compiler/map.go index f8531191a..e69393c22 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -39,7 +39,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu } } -func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) error { +func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value") c.builder.CreateStore(value, valueAlloca) valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr") @@ -48,7 +48,6 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p // key is a string params := []llvm.Value{m, key, valuePtr} c.createRuntimeCall("hashmapStringSet", params, "") - return nil } else if hashmapIsBinaryKey(keyType) { // key can be compared with runtime.memequal keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key") @@ -56,9 +55,8 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr") params := []llvm.Value{m, keyPtr, valuePtr} c.createRuntimeCall("hashmapBinarySet", params, "") - return nil } else { - return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String()) + c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String()) } } diff --git a/main.go b/main.go index c117de324..7bd025405 100644 --- a/main.go +++ b/main.go @@ -33,6 +33,16 @@ func (e *commandError) Error() string { return e.Msg + " " + e.File + ": " + e.Err.Error() } +// multiError is a list of multiple errors (actually: diagnostics) returned +// during LLVM IR generation. +type multiError struct { + Errs []error +} + +func (e *multiError) Error() string { + return e.Errs[0].Error() +} + type BuildConfig struct { opt string gc string @@ -77,9 +87,9 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act } // Compile Go code to IR. - err = c.Compile(pkgName) - if err != nil { - return err + errs := c.Compile(pkgName) + if errs != nil { + return &multiError{errs} } if config.printIR { fmt.Println("Generated LLVM IR:") @@ -498,6 +508,10 @@ func handleCompilerError(err error) { for _, err := range errLoader.Errs { fmt.Fprintln(os.Stderr, err) } + } else if errMulti, ok := err.(*multiError); ok { + for _, err := range errMulti.Errs { + fmt.Fprintln(os.Stderr, err) + } } else { fmt.Fprintln(os.Stderr, "error:", err) }