compiler: add basic debug info

Only implements debugging with source lines and stacktraces (without
parameters), which already is a huge improvement over no debug
information at all. This should be extended to arguments and local
variables (of the correct DWARF type), but this is more work.
This commit is contained in:
Ayke van Laethem
2018-09-03 18:26:07 +02:00
parent a7fcef62e0
commit efdc2b8672
+114
View File
@@ -8,6 +8,7 @@ import (
"go/token" "go/token"
"go/types" "go/types"
"os" "os"
"path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@@ -29,10 +30,15 @@ func init() {
type Compiler struct { type Compiler struct {
dumpSSA bool dumpSSA bool
debug bool
triple string triple string
mod llvm.Module mod llvm.Module
ctx llvm.Context ctx llvm.Context
builder llvm.Builder builder llvm.Builder
dibuilder *llvm.DIBuilder
cu llvm.Metadata
difiles map[string]llvm.Metadata
ditypes map[string]llvm.Metadata
machine llvm.TargetMachine machine llvm.TargetMachine
targetData llvm.TargetData targetData llvm.TargetData
intType llvm.Type intType llvm.Type
@@ -62,6 +68,7 @@ type Frame struct {
cleanupBlock llvm.BasicBlock cleanupBlock llvm.BasicBlock
suspendBlock llvm.BasicBlock suspendBlock llvm.BasicBlock
deferred []*Defer deferred []*Defer
difunc llvm.Metadata
} }
type Defer struct { type Defer struct {
@@ -79,7 +86,10 @@ var cgoWrapperError = errors.New("tinygo internal: cgo wrapper")
func NewCompiler(pkgName, triple string, dumpSSA bool) (*Compiler, error) { func NewCompiler(pkgName, triple string, dumpSSA bool) (*Compiler, error) {
c := &Compiler{ c := &Compiler{
dumpSSA: dumpSSA, dumpSSA: dumpSSA,
debug: false, // TODO: make configurable
triple: triple, triple: triple,
difiles: make(map[string]llvm.Metadata),
ditypes: make(map[string]llvm.Metadata),
} }
target, err := llvm.GetTargetFromTriple(triple) target, err := llvm.GetTargetFromTriple(triple)
@@ -92,6 +102,7 @@ func NewCompiler(pkgName, triple string, dumpSSA bool) (*Compiler, error) {
c.mod = llvm.NewModule(pkgName) c.mod = llvm.NewModule(pkgName)
c.ctx = c.mod.Context() c.ctx = c.mod.Context()
c.builder = c.ctx.NewBuilder() c.builder = c.ctx.NewBuilder()
c.dibuilder = llvm.NewDIBuilder(c.mod)
// Depends on platform (32bit or 64bit), but fix it here for now. // Depends on platform (32bit or 64bit), but fix it here for now.
c.intType = llvm.Int32Type() c.intType = llvm.Int32Type()
@@ -218,6 +229,15 @@ func (c *Compiler) Parse(mainPath string, buildTags []string) error {
c.ir.AnalyseBlockingRecursive() // make all parents of blocking calls blocking (transitively) c.ir.AnalyseBlockingRecursive() // make all parents of blocking calls blocking (transitively)
c.ir.AnalyseGoCalls() // check whether we need a scheduler c.ir.AnalyseGoCalls() // check whether we need a scheduler
// Initialize debug information.
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
Language: llvm.DW_LANG_Go,
File: mainPath,
Dir: "",
Producer: "TinyGo",
Optimized: true,
})
var frames []*Frame var frames []*Frame
// Declare all named struct types. // Declare all named struct types.
@@ -423,6 +443,16 @@ func (c *Compiler) Parse(mainPath string, buildTags []string) error {
c.mod.NamedGlobal("runtime.firstInterfaceNum").SetInitializer(llvm.ConstInt(llvm.Int16Type(), uint64(c.ir.FirstDynamicType()), false)) c.mod.NamedGlobal("runtime.firstInterfaceNum").SetInitializer(llvm.ConstInt(llvm.Int16Type(), uint64(c.ir.FirstDynamicType()), false))
// see: https://reviews.llvm.org/D18355
c.mod.AddNamedMetadataOperand("llvm.module.flags",
c.ctx.MDNode([]llvm.Metadata{
llvm.ConstInt(llvm.Int32Type(), 1, false).ConstantAsMetadata(), // Error on mismatch
llvm.GlobalContext().MDString("Debug Info Version"),
llvm.ConstInt(llvm.Int32Type(), 3, false).ConstantAsMetadata(), // DWARF version
}),
)
c.dibuilder.Finalize()
return nil return nil
} }
@@ -621,6 +651,47 @@ func isPointer(typ types.Type) bool {
} }
} }
// Get the DWARF type for this Go type.
func (c *Compiler) getDIType(typ types.Type) (llvm.Metadata, error) {
name := typ.String()
if dityp, ok := c.ditypes[name]; ok {
return dityp, nil
} else {
llvmType, err := c.getLLVMType(typ)
if err != nil {
return llvm.Metadata{}, err
}
sizeInBytes := c.targetData.TypeAllocSize(llvmType)
var encoding llvm.DwarfTypeEncoding
switch typ := typ.(type) {
case *types.Basic:
if typ.Info()&types.IsBoolean != 0 {
encoding = llvm.DW_ATE_boolean
} else if typ.Info()&types.IsFloat != 0 {
encoding = llvm.DW_ATE_float
} else if typ.Info()&types.IsComplex != 0 {
encoding = llvm.DW_ATE_complex_float
} else if typ.Info()&types.IsUnsigned != 0 {
encoding = llvm.DW_ATE_unsigned
} else if typ.Info()&types.IsInteger != 0 {
encoding = llvm.DW_ATE_signed
} else if typ.Kind() == types.UnsafePointer {
encoding = llvm.DW_ATE_address
}
case *types.Pointer:
encoding = llvm.DW_ATE_address
}
// TODO: other types
return c.dibuilder.CreateBasicType(llvm.DIBasicType{
Name: name,
SizeInBits: sizeInBytes * 8,
Encoding: encoding,
}), nil
c.ditypes[name] = dityp
return dityp, nil
}
}
// Get all methods of a type: both value receivers and pointer receivers. // Get all methods of a type: both value receivers and pointer receivers.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection { func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
var methods []*types.Selection var methods []*types.Selection
@@ -713,6 +784,44 @@ func (c *Compiler) parseFuncDecl(f *Function) (*Frame, error) {
if frame.fn.llvmFn.IsNil() { if frame.fn.llvmFn.IsNil() {
frame.fn.llvmFn = llvm.AddFunction(c.mod, name, fnType) frame.fn.llvmFn = llvm.AddFunction(c.mod, name, fnType)
} }
if c.debug && f.fn.Syntax() != nil && len(f.fn.Blocks) != 0 {
// Create debug info file if needed.
pos := c.ir.program.Fset.Position(f.fn.Syntax().Pos())
if _, ok := c.difiles[pos.Filename]; !ok {
dir, file := filepath.Split(pos.Filename)
c.difiles[pos.Filename] = c.dibuilder.CreateFile(file, dir[:len(dir)-1])
}
// Debug info for this function.
diparams := make([]llvm.Metadata, 0, len(f.fn.Params))
for _, param := range f.fn.Params {
ditype, err := c.getDIType(param.Type())
if err != nil {
return nil, err
}
diparams = append(diparams, ditype)
}
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.difiles[pos.Filename],
Parameters: diparams,
Flags: 0, // ?
})
frame.difunc = c.dibuilder.CreateFunction(c.difiles[pos.Filename], llvm.DIFunction{
Name: f.fn.RelString(nil),
LinkageName: f.LinkName(),
File: c.difiles[pos.Filename],
Line: pos.Line,
Type: diFuncType,
LocalToUnit: true,
IsDefinition: true,
ScopeLine: 0,
Flags: llvm.FlagPrototyped,
Optimized: true,
})
frame.fn.llvmFn.SetSubprogram(frame.difunc)
}
return frame, nil return frame, nil
} }
@@ -1136,6 +1245,11 @@ func (c *Compiler) parseFunc(frame *Frame) error {
} }
func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error { func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error {
if c.debug {
pos := c.ir.program.Fset.Position(instr.Pos())
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), frame.difunc, llvm.Metadata{})
}
switch instr := instr.(type) { switch instr := instr.(type) {
case ssa.Value: case ssa.Value:
value, err := c.parseExpr(frame, instr) value, err := c.parseExpr(frame, instr)