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