all: improve compiler error handling

Most of these errors are actually "todo" or "unimplemented" errors, so
the return type is known. This means that compilation can proceed (with
errors) even though the output will be incorrect. This is useful because
this way, all errors in a compilation unit can be shown together to the
user.
Этот коммит содержится в:
Ayke van Laethem 2019-04-21 16:20:25 +02:00 коммит произвёл Ron Evans
родитель 45cacda7b3
коммит d155e31b64
5 изменённых файлов: 65 добавлений и 55 удалений

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

@ -62,6 +62,7 @@ type Compiler struct {
initFuncs []llvm.Value initFuncs []llvm.Value
interfaceInvokeWrappers []interfaceInvokeWrapper interfaceInvokeWrappers []interfaceInvokeWrapper
ir *ir.Program ir *ir.Program
diagnostics []error
} }
type Frame struct { 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 // Compile the given package path or .go file path. Return an error when this
// fails (in any stage). // 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 // Prefix the GOPATH with the system GOROOT, as GOROOT is already set to
// the TinyGo root. // the TinyGo root.
overlayGopath := c.GOPATH overlayGopath := c.GOPATH
@ -171,7 +172,7 @@ func (c *Compiler) Compile(mainPath string) error {
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
return err return []error{err}
} }
lprogram := &loader.Program{ lprogram := &loader.Program{
Build: &build.Context{ Build: &build.Context{
@ -225,22 +226,22 @@ func (c *Compiler) Compile(mainPath string) error {
if strings.HasSuffix(mainPath, ".go") { if strings.HasSuffix(mainPath, ".go") {
_, err = lprogram.ImportFile(mainPath) _, err = lprogram.ImportFile(mainPath)
if err != nil { if err != nil {
return err return []error{err}
} }
} else { } else {
_, err = lprogram.Import(mainPath, wd) _, err = lprogram.Import(mainPath, wd)
if err != nil { if err != nil {
return err return []error{err}
} }
} }
_, err = lprogram.Import("runtime", "") _, err = lprogram.Import("runtime", "")
if err != nil { if err != nil {
return err return []error{err}
} }
err = lprogram.Parse() err = lprogram.Parse()
if err != nil { if err != nil {
return err return []error{err}
} }
c.ir = ir.NewProgram(lprogram, mainPath) c.ir = ir.NewProgram(lprogram, mainPath)
@ -311,10 +312,7 @@ func (c *Compiler) Compile(mainPath string) error {
if frame.fn.Blocks == nil { if frame.fn.Blocks == nil {
continue // external function continue // external function
} }
err := c.parseFunc(frame) c.parseFunc(frame)
if err != nil {
return err
}
} }
// Define the already declared functions that wrap methods for use in // 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() c.dibuilder.Finalize()
} }
return nil return c.diagnostics
} }
func (c *Compiler) getLLVMType(goType types.Type) llvm.Type { 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 return difunc
} }
func (c *Compiler) parseFunc(frame *Frame) error { func (c *Compiler) parseFunc(frame *Frame) {
if c.DumpSSA { if c.DumpSSA {
fmt.Printf("\nfunc %s:\n", frame.fn.Function) 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()) fmt.Printf("\t%s\n", instr.String())
} }
} }
err := c.parseInstr(frame, instr) c.parseInstr(frame, instr)
if err != nil {
return err
}
} }
if frame.fn.Name() == "init" && len(block.Instrs) == 0 { if frame.fn.Name() == "init" && len(block.Instrs) == 0 {
c.builder.CreateRetVoid() c.builder.CreateRetVoid()
@ -823,11 +818,9 @@ func (c *Compiler) parseFunc(frame *Frame) error {
phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock}) 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 { if c.Debug {
pos := c.ir.Program.Fset.Position(instr.Pos()) pos := c.ir.Program.Fset.Position(instr.Pos())
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), frame.difunc, llvm.Metadata{}) 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) { switch instr := instr.(type) {
case ssa.Value: case ssa.Value:
value, err := c.parseExpr(frame, instr) if value, err := c.parseExpr(frame, instr); err != nil {
frame.locals[instr] = value // This expression could not be parsed. Add the error to the list
return err // 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: case *ssa.DebugRef:
return nil // ignore // ignore
case *ssa.Defer: case *ssa.Defer:
return c.emitDefer(frame, instr) c.emitDefer(frame, instr)
case *ssa.Go: case *ssa.Go:
if instr.Call.IsInvoke() { 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() callee := instr.Call.StaticCallee()
if callee == nil { 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) calleeFn := c.ir.GetFunction(callee)
@ -871,36 +875,30 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error {
} }
c.createCall(calleeValue, params, "") c.createCall(calleeValue, params, "")
return nil
case *ssa.If: case *ssa.If:
cond := c.getValue(frame, instr.Cond) cond := c.getValue(frame, instr.Cond)
block := instr.Block() block := instr.Block()
blockThen := frame.blockEntries[block.Succs[0]] blockThen := frame.blockEntries[block.Succs[0]]
blockElse := frame.blockEntries[block.Succs[1]] blockElse := frame.blockEntries[block.Succs[1]]
c.builder.CreateCondBr(cond, blockThen, blockElse) c.builder.CreateCondBr(cond, blockThen, blockElse)
return nil
case *ssa.Jump: case *ssa.Jump:
blockJump := frame.blockEntries[instr.Block().Succs[0]] blockJump := frame.blockEntries[instr.Block().Succs[0]]
c.builder.CreateBr(blockJump) c.builder.CreateBr(blockJump)
return nil
case *ssa.MapUpdate: case *ssa.MapUpdate:
m := c.getValue(frame, instr.Map) m := c.getValue(frame, instr.Map)
key := c.getValue(frame, instr.Key) key := c.getValue(frame, instr.Key)
value := c.getValue(frame, instr.Value) value := c.getValue(frame, instr.Value)
mapType := instr.Map.Type().Underlying().(*types.Map) 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: case *ssa.Panic:
value := c.getValue(frame, instr.X) value := c.getValue(frame, instr.X)
c.createRuntimeCall("_panic", []llvm.Value{value}, "") c.createRuntimeCall("_panic", []llvm.Value{value}, "")
c.builder.CreateUnreachable() c.builder.CreateUnreachable()
return nil
case *ssa.Return: case *ssa.Return:
if len(instr.Results) == 0 { if len(instr.Results) == 0 {
c.builder.CreateRetVoid() c.builder.CreateRetVoid()
return nil
} else if len(instr.Results) == 1 { } else if len(instr.Results) == 1 {
c.builder.CreateRet(c.getValue(frame, instr.Results[0])) c.builder.CreateRet(c.getValue(frame, instr.Results[0]))
return nil
} else { } else {
// Multiple return values. Put them all in a struct. // Multiple return values. Put them all in a struct.
retVal := c.getZeroValue(frame.fn.LLVMFn.Type().ElementType().ReturnType()) 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, "") retVal = c.builder.CreateInsertValue(retVal, val, i, "")
} }
c.builder.CreateRet(retVal) c.builder.CreateRet(retVal)
return nil
} }
case *ssa.RunDefers: case *ssa.RunDefers:
return c.emitRunDefers(frame) c.emitRunDefers(frame)
case *ssa.Send: case *ssa.Send:
c.emitChanSend(frame, instr) c.emitChanSend(frame, instr)
return nil
case *ssa.Store: case *ssa.Store:
llvmAddr := c.getValue(frame, instr.Addr) llvmAddr := c.getValue(frame, instr.Addr)
llvmVal := c.getValue(frame, instr.Val) llvmVal := c.getValue(frame, instr.Val)
if c.targetData.TypeAllocSize(llvmVal.Type()) == 0 { if c.targetData.TypeAllocSize(llvmVal.Type()) == 0 {
// nothing to store // nothing to store
return nil return
} }
store := c.builder.CreateStore(llvmVal, llvmAddr) store := c.builder.CreateStore(llvmVal, llvmAddr)
valType := instr.Addr.Type().Underlying().(*types.Pointer).Elem() 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. // Volatile store, for memory-mapped registers.
store.SetVolatile(true) store.SetVolatile(true)
} }
return nil
default: 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: case *ssa.Function:
fn := c.ir.GetFunction(expr) fn := c.ir.GetFunction(expr)
if fn.IsExported() { if fn.IsExported() {
// TODO: report this as a compiler diagnostic c.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String())
panic("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) return c.createFuncValue(fn.LLVMFn, llvm.Undef(c.i8ptrType), fn.Signature)
case *ssa.Global: case *ssa.Global:
value := c.ir.GetGlobal(expr).LLVMGlobal value := c.ir.GetGlobal(expr).LLVMGlobal
if value.IsNil() { if value.IsNil() {
// TODO: report this as a compiler diagnostic c.addError(expr.Pos(), "global not found: "+c.ir.GetGlobal(expr).LinkName())
panic("global not found: " + c.ir.GetGlobal(expr).LinkName()) return llvm.Undef(c.getLLVMType(expr.Type()))
} }
return value return value
default: default:

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

@ -36,7 +36,7 @@ func (c *Compiler) deferInitFunc(frame *Frame) {
// emitDefer emits a single defer instruction, to be run when this function // emitDefer emits a single defer instruction, to be run when this function
// returns. // 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 // The pointer to the previous defer struct, which we will replace to
// make a linked list. // make a linked list.
next := c.builder.CreateLoad(frame.deferPtr, "defer.next") 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()) valueTypes = append(valueTypes, context.Type())
} else { } 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. // 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. // Push it on top of the linked list by replacing deferPtr.
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast") allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
c.builder.CreateStore(allocaCast, frame.deferPtr) c.builder.CreateStore(allocaCast, frame.deferPtr)
return nil
} }
// emitRunDefers emits code to run all deferred functions. // 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: // Add a loop like the following:
// for stack != nil { // for stack != nil {
// _stack := stack // _stack := stack
@ -301,5 +301,4 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
// End of loop. // End of loop.
c.builder.SetInsertPointAtEnd(end) c.builder.SetInsertPointAtEnd(end)
return nil
} }

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

@ -12,3 +12,7 @@ func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
Msg: msg, Msg: msg,
} }
} }
func (c *Compiler) addError(pos token.Pos, msg string) {
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
}

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

@ -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") valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value")
c.builder.CreateStore(value, valueAlloca) c.builder.CreateStore(value, valueAlloca)
valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr") 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 // key is a string
params := []llvm.Value{m, key, valuePtr} params := []llvm.Value{m, key, valuePtr}
c.createRuntimeCall("hashmapStringSet", params, "") c.createRuntimeCall("hashmapStringSet", params, "")
return nil
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal // key can be compared with runtime.memequal
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key") 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") keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
params := []llvm.Value{m, keyPtr, valuePtr} params := []llvm.Value{m, keyPtr, valuePtr}
c.createRuntimeCall("hashmapBinarySet", params, "") c.createRuntimeCall("hashmapBinarySet", params, "")
return nil
} else { } 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())
} }
} }

20
main.go
Просмотреть файл

@ -33,6 +33,16 @@ func (e *commandError) Error() string {
return e.Msg + " " + e.File + ": " + e.Err.Error() 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 { type BuildConfig struct {
opt string opt string
gc string gc string
@ -77,9 +87,9 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
} }
// Compile Go code to IR. // Compile Go code to IR.
err = c.Compile(pkgName) errs := c.Compile(pkgName)
if err != nil { if errs != nil {
return err return &multiError{errs}
} }
if config.printIR { if config.printIR {
fmt.Println("Generated LLVM IR:") fmt.Println("Generated LLVM IR:")
@ -498,6 +508,10 @@ func handleCompilerError(err error) {
for _, err := range errLoader.Errs { for _, err := range errLoader.Errs {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
} }
} else if errMulti, ok := err.(*multiError); ok {
for _, err := range errMulti.Errs {
fmt.Fprintln(os.Stderr, err)
}
} else { } else {
fmt.Fprintln(os.Stderr, "error:", err) fmt.Fprintln(os.Stderr, "error:", err)
} }