internal/mcp: add simd mapper tool to gopls mcp server
This CL adds a custom tool to gopls mcp server that maps simd
instructions to archsimd intrinsic calls.
diff --git a/gopls/internal/mcp/instructions.md b/gopls/internal/mcp/instructions.md
index 449ea8c..eeb921e 100644
--- a/gopls/internal/mcp/instructions.md
+++ b/gopls/internal/mcp/instructions.md
@@ -27,6 +27,9 @@
4. **Understand a package's public API**: When you need to understand what a package provides to external code (i.e., its public API), use `go_package_api`. This is especially useful for understanding third-party dependencies or other packages in the same monorepo.
EXAMPLE: to see the API of the `storage` package: `go_package_api({"packagePaths":["example.com/internal/storage"]})`
+5. **Translate SIMD assemblies**: When you need to understand SIMD instructions about their intrinsics representations (in package archsimd), use `go_simdmapper`. This is critical for your workflow if the user asked you to translate amd64 assemblies that contains SIMD instructions to Go code.
+ EXAMPLE: to know the API of "VPADDD X2, X9, X2": `go_simdmapper({"asm": "VPADDD X2, X9, X2"})`.
+
### Editing workflow
The editing workflow is iterative. You should cycle through these steps until the task is complete.
@@ -45,5 +48,4 @@
6. **Check for vulnerabilities**: If your edits involved adding or updating dependencies in the go.mod file, you MUST run a vulnerability check on the entire workspace. This ensures that the new dependencies do not introduce any security risks. This step should be performed after all build errors are resolved. EXAMPLE: `go_vulncheck({"pattern":"./..."})`
-7. **Run tests**: Once `go_diagnostics` reports no errors (and ONLY once there are no errors), run the tests for the packages you have changed. You can do this with `go test [packagePath...]`. Don't run `go test ./...` unless the user explicitly requests it, as doing so may slow down the iteration loop.
-
+7. **Run tests**: Once `go_diagnostics` reports no errors (and ONLY once there are no errors), run the tests for the packages you have changed. You can do this with `go test [packagePath...]`. Don't run `go test ./...` unless the user explicitly requests it, as doing so may slow down the iteration loop.
\ No newline at end of file
diff --git a/gopls/internal/mcp/mcp.go b/gopls/internal/mcp/mcp.go
index 6f4a0b4..ff151a7 100644
--- a/gopls/internal/mcp/mcp.go
+++ b/gopls/internal/mcp/mcp.go
@@ -171,7 +171,8 @@
"go_symbol_references",
"go_search",
"go_file_context",
- "go_vulncheck"}
+ "go_vulncheck",
+ "go_simdmapper"}
disabledTools := append(defaultTools,
// The fileMetadata tool is redundant with fileContext.
[]string{"go_file_metadata",
@@ -309,6 +310,19 @@
If no directory is provided, it defaults to the workspace root.
If no pattern is provided, it defaults to "./...".`,
}, h.vulncheckHandler)
+ case "go_simdmapper":
+ mcp.AddTool(mcpServer, &mcp.Tool{
+ Name: "go_simdmapper",
+ Description: `Given a Go assembler flavor simd instruction, this
+tool will return the archsimd API form of it and its CPU feature requirement.
+For example, given argument {"asm": "VPADDD X2, X9, X2"}, the tool will return
+"""
+ if archsimd.X86.AVX() {
+ X2 = X2.Add(X9)
+ }
+"""
+This tool only supports amd64 instructions at this moment.`,
+ }, h.simdMapperHandler)
}
}
diff --git a/gopls/internal/mcp/simdmapper.go b/gopls/internal/mcp/simdmapper.go
new file mode 100644
index 0000000..e3701ea
--- /dev/null
+++ b/gopls/internal/mcp/simdmapper.go
@@ -0,0 +1,92 @@
+// Copyright 2025 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 mcp
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+ "strings"
+
+ "github.com/modelcontextprotocol/go-sdk/mcp"
+)
+
+type simdMapperParams struct {
+ Asm string `json:"asm" jsonschema:"the assembly instruction to be mapped to simd intrinsics."`
+}
+
+type simdAsm struct {
+ name string
+ operands []string
+}
+
+// TODO: generate the full list of opOrder and opShape from simdgen.
+
+// If an entry here, the final [simdAsm.operands] field
+// would be reordered by this map.
+// e.g. intially VPINSRD $7, DX, X9, X11 => name: VPINSRD, operands: [7, DX, X9, X11]
+// would be reordered to [X9, 7, DX, X11] by doing operand_new[i] = operand_old[opOrder["VPINSRD"][i]]
+var opOrder = map[string][]int{
+ "VPINSRD": {2, 0, 1, 3},
+}
+
+// Returns the shape of the operands by index, after reordering if applicable.
+var opShape = map[string][]string{
+ "VPADDD": {"32x4", "32x4", "32x4"},
+ "VPINSRD": {"32x4", "immediate", "float32", "32x4"},
+}
+
+func (h *handler) simdMapperHandler(ctx context.Context, req *mcp.CallToolRequest, params simdMapperParams) (*mcp.CallToolResult, any, error) {
+ query := params.Asm
+ if len(query) == 0 {
+ return nil, nil, fmt.Errorf("empty query")
+ }
+ var b strings.Builder
+
+ // parse the assembly instruction into simdAsm
+ // Example: VPADDD X2, X9, X2 => name: VPADDD, operands: [X2, X9, X2]
+ // Example: VPADDD (R11), X9, X2 => name: VPADDD, operands: [archsimd.Load32x4(r11), X9, X2]
+ // Example: VPADDD -7(DI)(R8*8), X2, K1, X2 => name: VPADDD, operands: [archsimd.Load32x4(di+r8*8-7), X2, K1, X2]
+ // Example: VPINSRD $7, DX, X9, X11 => name: VPINSRD, operands: [X9, 7, DX, X11]
+ var asm simdAsm
+ parts := strings.Fields(query)
+ if len(parts) == 0 {
+ return nil, nil, fmt.Errorf("empty query")
+ }
+ asm.name = parts[0]
+ asm.operands = parts[1:]
+ if _, ok := opOrder[asm.name]; ok {
+ newOps := make([]string, len(opOrder[asm.name]))
+ for i, op := range opOrder[asm.name] {
+ newOps[i] = asm.operands[op]
+ }
+ asm.operands = newOps
+ }
+ for i, op := range asm.operands {
+ if strings.HasPrefix(op, "$") {
+ asm.operands[i] = op[1:]
+ } else if strings.Contains(op, "(") {
+ addrParts := regexp.MustCompile(`(-?\d+)?(\(\w+\))?\(\w+\)`).FindStringSubmatch(op)
+ if len(addrParts) != 4 {
+ return nil, nil, fmt.Errorf("failed to parse address register: %s", op)
+ }
+ addr := ""
+ if addrParts[2] != "" {
+ addr += strings.Trim(strings.Trim(addrParts[2], "("), ")")
+ }
+ if addrParts[3] != "" {
+ addr += "+" + strings.Trim(strings.Trim(addrParts[3], "("), ")")
+ }
+ if addrParts[1] != "" {
+ addr += addrParts[1]
+ }
+ asm.operands[i] = fmt.Sprintf("archsimd.Load%s(%s)", opShape[asm.name][i], addr)
+ }
+ }
+ // Write the final result.
+ fmt.Fprintf(&b, "%s.%s(%s)\n", asm.operands[0], asm.name, strings.Join(asm.operands[1:], ", "))
+
+ return textResult(b.String()), nil, nil
+}
| 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. |
| Hold | +1 |
Thanks, this is an interesting idea. But it would be good to discuss it on the issue tracker before we commit to code. My main concerns are:
(a) that it is very obscure and likely to be useful to less than 1% of users;
(b) that gopls' MCP server should really be a set of thin wrappers around existing debugged and tested packages (e.g. LSP operations, analyzers) rather than large amounts of new logic; and
(c) it adds more than 300KB to the executable, and the big map may have a noticeable cost on allocation and init time. (Its costs are doubled because gopls has a second process for telemetry.) A lazily decompressed rodata section would be better.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |