//===- debug.go - debug info builder --------------------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This package builds LLVM debug info from go/* data structures. // //===----------------------------------------------------------------------===// package debug import ( "debug/dwarf" "fmt" "go/token" "os" "strings" "llvm.org/llgo/third_party/gotools/go/ssa" "llvm.org/llgo/third_party/gotools/go/types" "llvm.org/llgo/third_party/gotools/go/types/typeutil" "llvm.org/llvm/bindings/go/llvm" ) const ( // non-standard debug metadata tags tagAutoVariable dwarf.Tag = 0x100 tagArgVariable dwarf.Tag = 0x101 ) type PrefixMap struct { Source, Replacement string } // DIBuilder builds debug metadata for Go programs. type DIBuilder struct { // builder is the current builder; there is one per CU. builder *llvm.DIBuilder module llvm.Module files map[*token.File]llvm.Metadata cu, fn, lb llvm.Metadata fnFile string sizes types.Sizes fset *token.FileSet prefixMaps []PrefixMap types typeutil.Map voidType llvm.Metadata } // NewDIBuilder creates a new debug information builder. func NewDIBuilder(sizes types.Sizes, module llvm.Module, fset *token.FileSet, prefixMaps []PrefixMap) *DIBuilder { var d DIBuilder d.module = module d.files = make(map[*token.File]llvm.Metadata) d.sizes = sizes d.fset = fset d.prefixMaps = prefixMaps d.builder = llvm.NewDIBuilder(d.module) d.cu = d.createCompileUnit() return &d } // Destroy destroys the DIBuilder. func (d *DIBuilder) Destroy() { d.builder.Destroy() } func (d *DIBuilder) scope() llvm.Metadata { if d.lb.C != nil { return d.lb } if d.fn.C != nil { return d.fn } return d.cu } func (d *DIBuilder) remapFilePath(path string) string { for _, pm := range d.prefixMaps { if strings.HasPrefix(path, pm.Source) { return pm.Replacement + path[len(pm.Source):] } } return path } func (d *DIBuilder) getFile(file *token.File) llvm.Metadata { if diFile := d.files[file]; diFile.C != nil { return diFile } diFile := d.builder.CreateFile(d.remapFilePath(file.Name()), "") d.files[file] = diFile return diFile } // createCompileUnit creates and returns debug metadata for the compile // unit as a whole, using the first file in the file set as a representative // (the choice of file is arbitrary). func (d *DIBuilder) createCompileUnit() llvm.Metadata { var file *token.File d.fset.Iterate(func(f *token.File) bool { file = f return false }) dir, err := os.Getwd() if err != nil { panic("could not get current directory: " + err.Error()) } return d.builder.CreateCompileUnit(llvm.DICompileUnit{ Language: llvm.DW_LANG_Go, File: d.remapFilePath(file.Name()), Dir: dir, Producer: "llgo", }) } // PushFunction creates debug metadata for the specified function, // and pushes it onto the scope stack. func (d *DIBuilder) PushFunction(fnptr llvm.Value, sig *types.Signature, pos token.Pos) { var diFile llvm.Metadata var line int if file := d.fset.File(pos); file != nil { d.fnFile = file.Name() diFile = d.getFile(file) line = file.Line(pos) } d.fn = d.builder.CreateFunction(d.scope(), llvm.DIFunction{ Name: fnptr.Name(), // TODO(axw) unmangled name? LinkageName: fnptr.Name(), File: diFile, Line: line, Type: d.DIType(sig), IsDefinition: true, Function: fnptr, }) } // PopFunction pops the previously pushed function off the scope stack. func (d *DIBuilder) PopFunction() { d.lb = llvm.Metadata{} d.fn = llvm.Metadata{} d.fnFile = "" } // Value creates an llvm.dbg.value call for the specified register value. func (d *DIBuilder) Value(b llvm.Builder, v ssa.Value, llv llvm.Value, paramIndex int) { // TODO(axw) } // SetLocation sets the current debug location. func (d *DIBuilder) SetLocation(b llvm.Builder, pos token.Pos) { if !pos.IsValid() { return } position := d.fset.Position(pos) d.lb = llvm.Metadata{} if position.Filename != d.fnFile && position.Filename != "" { // This can happen rarely, e.g. in init functions. diFile := d.builder.CreateFile(d.remapFilePath(position.Filename), "") d.lb = d.builder.CreateLexicalBlockFile(d.scope(), diFile, 0) } b.SetCurrentDebugLocation(uint(position.Line), uint(position.Column), d.scope(), llvm.Metadata{}) } // Finalize must be called after all compilation units are translated, // generating the final debug metadata for the module. func (d *DIBuilder) Finalize() { d.module.AddNamedMetadataOperand( "llvm.module.flags", llvm.GlobalContext().MDNode([]llvm.Metadata{ llvm.ConstInt(llvm.Int32Type(), 2, false).ConstantAsMetadata(), // Warn on mismatch llvm.GlobalContext().MDString("Dwarf Version"), llvm.ConstInt(llvm.Int32Type(), 4, false).ConstantAsMetadata(), }), ) d.module.AddNamedMetadataOperand( "llvm.module.flags", llvm.GlobalContext().MDNode([]llvm.Metadata{ llvm.ConstInt(llvm.Int32Type(), 1, false).ConstantAsMetadata(), // Error on mismatch llvm.GlobalContext().MDString("Debug Info Version"), llvm.ConstInt(llvm.Int32Type(), 2, false).ConstantAsMetadata(), }), ) d.builder.Finalize() } // DIType maps a Go type to DIType debug metadata value. func (d *DIBuilder) DIType(t types.Type) llvm.Metadata { return d.typeDebugDescriptor(t, types.TypeString(nil, t)) } func (d *DIBuilder) typeDebugDescriptor(t types.Type, name string) llvm.Metadata { // Signature needs to be handled specially, to preprocess // methods, moving the receiver to the parameter list. if t, ok := t.(*types.Signature); ok { return d.descriptorSignature(t, name) } if t == nil { if d.voidType.C == nil { d.voidType = d.builder.CreateBasicType(llvm.DIBasicType{Name: "void"}) } return d.voidType } if dt, ok := d.types.At(t).(llvm.Metadata); ok { return dt } dt := d.descriptor(t, name) d.types.Set(t, dt) return dt } func (d *DIBuilder) descriptor(t types.Type, name string) llvm.Metadata { switch t := t.(type) { case *types.Basic: return d.descriptorBasic(t, name) case *types.Pointer: return d.descriptorPointer(t) case *types.Struct: return d.descriptorStruct(t, name) case *types.Named: return d.descriptorNamed(t) case *types.Array: return d.descriptorArray(t, name) case *types.Slice: return d.descriptorSlice(t, name) case *types.Map: return d.descriptorMap(t, name) case *types.Chan: return d.descriptorChan(t, name) case *types.Interface: return d.descriptorInterface(t, name) default: panic(fmt.Sprintf("unhandled type: %T", t)) } } func (d *DIBuilder) descriptorBasic(t *types.Basic, name string) llvm.Metadata { switch t.Kind() { case types.String: return d.typeDebugDescriptor(types.NewStruct([]*types.Var{ types.NewVar(0, nil, "ptr", types.NewPointer(types.Typ[types.Uint8])), types.NewVar(0, nil, "len", types.Typ[types.Int]), }, nil), name) case types.UnsafePointer: return d.builder.CreateBasicType(llvm.DIBasicType{ Name: name, SizeInBits: uint64(d.sizes.Sizeof(t) * 8), AlignInBits: uint64(d.sizes.Alignof(t) * 8), Encoding: llvm.DW_ATE_unsigned, }) default: bt := llvm.DIBasicType{ Name: t.String(), SizeInBits: uint64(d.sizes.Sizeof(t) * 8), AlignInBits: uint64(d.sizes.Alignof(t) * 8), } switch bi := t.Info(); { case bi&types.IsBoolean != 0: bt.Encoding = llvm.DW_ATE_boolean case bi&types.IsUnsigned != 0: bt.Encoding = llvm.DW_ATE_unsigned case bi&types.IsInteger != 0: bt.Encoding = llvm.DW_ATE_signed case bi&types.IsFloat != 0: bt.Encoding = llvm.DW_ATE_float case bi&types.IsComplex != 0: bt.Encoding = llvm.DW_ATE_imaginary_float case bi&types.IsUnsigned != 0: bt.Encoding = llvm.DW_ATE_unsigned default: panic(fmt.Sprintf("unhandled: %#v", t)) } return d.builder.CreateBasicType(bt) } } func (d *DIBuilder) descriptorPointer(t *types.Pointer) llvm.Metadata { return d.builder.CreatePointerType(llvm.DIPointerType{ Pointee: d.DIType(t.Elem()), SizeInBits: uint64(d.sizes.Sizeof(t) * 8), AlignInBits: uint64(d.sizes.Alignof(t) * 8), }) } func (d *DIBuilder) descriptorStruct(t *types.Struct, name string) llvm.Metadata { fields := make([]*types.Var, t.NumFields()) for i := range fields { fields[i] = t.Field(i) } offsets := d.sizes.Offsetsof(fields) members := make([]llvm.Metadata, len(fields)) for i, f := range fields { // TODO(axw) file/line where member is defined. t := f.Type() members[i] = d.builder.CreateMemberType(d.cu, llvm.DIMemberType{ Name: f.Name(), Type: d.DIType(t), SizeInBits: uint64(d.sizes.Sizeof(t) * 8), AlignInBits: uint64(d.sizes.Alignof(t) * 8), OffsetInBits: uint64(offsets[i] * 8), }) } // TODO(axw) file/line where struct is defined. return d.builder.CreateStructType(d.cu, llvm.DIStructType{ Name: name, SizeInBits: uint64(d.sizes.Sizeof(t) * 8), AlignInBits: uint64(d.sizes.Alignof(t) * 8), Elements: members, }) } func (d *DIBuilder) descriptorNamed(t *types.Named) llvm.Metadata { var diFile llvm.Metadata var line int if file := d.fset.File(t.Obj().Pos()); file != nil { line = file.Line(t.Obj().Pos()) diFile = d.getFile(file) } // Create a placeholder for the named type, to terminate cycles. name := t.Obj().Name() placeholder := d.builder.CreateReplaceableCompositeType(d.scope(), llvm.DIReplaceableCompositeType{ Tag: dwarf.TagStructType, Name: name, File: diFile, Line: line, }) d.types.Set(t, placeholder) typedef := d.builder.CreateTypedef(llvm.DITypedef{ Type: d.DIType(t.Underlying()), Name: name, File: diFile, Line: line, }) placeholder.ReplaceAllUsesWith(typedef) return typedef } func (d *DIBuilder) descriptorArray(t *types.Array, name string) llvm.Metadata { return d.builder.CreateArrayType(llvm.DIArrayType{ SizeInBits: uint64(d.sizes.Sizeof(t) * 8), AlignInBits: uint64(d.sizes.Alignof(t) * 8), ElementType: d.DIType(t.Elem()), Subscripts: []llvm.DISubrange{{Count: t.Len()}}, }) } func (d *DIBuilder) descriptorSlice(t *types.Slice, name string) llvm.Metadata { sliceStruct := types.NewStruct([]*types.Var{ types.NewVar(0, nil, "ptr", types.NewPointer(t.Elem())), types.NewVar(0, nil, "len", types.Typ[types.Int]), types.NewVar(0, nil, "cap", types.Typ[types.Int]), }, nil) return d.typeDebugDescriptor(sliceStruct, name) } func (d *DIBuilder) descriptorMap(t *types.Map, name string) llvm.Metadata { // FIXME: This should be DW_TAG_pointer_type to __go_map. return d.descriptorBasic(types.Typ[types.Uintptr], name) } func (d *DIBuilder) descriptorChan(t *types.Chan, name string) llvm.Metadata { // FIXME: This should be DW_TAG_pointer_type to __go_channel. return d.descriptorBasic(types.Typ[types.Uintptr], name) } func (d *DIBuilder) descriptorInterface(t *types.Interface, name string) llvm.Metadata { ifaceStruct := types.NewStruct([]*types.Var{ types.NewVar(0, nil, "type", types.NewPointer(types.Typ[types.Uint8])), types.NewVar(0, nil, "data", types.NewPointer(types.Typ[types.Uint8])), }, nil) return d.typeDebugDescriptor(ifaceStruct, name) } func (d *DIBuilder) descriptorSignature(t *types.Signature, name string) llvm.Metadata { // If there's a receiver change the receiver to an // additional (first) parameter, and take the value of // the resulting signature instead. if recv := t.Recv(); recv != nil { params := t.Params() paramvars := make([]*types.Var, int(params.Len()+1)) paramvars[0] = recv for i := 0; i < int(params.Len()); i++ { paramvars[i+1] = params.At(i) } params = types.NewTuple(paramvars...) t := types.NewSignature(nil, nil, params, t.Results(), t.Variadic()) return d.typeDebugDescriptor(t, name) } if dt, ok := d.types.At(t).(llvm.Metadata); ok { return dt } var returnType llvm.Metadata results := t.Results() switch n := results.Len(); n { case 0: returnType = d.DIType(nil) // void case 1: returnType = d.DIType(results.At(0).Type()) default: fields := make([]*types.Var, results.Len()) for i := range fields { f := results.At(i) // Structs may not have multiple fields // with the same name, excepting "_". if f.Name() == "" { f = types.NewVar(f.Pos(), f.Pkg(), "_", f.Type()) } fields[i] = f } returnType = d.typeDebugDescriptor(types.NewStruct(fields, nil), "") } var paramTypes []llvm.Metadata params := t.Params() if params != nil && params.Len() > 0 { paramTypes = make([]llvm.Metadata, params.Len()+1) paramTypes[0] = returnType for i := range paramTypes[1:] { paramTypes[i+1] = d.DIType(params.At(i).Type()) } } else { paramTypes = []llvm.Metadata{returnType} } // TODO(axw) get position of type definition for File field return d.builder.CreateSubroutineType(llvm.DISubroutineType{ Parameters: paramTypes, }) }