mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-16 18:53:29 +00:00
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:
+114
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user