diff --git a/compiler/calls.go b/compiler/calls.go index b5296f91..f0a49735 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -55,6 +55,25 @@ func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type { } } +// Expand an argument type to a list of offsets from the start of the object. +// Used together with expandFormalParam to get the offset of each value from the +// start of the non-expanded value. +func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 { + switch t.TypeKind() { + case llvm.StructTypeKind: + fields := c.flattenAggregateTypeOffsets(t) + if len(fields) <= MaxFieldsPerParam { + return fields + } else { + // failed to lower + return []uint64{0} + } + default: + // TODO: split small arrays + return []uint64{0} + } +} + // Equivalent of expandFormalParamType for parameter values. func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value { switch v.Type().TypeKind() { @@ -92,6 +111,27 @@ func (c *Compiler) flattenAggregateType(t llvm.Type) []llvm.Type { } } +// Return the offsets from the start of the object if this object type were +// flattened like in flattenAggregate. Used together with flattenAggregate to +// know the start indices of each value in the non-flattened object. +func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 { + switch t.TypeKind() { + case llvm.StructTypeKind: + fields := make([]uint64, 0, t.StructElementTypesCount()) + for fieldIndex, field := range t.StructElementTypes() { + suboffsets := c.flattenAggregateTypeOffsets(field) + offset := c.targetData.ElementOffset(t, fieldIndex) + for i := range suboffsets { + suboffsets[i] += offset + } + fields = append(fields, suboffsets...) + } + return fields + default: + return []uint64{0} + } +} + // Break down a struct into its elementary types for argument passing. The value // equivalent of flattenAggregateType func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value { diff --git a/compiler/compiler.go b/compiler/compiler.go index 1885f246..48a03550 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -52,7 +52,6 @@ type Compiler struct { 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 @@ -96,7 +95,6 @@ func NewCompiler(pkgName string, config Config) (*Compiler, error) { c := &Compiler{ Config: config, difiles: make(map[string]llvm.Metadata), - ditypes: make(map[string]llvm.Metadata), } target, err := llvm.GetTargetFromTriple(config.Triple) @@ -252,7 +250,7 @@ func (c *Compiler) Compile(mainPath string) []error { // Initialize debug information. if c.Debug { c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{ - Language: llvm.DW_LANG_Go, + Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?) File: mainPath, Dir: "", Producer: "TinyGo", @@ -387,6 +385,13 @@ func (c *Compiler) Compile(mainPath string) []error { llvm.ConstInt(c.ctx.Int32Type(), 3, false).ConstantAsMetadata(), // DWARF version }), ) + c.mod.AddNamedMetadataOperand("llvm.module.flags", + c.ctx.MDNode([]llvm.Metadata{ + llvm.ConstInt(c.ctx.Int32Type(), 1, false).ConstantAsMetadata(), + llvm.GlobalContext().MDString("Dwarf Version"), + llvm.ConstInt(c.ctx.Int32Type(), 4, false).ConstantAsMetadata(), + }), + ) c.dibuilder.Finalize() } @@ -550,39 +555,166 @@ func isPointer(typ types.Type) bool { // Get the DWARF type for this Go type. func (c *Compiler) getDIType(typ types.Type) llvm.Metadata { - name := typ.String() - if dityp, ok := c.ditypes[name]; ok { - return dityp - } else { - llvmType := c.getLLVMType(typ) - sizeInBytes := c.targetData.TypeAllocSize(llvmType) + llvmType := c.getLLVMType(typ) + sizeInBytes := c.targetData.TypeAllocSize(llvmType) + switch typ := typ.(type) { + case *types.Array: + return c.dibuilder.CreateArrayType(llvm.DIArrayType{ + SizeInBits: sizeInBytes * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, + ElementType: c.getDIType(typ.Elem()), + Subscripts: []llvm.DISubrange{ + llvm.DISubrange{ + Lo: 0, + Count: typ.Len(), + }, + }, + }) + case *types.Basic: 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 + 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 { + return c.dibuilder.CreatePointerType(llvm.DIPointerType{ + Name: "unsafe.Pointer", + SizeInBits: c.targetData.TypeAllocSize(llvmType) * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, + AddressSpace: 0, + }) + } else if typ.Info()&types.IsString != 0 { + return c.dibuilder.CreateStructType(llvm.Metadata{}, llvm.DIStructType{ + Name: "string", + SizeInBits: sizeInBytes * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, + Elements: []llvm.Metadata{ + c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ + Name: "ptr", + SizeInBits: c.targetData.TypeAllocSize(c.i8ptrType) * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(c.i8ptrType)) * 8, + OffsetInBits: 0, + Type: c.getDIType(types.NewPointer(types.Typ[types.Byte])), + }), + c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ + Name: "len", + SizeInBits: c.targetData.TypeAllocSize(c.uintptrType) * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(c.uintptrType)) * 8, + OffsetInBits: c.targetData.ElementOffset(llvmType, 1) * 8, + Type: c.getDIType(types.Typ[types.Uintptr]), + }), + }, + }) + } else { + panic("unknown basic type") } - // TODO: other types - dityp = c.dibuilder.CreateBasicType(llvm.DIBasicType{ - Name: name, + return c.dibuilder.CreateBasicType(llvm.DIBasicType{ + Name: typ.String(), SizeInBits: sizeInBytes * 8, Encoding: encoding, }) - c.ditypes[name] = dityp - return dityp + case *types.Chan: + return c.getDIType(types.NewPointer(c.ir.Program.ImportedPackage("runtime").Members["channel"].(*ssa.Type).Type())) + case *types.Interface: + return c.getDIType(c.ir.Program.ImportedPackage("runtime").Members["_interface"].(*ssa.Type).Type()) + case *types.Map: + return c.getDIType(types.NewPointer(c.ir.Program.ImportedPackage("runtime").Members["hashmap"].(*ssa.Type).Type())) + case *types.Named: + return c.dibuilder.CreateTypedef(llvm.DITypedef{ + Type: c.getDIType(typ.Underlying()), + Name: typ.String(), + }) + case *types.Pointer: + return c.dibuilder.CreatePointerType(llvm.DIPointerType{ + Pointee: c.getDIType(typ.Elem()), + SizeInBits: c.targetData.TypeAllocSize(llvmType) * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, + AddressSpace: 0, + }) + case *types.Signature: + // actually a closure + fields := llvmType.StructElementTypes() + return c.dibuilder.CreateStructType(llvm.Metadata{}, llvm.DIStructType{ + SizeInBits: sizeInBytes * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, + Elements: []llvm.Metadata{ + c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ + Name: "context", + SizeInBits: c.targetData.TypeAllocSize(fields[1]) * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(fields[1])) * 8, + OffsetInBits: 0, + Type: c.getDIType(types.Typ[types.UnsafePointer]), + }), + c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ + Name: "fn", + SizeInBits: c.targetData.TypeAllocSize(fields[0]) * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(fields[0])) * 8, + OffsetInBits: c.targetData.ElementOffset(llvmType, 1) * 8, + Type: c.getDIType(types.Typ[types.UnsafePointer]), + }), + }, + }) + case *types.Slice: + fields := llvmType.StructElementTypes() + return c.dibuilder.CreateStructType(llvm.Metadata{}, llvm.DIStructType{ + Name: typ.String(), + SizeInBits: sizeInBytes * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, + Elements: []llvm.Metadata{ + c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ + Name: "ptr", + SizeInBits: c.targetData.TypeAllocSize(fields[0]) * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(fields[0])) * 8, + OffsetInBits: 0, + Type: c.getDIType(types.NewPointer(typ.Elem())), + }), + c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ + Name: "len", + SizeInBits: c.targetData.TypeAllocSize(c.uintptrType) * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(c.uintptrType)) * 8, + OffsetInBits: c.targetData.ElementOffset(llvmType, 1) * 8, + Type: c.getDIType(types.Typ[types.Uintptr]), + }), + c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ + Name: "cap", + SizeInBits: c.targetData.TypeAllocSize(c.uintptrType) * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(c.uintptrType)) * 8, + OffsetInBits: c.targetData.ElementOffset(llvmType, 2) * 8, + Type: c.getDIType(types.Typ[types.Uintptr]), + }), + }, + }) + case *types.Struct: + elements := make([]llvm.Metadata, typ.NumFields()) + for i := range elements { + field := typ.Field(i) + fieldType := field.Type() + if _, ok := fieldType.Underlying().(*types.Pointer); ok { + // XXX hack to avoid recursive types + fieldType = types.Typ[types.UnsafePointer] + } + llvmField := c.getLLVMType(fieldType) + elements[i] = c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ + Name: field.Name(), + SizeInBits: c.targetData.TypeAllocSize(llvmField) * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmField)) * 8, + OffsetInBits: c.targetData.ElementOffset(llvmType, i) * 8, + Type: c.getDIType(fieldType), + }) + } + return c.dibuilder.CreateStructType(llvm.Metadata{}, llvm.DIStructType{ + SizeInBits: sizeInBytes * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, + Elements: elements, + }) + default: + panic("unknown type while generating DWARF debug type: " + typ.String()) } } @@ -733,15 +865,30 @@ func (c *Compiler) parseFunc(frame *Frame) { // Add debug information to this parameter (if available) if c.Debug && frame.fn.Syntax() != nil { pos := c.ir.Program.Fset.Position(frame.fn.Syntax().Pos()) - c.dibuilder.CreateParameterVariable(frame.difunc, llvm.DIParameterVariable{ + diType := c.getDIType(param.Type()) + dbgParam := c.dibuilder.CreateParameterVariable(frame.difunc, llvm.DIParameterVariable{ Name: param.Name(), File: c.difiles[pos.Filename], Line: pos.Line, - Type: c.getDIType(param.Type()), + Type: diType, AlwaysPreserve: true, ArgNo: i + 1, }) - // TODO: set the value of this parameter. + loc := c.builder.GetCurrentDebugLocation() + if len(fields) == 1 { + expr := c.dibuilder.CreateExpression(nil) + c.dibuilder.InsertValueAtEnd(fields[0], dbgParam, expr, loc, entryBlock) + } else { + fieldOffsets := c.expandFormalParamOffsets(llvmType) + for i, field := range fields { + expr := c.dibuilder.CreateExpression([]int64{ + 0x1000, // DW_OP_LLVM_fragment + int64(fieldOffsets[i]) * 8, // offset in bits + int64(c.targetData.TypeAllocSize(field.Type())) * 8, // size in bits + }) + c.dibuilder.InsertValueAtEnd(field, dbgParam, expr, loc, entryBlock) + } + } } } diff --git a/interp/frame.go b/interp/frame.go index 18631eea..7e280b87 100644 --- a/interp/frame.go +++ b/interp/frame.go @@ -347,6 +347,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), implements, false)} case callee.Name() == "runtime.nanotime": fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)} + case callee.Name() == "llvm.dbg.value": + // do nothing case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic": // This are all print instructions, which necessarily have side // effects but no results. diff --git a/interp/interp.go b/interp/interp.go index ea59334e..14cd6e78 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -18,7 +18,6 @@ type Eval struct { TargetData llvm.TargetData Debug bool builder llvm.Builder - dibuilder *llvm.DIBuilder dirtyGlobals map[llvm.Value]struct{} sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results } @@ -38,7 +37,6 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error { dirtyGlobals: map[llvm.Value]struct{}{}, } e.builder = mod.Context().NewBuilder() - e.dibuilder = llvm.NewDIBuilder(mod) initAll := mod.NamedFunction(name) bb := initAll.EntryBasicBlock() @@ -49,7 +47,6 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error { e.builder.SetInsertPointBefore(bb.FirstInstruction()) dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy") e.builder.SetInsertPointBefore(dummy) - e.builder.SetInstDebugLocation(bb.FirstInstruction()) var initCalls []llvm.Value for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { if inst == dummy { diff --git a/interp/scan.go b/interp/scan.go index d388aa52..96e61526 100644 --- a/interp/scan.go +++ b/interp/scan.go @@ -35,6 +35,8 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult { return &sideEffectResult{severity: sideEffectLimited} case "runtime.interfaceImplements": return &sideEffectResult{severity: sideEffectNone} + case "llvm.dbg.value": + return &sideEffectResult{severity: sideEffectNone} } if e.sideEffectFuncs == nil { e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)