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.
Этот коммит содержится в:
Ayke van Laethem 2018-09-03 18:26:07 +02:00
родитель a7fcef62e0
коммит efdc2b8672
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: E97FF5335DFDFDED

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

@ -8,6 +8,7 @@ import (
"go/token"
"go/types"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
@ -29,10 +30,15 @@ func init() {
type Compiler struct {
dumpSSA bool
debug bool
triple string
mod llvm.Module
ctx llvm.Context
builder llvm.Builder
dibuilder *llvm.DIBuilder
cu llvm.Metadata
difiles map[string]llvm.Metadata
ditypes map[string]llvm.Metadata
machine llvm.TargetMachine
targetData llvm.TargetData
intType llvm.Type
@ -62,6 +68,7 @@ type Frame struct {
cleanupBlock llvm.BasicBlock
suspendBlock llvm.BasicBlock
deferred []*Defer
difunc llvm.Metadata
}
type Defer struct {
@ -79,7 +86,10 @@ var cgoWrapperError = errors.New("tinygo internal: cgo wrapper")
func NewCompiler(pkgName, triple string, dumpSSA bool) (*Compiler, error) {
c := &Compiler{
dumpSSA: dumpSSA,
debug: false, // TODO: make configurable
triple: triple,
difiles: make(map[string]llvm.Metadata),
ditypes: make(map[string]llvm.Metadata),
}
target, err := llvm.GetTargetFromTriple(triple)
@ -92,6 +102,7 @@ func NewCompiler(pkgName, triple string, dumpSSA bool) (*Compiler, error) {
c.mod = llvm.NewModule(pkgName)
c.ctx = c.mod.Context()
c.builder = c.ctx.NewBuilder()
c.dibuilder = llvm.NewDIBuilder(c.mod)
// Depends on platform (32bit or 64bit), but fix it here for now.
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.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
// 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))
// 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
}
@ -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.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
var methods []*types.Selection
@ -713,6 +784,44 @@ func (c *Compiler) parseFuncDecl(f *Function) (*Frame, error) {
if frame.fn.llvmFn.IsNil() {
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
}
@ -1136,6 +1245,11 @@ func (c *Compiler) parseFunc(frame *Frame) 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) {
case ssa.Value:
value, err := c.parseExpr(frame, instr)