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
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:

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

@ -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
}

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

@ -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))
}

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

@ -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())
}
}

20
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)
}