File: assembly.go

package info (click to toggle)
golang-golang-x-tools 1%3A0.25.0%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 22,724 kB
  • sloc: javascript: 2,027; asm: 1,645; sh: 166; yacc: 155; makefile: 49; ansic: 8
file content (133 lines) | stat: -rw-r--r-- 4,168 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package golang

// This file produces the "Browse GOARCH assembly of f" HTML report.
//
// See also:
// - ./codeaction.go - computes the symbol and offers the CodeAction command.
// - ../server/command.go - handles the command by opening a web page.
// - ../server/server.go - handles the HTTP request and calls this function.

import (
	"bytes"
	"context"
	"fmt"
	"html"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"golang.org/x/tools/gopls/internal/cache"
	"golang.org/x/tools/internal/gocommand"
)

// AssemblyHTML returns an HTML document containing an assembly listing of the selected function.
//
// TODO(adonovan):
// - display a "Compiling..." message as a cold build can be slow.
// - cross-link jumps and block labels, like github.com/aclements/objbrowse.
func AssemblyHTML(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, symbol string, web Web) ([]byte, error) {
	// Compile the package with -S, and capture its stderr stream.
	inv, cleanupInvocation, err := snapshot.GoCommandInvocation(false, &gocommand.Invocation{
		Verb:       "build",
		Args:       []string{"-gcflags=-S", "."},
		WorkingDir: filepath.Dir(pkg.Metadata().CompiledGoFiles[0].Path()),
	})
	if err != nil {
		return nil, err // e.g. failed to write overlays (rare)
	}
	defer cleanupInvocation()
	_, stderr, err, _ := snapshot.View().GoCommandRunner().RunRaw(ctx, *inv)
	if err != nil {
		return nil, err // e.g. won't compile
	}
	content := stderr.String()

	escape := html.EscapeString

	// Produce the report.
	title := fmt.Sprintf("%s assembly for %s",
		escape(snapshot.View().GOARCH()),
		escape(symbol))
	var buf bytes.Buffer
	buf.WriteString(`<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>` + escape(title) + `</title>
  <link rel="stylesheet" href="/assets/common.css">
  <script src="/assets/common.js"></script>
</head>
<body>
<h1>` + title + `</h1>
<p>
  <a href='https://go.dev/doc/asm'>A Quick Guide to Go's Assembler</a>
</p>
<p>
  Experimental. <a href='https://github.com/golang/go/issues/67478'>Contributions welcome!</a>
</p>
<p>
  Click on a source line marker <code>L1234</code> to navigate your editor there.
  (VS Code users: please upvote <a href='https://github.com/microsoft/vscode/issues/208093'>#208093</a>)
</p>
<p>
  Reload the page to recompile.
</p>
<pre>
`)

	// insnRx matches an assembly instruction line.
	// Submatch groups are: (offset-hex-dec, file-line-column, instruction).
	insnRx := regexp.MustCompile(`^(\s+0x[0-9a-f ]+)\(([^)]*)\)\s+(.*)$`)

	// Parse the functions of interest out of the listing.
	// Each function is of the form:
	//
	//     symbol STEXT k=v...
	//         0x0000 00000 (/file.go:123) NOP...
	//         ...
	//
	// Allow matches of symbol, symbol.func1, symbol.deferwrap, etc.
	on := false
	for _, line := range strings.Split(content, "\n") {
		// start of function symbol?
		if strings.Contains(line, " STEXT ") {
			on = strings.HasPrefix(line, symbol) &&
				(line[len(symbol)] == ' ' || line[len(symbol)] == '.')
		}
		if !on {
			continue // within uninteresting symbol
		}

		// In lines of the form
		//   "\t0x0000 00000 (/file.go:123) NOP..."
		// replace the "(/file.go:123)" portion with an "L0123" source link.
		// Skip filenames of the form "<foo>".
		if parts := insnRx.FindStringSubmatch(line); parts != nil {
			link := "     " // if unknown
			if file, linenum, ok := cutLast(parts[2], ":"); ok && !strings.HasPrefix(file, "<") {
				if linenum, err := strconv.Atoi(linenum); err == nil {
					text := fmt.Sprintf("L%04d", linenum)
					link = sourceLink(text, web.SrcURL(file, linenum, 1))
				}
			}
			fmt.Fprintf(&buf, "%s\t%s\t%s", escape(parts[1]), link, escape(parts[3]))
		} else {
			buf.WriteString(escape(line))
		}
		buf.WriteByte('\n')
	}
	return buf.Bytes(), nil
}

// cutLast is the "last" analogue of [strings.Cut].
func cutLast(s, sep string) (before, after string, ok bool) {
	if i := strings.LastIndex(s, sep); i >= 0 {
		return s[:i], s[i+len(sep):], true
	}
	return s, "", false
}