cmd/compile: add loclist for pruned reg ret params
Certain return parameters which live in registers and end up pruned by
earlier SSA passes will end up with a DWARF entry but no location list.
This patch fixes this by ensuring those params have proper location
lists generated for them.
diff --git a/src/cmd/compile/internal/dwarfgen/dwarf.go b/src/cmd/compile/internal/dwarfgen/dwarf.go
index 6ab39d2..b548a6d 100644
--- a/src/cmd/compile/internal/dwarfgen/dwarf.go
+++ b/src/cmd/compile/internal/dwarfgen/dwarf.go
@@ -191,7 +191,7 @@
dcl := apDecls
if fnsym.WasInlined() {
dcl = preInliningDcls(fnsym)
- } else {
+ } else if len(dcl) != len(fn.Type().RecvParamsResults()) {
// The backend's stackframe pass prunes away entries from the
// fn's Dcl list, including PARAMOUT nodes that correspond to
// output params passed in registers. Add back in these
@@ -221,6 +221,8 @@
// reliably report its contents."
// For non-SSA-able arguments, however, the correct information
// is known -- they have a single home on the stack.
+ var outidx int
+ debug, _ := fn.DebugInfo.(*ssa.FuncDebug)
for _, n := range dcl {
if selected.Has(n) {
continue
@@ -229,6 +231,11 @@
if c == '.' || n.Type().IsUntyped() {
continue
}
+
+ // Occasionally the dcl list will contain duplicates. Add here
+ // so the `selected.Has` check above filters them out.
+ selected.Add(n)
+
if n.Class == ir.PPARAM && !ssa.CanSSA(n.Type()) {
// SSA-able args get location lists, and may move in and
// out of registers, so those are handled elsewhere.
@@ -241,6 +248,7 @@
decls = append(decls, n)
continue
}
+
typename := dwarf.InfoPrefix + types.TypeSymName(n.Type())
decls = append(decls, n)
tag := dwarf.DW_TAG_variable
@@ -273,11 +281,20 @@
DictIndex: n.DictIndex,
ClosureOffset: closureOffset(n, closureVars),
}
- if n.Esc() == ir.EscHeap {
+ if debug != nil && isReturnValue && n.IsOutputParamInRegisters() {
+ // Create register based location list
+ // This assumes that the register return params in the dcl list is sorted correctly and
+ // params appear in order they are defined in the function signature.
+ list := createRegisterOnlyLocationList(debug.RegOutputParamRegList[outidx], debug.EntryID)
+ outidx++
+ dvar.PutLocationList = func(listSym, startPC dwarf.Sym) {
+ debug.PutLocationList(list, base.Ctxt, listSym.(*obj.LSym), startPC.(*obj.LSym))
+ }
+ }
+ if debug != nil && n.Esc() == ir.EscHeap {
if n.Heapaddr == nil {
base.Fatalf("invalid heap allocated var without Heapaddr")
}
- debug := fn.DebugInfo.(*ssa.FuncDebug)
list := createHeapDerefLocationList(n, debug.EntryID)
dvar.PutLocationList = func(listSym, startPC dwarf.Sym) {
debug.PutLocationList(list, base.Ctxt, listSym.(*obj.LSym), startPC.(*obj.LSym))
@@ -294,6 +311,37 @@
return decls, vars
}
+// CreateRegisterOnlyLocationList creates a minimal location list that specifies
+// a value is in a register from function entry onwards.
+// This follows the same pattern as createHeapDerefLocationList.
+func createRegisterOnlyLocationList(regs []int8, entryID ssa.ID) []byte {
+ ctxt := base.Ctxt
+
+ var list []byte
+ var sizeIdx int
+ list, sizeIdx = ssa.SetupLocList(base.Ctxt, entryID, list, ssa.BlockStart.ID, ssa.FuncEnd.ID)
+
+ // The actual location expression
+ for _, reg := range regs {
+ if reg < 32 {
+ list = append(list, dwarf.DW_OP_reg0+byte(reg))
+ } else {
+ list = append(list, dwarf.DW_OP_regx)
+ list = dwarf.AppendUleb128(list, uint64(reg))
+ }
+ if len(regs) > 1 {
+ list = append(list, dwarf.DW_OP_piece)
+ list = dwarf.AppendUleb128(list, uint64(ctxt.Arch.RegSize))
+ }
+ }
+
+ // Update the size
+ locSize := len(list) - sizeIdx - 2
+ ctxt.Arch.ByteOrder.PutUint16(list[sizeIdx:], uint16(locSize))
+
+ return list
+}
+
// sortDeclsAndVars sorts the decl and dwarf var lists according to
// parameter declaration order, so as to insure that when a subprogram
// DIE is emitted, its parameter children appear in declaration order.
diff --git a/src/cmd/compile/internal/ssa/config.go b/src/cmd/compile/internal/ssa/config.go
index f209717..b9afa54 100644
--- a/src/cmd/compile/internal/ssa/config.go
+++ b/src/cmd/compile/internal/ssa/config.go
@@ -381,6 +381,10 @@
func (c *Config) Ctxt() *obj.Link { return c.ctxt }
+func (c *Config) Reg(i int8) int16 { return c.registers[i].objNum }
+func (c *Config) IntParamReg(i abi.RegIndex) int8 { return c.intParamRegs[i] }
+func (c *Config) FloatParamReg(i abi.RegIndex) int8 { return c.floatParamRegs[i] }
+
func (c *Config) haveByteSwap(size int64) bool {
switch size {
case 8:
diff --git a/src/cmd/compile/internal/ssa/debug.go b/src/cmd/compile/internal/ssa/debug.go
index c9a3e42..1a39af7 100644
--- a/src/cmd/compile/internal/ssa/debug.go
+++ b/src/cmd/compile/internal/ssa/debug.go
@@ -38,7 +38,8 @@
LocationLists [][]byte
// Register-resident output parameters for the function. This is filled in at
// SSA generation time.
- RegOutputParams []*ir.Name
+ RegOutputParams []*ir.Name
+ RegOutputParamRegList [][]int8
// Variable declarations that were removed during optimization
OptDcl []*ir.Name
// The ssa.Func.EntryID value, used to build location lists for
diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go
index 1e21595..416212d 100644
--- a/src/cmd/compile/internal/ssagen/ssa.go
+++ b/src/cmd/compile/internal/ssagen/ssa.go
@@ -439,6 +439,15 @@
var params *abi.ABIParamResultInfo
params = s.f.ABISelf.ABIAnalyze(fn.Type(), true)
+ abiRegIndexToRegister := func(reg abi.RegIndex) int8 {
+ i := s.f.ABISelf.FloatIndexFor(reg)
+ if i >= 0 { // float PR
+ return s.f.Config.FloatParamReg(abi.RegIndex(i))
+ } else {
+ return s.f.Config.IntParamReg(reg)
+ }
+ }
+
// The backend's stackframe pass prunes away entries from the fn's
// Dcl list, including PARAMOUT nodes that correspond to output
// params passed in registers. Walk the Dcl list and capture these
@@ -448,6 +457,16 @@
for _, n := range fn.Dcl {
if n.Class == ir.PPARAMOUT && n.IsOutputParamInRegisters() {
debugInfo.RegOutputParams = append(debugInfo.RegOutputParams, n)
+ op := params.OutParam(len(debugInfo.RegOutputParamRegList))
+ debugInfo.RegOutputParamRegList = append(debugInfo.RegOutputParamRegList, make([]int8, len(op.Registers)))
+ for i, reg := range op.Registers {
+ idx := len(debugInfo.RegOutputParamRegList) - 1
+ // TODO(deparker) This is a rather large amount of conversions to get from
+ // an abi.RegIndex to a Dwarf register number. Can this be simplified?
+ abiReg := s.f.Config.Reg(abiRegIndexToRegister(reg))
+ dwarfReg := base.Ctxt.Arch.DWARFRegisters[abiReg]
+ debugInfo.RegOutputParamRegList[idx][i] = int8(dwarfReg)
+ }
}
}
fn.DebugInfo = &debugInfo
diff --git a/src/cmd/link/dwarf_test.go b/src/cmd/link/dwarf_test.go
index d269aa7..dd7509b 100644
--- a/src/cmd/link/dwarf_test.go
+++ b/src/cmd/link/dwarf_test.go
@@ -10,17 +10,76 @@
"cmd/internal/objfile"
"cmd/internal/quoted"
"debug/dwarf"
+ "debug/elf"
+ "fmt"
+ "internal/abi"
"internal/platform"
"internal/testenv"
"os"
"os/exec"
"path"
"path/filepath"
+ "regexp"
"runtime"
"strings"
"testing"
)
+// loclistExpressionAt returns the bytes of the counted location description,
+// ignoring the DW_LLE_* operands.
+func loclistExpressionAt(r *bytes.Reader, off int64) ([]byte, error) {
+ list := make([]byte, 0, 8) // start with a small buffer, loclists are usually compact
+ if _, err := r.Seek(off, 0); err != nil {
+ return nil, fmt.Errorf("failed to seek to offset %d: %v", off, err)
+ }
+ for {
+ b, err := r.ReadByte()
+ if err != nil || b == cmddwarf.DW_LLE_end_of_list {
+ break
+ }
+ switch b {
+ case cmddwarf.DW_LLE_base_addressx:
+ _ = readULEB128(r)
+ case cmddwarf.DW_LLE_start_end:
+ // Read and ignore start / end.
+ _ = readULEB128(r)
+ _ = readULEB128(r)
+
+ l := readULEB128(r)
+ buf := make([]byte, l)
+ r.Read(buf)
+ list = append(list, buf...)
+ case cmddwarf.DW_LLE_offset_pair:
+ // Read and ignore pair.
+ _ = readULEB128(r)
+ _ = readULEB128(r)
+
+ l := readULEB128(r)
+ buf := make([]byte, l)
+ r.Read(buf)
+ list = append(list, buf...)
+ default:
+ return nil, fmt.Errorf("unexpected loclist entry: %x", b)
+ }
+ }
+ return list, nil
+}
+
+func readULEB128(r *bytes.Reader) uint64 {
+ var shift uint
+ var value uint64
+
+ for {
+ b, _ := r.ReadByte()
+ value |= (uint64(b&0x7F) << shift)
+ if b&0x80 == 0 {
+ break
+ }
+ shift += 7
+ }
+ return value
+}
+
// TestMain allows this test binary to run as a -toolexec wrapper for
// the 'go' command. If LINK_TEST_TOOLEXEC is set, TestMain runs the
// binary as if it were cmd/link, and otherwise runs the requested
@@ -259,16 +318,9 @@
// This test ensures that variables promoted to the heap, specifically
// function return parameters, have correct location lists generated.
-//
-// TODO(deparker): This test is intentionally limited to GOOS=="linux"
-// and scoped to net.sendFile, which was the function reported originally in
-// issue #65405. There is relevant discussion in https://go-review.googlesource.com/c/go/+/684377
-// pertaining to these limitations. There are other missing location lists which must be fixed
-// particularly in functions where `linkname` is involved.
func TestDWARFLocationList(t *testing.T) {
- if runtime.GOOS != "linux" {
- t.Skip("skipping test on non-linux OS")
- }
+ const dwarfGoLanguage = 22
+
testenv.MustHaveCGO(t)
testenv.MustHaveGoBuild(t)
@@ -304,6 +356,11 @@
// Find the net.sendFile function and check its return parameter location list
reader := d.Reader()
+ pattern := `\w+(\.\w+)*\.func\d+`
+ re := regexp.MustCompile(pattern)
+
+ var cu *dwarf.Entry
+ var lr *dwarf.LineReader
for {
entry, err := reader.Next()
if err != nil {
@@ -313,14 +370,37 @@
break
}
- // Look for the net.sendFile subprogram
- if entry.Tag == dwarf.TagSubprogram {
- fnName, ok := entry.Val(dwarf.AttrName).(string)
- if !ok || fnName != "net.sendFile" {
+ if entry.Tag == dwarf.TagCompileUnit {
+ if lang, _ := entry.Val(dwarf.AttrLanguage).(int64); lang != dwarfGoLanguage {
reader.SkipChildren()
+ continue // Skip non-Go compile units.
+ }
+ cu = entry
+ lr, err = d.LineReader(cu)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ if cu != nil && entry.Tag == dwarf.TagSubprogram {
+ fnName, ok := entry.Val(dwarf.AttrName).(string)
+ if !ok || re.MatchString(fnName) {
+ continue // Skip if no name or an anonymous function. TODO(deparker): fix loclists for anonymous functions (possible unused param).
+ }
+
+ if strings.HasPrefix(fnName, "runtime.") {
+ // Ignore runtime for now, there are a lot of runtime functions which use
+ // certain pragmas that seemingly cause the location lists to be empty such as
+ // cgo_unsafe_args and nosplit.
+ // TODO(deparker): fix loclists for runtime functions.
continue
}
+ fi, ok := entry.Val(dwarf.AttrDeclFile).(int64)
+ if !ok || lr.Files()[fi].Name == "<autogenerated>" {
+ continue // Function may be implemented in assembly, skip it.
+ }
+
for {
paramEntry, err := reader.Next()
if err != nil {
@@ -333,6 +413,15 @@
if paramEntry.Tag == dwarf.TagFormalParameter {
paramName, _ := paramEntry.Val(dwarf.AttrName).(string)
+ if paramName == ".dict" {
+ continue
+ }
+
+ // Skip anonymous / blank (unused) params.
+ if strings.HasPrefix(paramName, "~p") {
+ continue
+ }
+
// Check if this parameter has a location attribute
if loc := paramEntry.Val(dwarf.AttrLocation); loc != nil {
switch locData := loc.(type) {
@@ -358,3 +447,180 @@
}
}
}
+
+// TestDWARFReturnValueRegisters verifies that DWARF location lists for function
+// return values follow the platform ABI register conventions.
+func TestDWARFReturnValueRegisters(t *testing.T) {
+ const dwarfGoLanguage = 22
+ const dwarfOpReg0 = 0x50 // DW_OP_reg0
+ const dwarfOpRegx = 0x90 // DW_OP_regx
+
+ if runtime.GOOS != "linux" {
+ t.Skip("skipping test on non-linux OS")
+ }
+
+ testenv.MustHaveCGO(t)
+ testenv.MustHaveGoBuild(t)
+
+ if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) {
+ t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH)
+ }
+
+ t.Parallel()
+
+ testProgram := `
+ package main
+
+ //go:noinline
+ func multiReturn() (int, int, int, string, float64) {
+ return 1, 2, 3, "test", 4.5
+ }
+
+ //go:noinline
+ func singleReturn() int {
+ return 42
+ }
+
+ func main() {
+ a, b, _, _, _ := multiReturn()
+ f := singleReturn()
+ _ = a + b + f
+ }
+ `
+
+ tmpDir := t.TempDir()
+ srcFile := filepath.Join(tmpDir, "test_returns.go")
+ if err := os.WriteFile(srcFile, []byte(testProgram), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ exe := filepath.Join(tmpDir, "test_returns.exe")
+ cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-toolexec", os.Args[0], "-o", exe, srcFile)
+ cmd.Env = append(os.Environ(), "CGO_CFLAGS=")
+ cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("go build failed: %v\n%s", err, out)
+ }
+
+ f, err := elf.Open(exe)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+
+ s := f.Section(".debug_loclists")
+ data, err := s.Data()
+ if err != nil {
+ t.Fatal(err)
+ }
+ locRdr := bytes.NewReader(data)
+
+ d, err := f.DWARF()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ reader := d.Reader()
+
+ var foundTestFunc bool
+ var cu *dwarf.Entry
+ for {
+ entry, err := reader.Next()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if entry == nil {
+ break
+ }
+
+ if entry.Tag == dwarf.TagCompileUnit {
+ if lang, _ := entry.Val(dwarf.AttrLanguage).(int64); lang != dwarfGoLanguage {
+ reader.SkipChildren()
+ continue
+ }
+ cu = entry
+ }
+
+ if cu != nil && entry.Tag == dwarf.TagSubprogram {
+ var outintparam int
+ var outfloatparam int
+ var outparam *int
+ fnName, ok := entry.Val(dwarf.AttrName).(string)
+ if !ok {
+ continue
+ }
+ if fnName != "main.multiReturn" && fnName != "main.singleReturn" {
+ reader.SkipChildren()
+ continue
+ }
+ foundTestFunc = true
+
+ for {
+ paramEntry, err := reader.Next()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if paramEntry == nil || paramEntry.Tag == 0 {
+ break
+ }
+
+ if paramEntry.Tag == dwarf.TagFormalParameter {
+ paramName, _ := paramEntry.Val(dwarf.AttrName).(string)
+
+ if isret, _ := paramEntry.Val(dwarf.AttrVarParam).(bool); !isret {
+ continue
+ }
+
+ ti := paramEntry.Val(dwarf.AttrType).(dwarf.Offset)
+ typ, err := d.Type(ti)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if _, ok := typ.(*dwarf.BasicType); !ok {
+ // TODO(deparker) improve test to assert about more complex types
+ continue
+ }
+
+ base := 0
+ if strings.HasPrefix(typ.Common().Name, "int") {
+ outparam = &outintparam
+ }
+ if strings.HasPrefix(typ.Common().Name, "float") {
+ base = abi.IntArgRegs
+ outparam = &outfloatparam
+ }
+
+ if loc := paramEntry.Val(dwarf.AttrLocation); loc != nil {
+ switch off := loc.(type) {
+ case []byte:
+ // Not using location list stored in .debug_loclists
+ if len(off) == 0 {
+ t.Errorf("missing location for output parameter %s.%s", fnName, paramName)
+ }
+ case int64:
+ expr, err := loclistExpressionAt(locRdr, off)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i, op := range expr {
+ regidx := int(op - cmddwarf.DW_OP_reg0)
+ if regidx != base+*outparam+i {
+ t.Errorf("unexpected register index %d for output parameter %s wanted %d", regidx, paramName, base+outintparam+i)
+ }
+ }
+ *outparam++
+ }
+ } else {
+ t.Errorf("missing location for output parameter %s", paramName)
+ }
+ }
+ }
+ }
+ }
+
+ if !foundTestFunc {
+ t.Error("No test functions with return values found in DWARF info")
+ }
+}
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Certain return parameters which live in registers and end up pruned byI think this description needs to be clarified or reworded a bit -- "pruned" is kind of a generic term and it's not clear from the comment exactly what it is that's being pruned or when.
I think it might be clearer if the comment could emphasis that the SSA stack frame layout pass is removing a DCL node (internal compiler IR construct) for the out param which then triggers the problem.
// a value is in a register from function entry onwards.Is this correct? Seems to me that for a large function you could have other values living in the register in question at an early point and then only fill in those registers with the return value at a later point.
Regarding the changes in this file, this seems to add a lot of complexity -- we're taking steps towards adding a real DWARF location expression parser, which seems like a big bite to chew off. Why not use the dwtest framework in x/debug instead?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Certain return parameters which live in registers and end up pruned byI think this description needs to be clarified or reworded a bit -- "pruned" is kind of a generic term and it's not clear from the comment exactly what it is that's being pruned or when.
I think it might be clearer if the comment could emphasis that the SSA stack frame layout pass is removing a DCL node (internal compiler IR construct) for the out param which then triggers the problem.
Ack, I will update.
// a value is in a register from function entry onwards.Is this correct? Seems to me that for a large function you could have other values living in the register in question at an early point and then only fill in those registers with the return value at a later point.
You're definitely right with this. I'll add a test to x/debug while I'm porting the existing one to cover this and thread through the liveness information along with the registers.
Regarding the changes in this file, this seems to add a lot of complexity -- we're taking steps towards adding a real DWARF location expression parser, which seems like a big bite to chew off. Why not use the dwtest framework in x/debug instead?
Agreed, I need to get into the habit of adding the tests there instead. I will send a CL to x/debug with a test and revert the changes to this file.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Apologies for the delay on this, I have been on vacation for the last few weeks. Now that I've returned I'll rebase this and wrap this work up.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Ok, I've updated the patch with better liveness tracking. I also added some more comprehensive tests to `x/debug`: https://go-review.googlesource.com/c/debug/+/714340.
A few things I wanted to mention: while this CL doesn't solve all the problems, it does improve the situation for DWARF generation in optimized binaries and can be used as a jumping off point for further improvements. I'm very committed to improving DWARF generation in optimized binaries over the next few releases so I am definitely signed on to continue this work in follow-up CLs, I already have a few improvements planned once this lands.
That being said, this current implementation passes all tests in the stdlib on my machine via `all.bash` and all tests within `x/debug` with the exception of `TestDwarfVariableLocations/Issue65405`, which I had some questions about.
So the resulting location list is mostly correct for that function, but for some reason it is not extending to the actual `ret` instruction, stopping instead a few instructions short.
Concretely, here is the location list for `~r0`:
```
0000a2ac 0000000000000009 (index into .debug_addr) 000000000056bac0 (base address)
0000a2ae 000000000056bb7e 000000000056bba6 (DW_OP_reg0 (rax))
0000a2b5 <End of list>
0000a2b6 0000000000000009 (index into .debug_addr) 000000000056bac0 (base address)
```
And here is the relevant dump of the assembly:
```
...
server.go:2374 0x56bb7e 48897020 mov qword ptr [rax+0x20], rsi
main.go:66 0x56bb82 bb01000000 mov ebx, 0x1
main.go:66 0x56bb87 488d0db2a00e00 lea rcx, ptr [_cgo_yield+4920]
main.go:66 0x56bb8e 4889c7 mov rdi, rax
main.go:66 0x56bb91 488d05209a0900 lea rax, ptr [rip+0x99a20]
main.go:66 0x56bb98 e823cfffff call $net/http.Handle
main.go:67 0x56bb9d 488b442420 mov rax, qword ptr [rsp+0x20]
main.go:67 0x56bba2 31db xor ebx, ebx
main.go:67 0x56bba4 31c9 xor ecx, ecx
main.go:67 0x56bba6 4883c430 add rsp, 0x30
main.go:67 0x56bbaa 5d pop rbp
main.go:67 0x56bbab c3 ret
...
```
You can see that the location list ends at:
```
0x56bba6 4883c430 add rsp, 0x30
```
Instead of what I would expect which is the `ret` instruction at:
```
0x56bbab c3 ret
```
I'm not sure why this is the case, so if anyone can provide some insight and point me in the right direction that would be great.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Certain return parameters which live in registers and end up pruned byDerek ParkerI think this description needs to be clarified or reworded a bit -- "pruned" is kind of a generic term and it's not clear from the comment exactly what it is that's being pruned or when.
I think it might be clearer if the comment could emphasis that the SSA stack frame layout pass is removing a DCL node (internal compiler IR construct) for the out param which then triggers the problem.
Ack, I will update.
Done
// a value is in a register from function entry onwards.Derek ParkerIs this correct? Seems to me that for a large function you could have other values living in the register in question at an early point and then only fill in those registers with the return value at a later point.
You're definitely right with this. I'll add a test to x/debug while I'm porting the existing one to cover this and thread through the liveness information along with the registers.
Derek ParkerRegarding the changes in this file, this seems to add a lot of complexity -- we're taking steps towards adding a real DWARF location expression parser, which seems like a big bite to chew off. Why not use the dwtest framework in x/debug instead?
Agreed, I need to get into the habit of adding the tests there instead. I will send a CL to x/debug with a test and revert the changes to this file.
I haven't removed these yet from this CL, but I will before it lands. I've instead added tests to x/debug.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Maybe because the last 3 instructions are epilog, generated by cmd/internal/obj/amd64/*. Those instructions don't exist when liveness information is computed.
We should probably extend dwarf ranges depending on what cmd/internal/obj does. If an instruction X expands into 3 instructions Y1, Y2, Y3, then if a value is live at the start of X then it should be live at the start of Y1, Y2, and Y3 (not just Y1). Although that may not universally be the right thing, it is for RET.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
There are a couple of places in ssa/debug.go where if we know a value is live until the end of the function, we use the special FuncEnd marker, e.g.
would that be helpful here maybe?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
I used that before, but it didn't work in all cases. Specifically functions with multiple return statements. Using ssa.FuncEnd.ID would cause an `~r0` val to be live from the first return statement to the end of the function, which is incorrect. See the following (small, contrived) example:
```
func myfunc(i int) int {
if i < 10 {
return i*i // ~r0 should be live here
}
println("~r0 should not be live here")
i += 10
return i // ~r0 should be live here
```
If we used `ssa.FuncEnd.ID` for both ranges, the first range would be active way past when it should. To that end I created a new value `ssa.FuncLocalEnd.ID` which acts similar and is handled by `GetPC` to find the closest ret to the enclosing block.
Added more nuanced liveness tracking for these register return params, handling multiple return statements better than before.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Derek ParkerRegarding the changes in this file, this seems to add a lot of complexity -- we're taking steps towards adding a real DWARF location expression parser, which seems like a big bite to chew off. Why not use the dwtest framework in x/debug instead?
Derek ParkerAgreed, I need to get into the habit of adding the tests there instead. I will send a CL to x/debug with a test and revert the changes to this file.
I haven't removed these yet from this CL, but I will before it lands. I've instead added tests to x/debug.
Done
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
Looks reasonable, but I don't think I have the context to give a +2.
Than, could you take another look?
// as RegOutputParamRegList. Each pair represents [blockID, valueID] where valueIDHow are pairs formed?
// Block ranges where each return value register is live. Indexed the same
// as RegOutputParamRegList. Each pair represents [blockID, valueID] where valueIDRegOutputParamRegList should have a comment describing how it is indexed.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// as RegOutputParamRegList. Each pair represents [blockID, valueID] where valueIDDerek ParkerHow are pairs formed?
This is no longer represented as pairs, updated comment to be more accurate.
// Block ranges where each return value register is live. Indexed the same
// as RegOutputParamRegList. Each pair represents [blockID, valueID] where valueIDRegOutputParamRegList should have a comment describing how it is indexed.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +2 |
// startRanges contains a list of SSA IDs where the value is live, and it is assumed by this functionstartIDs
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// startRanges contains a list of SSA IDs where the value is live, and it is assumed by this functionDerek ParkerstartIDs
Done
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
Hey, with the freeze already active just curious if this will hold off until it reopens for 1.27 work. I don't feel strongly about this being included in the 1.26 release so if there's a lot of hoops to jump through for exceptions, etc... I'm happy to wait for this to land until the tree reopens.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Hey, with the freeze already active just curious if this will hold off until it reopens for 1.27 work. I don't feel strongly about this being included in the 1.26 release so if there's a lot of hoops to jump through for exceptions, etc... I'm happy to wait for this to land until the tree reopens.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |