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.
Этот коммит содержится в:
родитель
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
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)
|
||||
}
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче