pair.go raw
1 package main
2
3 import (
4 "encoding/json"
5 "fmt"
6 "os"
7 "path/filepath"
8 "strings"
9 )
10
11 func enrichManifest(importPath, outdir string) error {
12 manifestPath := filepath.Join(outdir, "manifest.json")
13 data, err := os.ReadFile(manifestPath)
14 if err != nil {
15 return fmt.Errorf("read manifest: %w", err)
16 }
17 var manifest PackageManifest
18 if err := json.Unmarshal(data, &manifest); err != nil {
19 return fmt.Errorf("parse manifest: %w", err)
20 }
21
22 pairManifest(&manifest, importPath, outdir)
23
24 data, err = json.MarshalIndent(&manifest, "", " ")
25 if err != nil {
26 return err
27 }
28 if err := os.WriteFile(manifestPath, data, 0644); err != nil {
29 return err
30 }
31
32 paired := 0
33 for _, f := range manifest.Files {
34 for _, s := range f.Segments {
35 if len(s.IRFiles) > 0 {
36 paired++
37 }
38 }
39 }
40 fmt.Printf(" PAIRED: %d segments\n", paired)
41
42 if err := writeCSVManifest(&manifest, outdir); err != nil {
43 return fmt.Errorf("csv manifest: %w", err)
44 }
45
46 return nil
47 }
48
49 // writeCSVManifest writes manifest.csv: tab-separated, one row per segment.
50 // Columns: id, kind, name, ast_file, ir_O0, asm_O0, bin_O0, lineinfo
51 func writeCSVManifest(manifest *PackageManifest, outdir string) error {
52 csvPath := filepath.Join(outdir, "manifest.csv")
53 f, err := os.Create(csvPath)
54 if err != nil {
55 return err
56 }
57 defer f.Close()
58
59 f.WriteString("id\tkind\tname\tast_file\tast_dump\tir_O0\tasm_O0\tbin_O0\tlineinfo\n")
60
61 for _, fm := range manifest.Files {
62 for _, seg := range fm.Segments {
63 irFile := ""
64 if sf, ok := seg.IRFiles["O0"]; ok && sf != nil {
65 irFile = sf.File
66 }
67 asmFile := ""
68 if sf, ok := seg.ASMFiles["O0"]; ok && sf != nil {
69 asmFile = sf.File
70 }
71 binFile := ""
72 if sf, ok := seg.BinFiles["O0"]; ok && sf != nil {
73 binFile = sf.File
74 }
75 liFile := ""
76 if seg.Lineinfo != nil {
77 liFile = seg.Lineinfo.File
78 }
79 fmt.Fprintf(f, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
80 seg.ID, seg.Kind, seg.Name, seg.ASTFile, seg.ASTDump,
81 irFile, asmFile, binFile, liFile)
82 }
83 }
84
85 fmt.Printf(" wrote %s\n", csvPath)
86 return nil
87 }
88
89 func pairManifest(manifest *PackageManifest, importPath, outdir string) {
90 irFiles := listSegDir(filepath.Join(outdir, "ir"))
91 asmFiles := listSegDir(filepath.Join(outdir, "asm"))
92 binFiles := listSegDir(filepath.Join(outdir, "bin"))
93 lineinfoFiles := listSegDir(filepath.Join(outdir, "lineinfo"))
94
95 for fi := range manifest.Files {
96 fm := &manifest.Files[fi]
97 for si := range fm.Segments {
98 seg := &fm.Segments[si]
99 if seg.Kind != "func" && seg.Kind != "method" {
100 continue
101 }
102
103 irName := buildIRName(importPath, seg.Kind, seg.Name)
104 seg.IRName = irName
105 safeName := sanitizeIRName(irName)
106
107 seg.IRFiles = matchOptFiles(irFiles, safeName, ".ll")
108 seg.ASMFiles = matchOptFiles(asmFiles, safeName, ".s")
109 seg.BinFiles = matchOptFiles(binFiles, safeName, ".bin.hex")
110
111 liFile := safeName + ".lineinfo"
112 if info, ok := lineinfoFiles[liFile]; ok {
113 seg.Lineinfo = &SizedFile{File: liFile, SizeBytes: info}
114 }
115 }
116 }
117 }
118
119 func buildIRName(importPath, kind, name string) string {
120 if kind == "method" {
121 // name is like "(*RuneIter).Next" → "(*unicode/utf8.RuneIter).Next"
122 if strings.HasPrefix(name, "(*") {
123 dotIdx := strings.Index(name, ").")
124 if dotIdx > 2 {
125 typeName := name[2:dotIdx]
126 method := name[dotIdx+2:]
127 return "(*" + importPath + "." + typeName + ")." + method
128 }
129 }
130 // Value receiver: "(T).M" → "(unicode/utf8.T).M"
131 if strings.HasPrefix(name, "(") {
132 dotIdx := strings.Index(name, ").")
133 if dotIdx > 1 {
134 typeName := name[1:dotIdx]
135 method := name[dotIdx+2:]
136 return "(" + importPath + "." + typeName + ")." + method
137 }
138 }
139 }
140 return importPath + "." + name
141 }
142
143 func matchOptFiles(files map[string]int, baseName, ext string) OptFileMap {
144 m := OptFileMap{}
145 for _, opt := range optLevels {
146 label := optLabel(opt)
147 filename := baseName + "." + label + ext
148 if size, ok := files[filename]; ok {
149 m[label] = &SizedFile{File: filename, SizeBytes: size}
150 }
151 }
152 if len(m) == 0 {
153 return nil
154 }
155 return m
156 }
157
158 func listSegDir(dir string) map[string]int {
159 m := map[string]int{}
160 entries, err := os.ReadDir(dir)
161 if err != nil {
162 return m
163 }
164 for _, e := range entries {
165 if e.IsDir() {
166 continue
167 }
168 info, err := e.Info()
169 if err != nil {
170 continue
171 }
172 m[e.Name()] = int(info.Size())
173 }
174 return m
175 }
176