| //===- debug.go - debug info builder --------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // 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, |
| }) |
| fnptr.SetSubprogram(d.fn) |
| } |
| |
| // 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) { |
| 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(), 3, 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), |
| Encoding: llvm.DW_ATE_unsigned, |
| }) |
| default: |
| bt := llvm.DIBasicType{ |
| Name: t.String(), |
| SizeInBits: uint64(d.sizes.Sizeof(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: uint32(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: uint32(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: uint32(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: uint32(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, |
| }) |
| } |