diff --git a/compiler/compiler.go b/compiler/compiler.go index e4bb613840..80c60caff0 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1264,6 +1264,11 @@ func (b *builder) createFunctionStart(intrinsic bool) { } else if b.fn.Syntax() != nil { // Create debug info file if needed. b.difunc = b.attachDebugInfo(b.fn) + } else if b.fn.Pos().IsValid() { + // Synthetic wrappers such as $bound and $thunk have no syntax but share the + // position of the wrapped method. See https://pkg.go.dev/golang.org/x/tools/go/ssa#Function + pos := b.program.Fset.Position(b.fn.Pos()) + b.difunc = b.attachDebugInfoRaw(b.fn, b.llvmFn, "", pos.Filename, pos.Line) } b.setDebugLocation(b.fn.Pos()) } diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 4831a6868e..654b828093 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -339,6 +339,44 @@ func TestAggregateExportedInterfaceABI(t *testing.T) { } } +func TestWrapperDebugInfo(t *testing.T) { + mod, errs := testCompilePackageWithDebug(t, &compileopts.Options{Target: "wasm"}, "wrapper-debug.go", true) + defer mod.Dispose() + for _, err := range errs { + t.Error(err) + } + + for _, name := range []string{ + "(main.wrapperValue).get$bound", + "(main.wrapperValue).get$thunk", + "(*main.wrapperValue).get", + } { + fn := mod.NamedFunction(name) + if fn.IsNil() { + t.Errorf("missing function %s", name) + continue + } + sp := fn.Subprogram() + if sp.IsNil() { + t.Errorf("%s has no subprogram", name) + continue + } + if line := sp.SubprogramLine(); line != 5 { + t.Errorf("%s subprogram line is %d, want 5", name, line) + } + if file := sp.ScopeFile().FileFilename(); !strings.HasSuffix(file, "wrapper-debug.go") { + t.Errorf("%s subprogram file is %q", name, file) + } + for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) { + for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { + if !inst.IsACallInst().IsNil() && inst.InstructionDebugLoc().IsNil() { + t.Errorf("%s has a call without a debug location", name) + } + } + } + } +} + func TestValidateWasmFunctionParameters(t *testing.T) { for _, test := range []struct { name string diff --git a/compiler/testdata/wrapper-debug.go b/compiler/testdata/wrapper-debug.go new file mode 100644 index 0000000000..ea5596249b --- /dev/null +++ b/compiler/testdata/wrapper-debug.go @@ -0,0 +1,20 @@ +package main + +type wrapperValue struct{ n int } + +func (v wrapperValue) get() int { + return v.n +} + +func boundWrapper(v wrapperValue) func() int { + return v.get +} + +func thunkWrapper() func(wrapperValue) int { + return wrapperValue.get +} + +func pointerWrapper(v *wrapperValue) int { + var i interface{ get() int } = v + return i.get() +}