github.com/bir3/gocompiler@v0.3.205/src/internal/coverage/encodecounter/encode.go (about) 1 // Copyright 2021 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package encodecounter 6 7 import ( 8 "bufio" 9 "encoding/binary" 10 "fmt" 11 "github.com/bir3/gocompiler/src/internal/coverage" 12 "github.com/bir3/gocompiler/src/internal/coverage/slicewriter" 13 "github.com/bir3/gocompiler/src/internal/coverage/stringtab" 14 "github.com/bir3/gocompiler/src/internal/coverage/uleb128" 15 "io" 16 "os" 17 "sort" 18 ) 19 20 // This package contains APIs and helpers for encoding initial portions 21 // of the counter data files emitted at runtime when coverage instrumentation 22 // is enabled. Counter data files may contain multiple segments; the file 23 // header and first segment are written via the "Write" method below, and 24 // additional segments can then be added using "AddSegment". 25 26 type CoverageDataWriter struct { 27 stab *stringtab.Writer 28 w *bufio.Writer 29 tmp []byte 30 cflavor coverage.CounterFlavor 31 segs uint32 32 debug bool 33 } 34 35 func NewCoverageDataWriter(w io.Writer, flav coverage.CounterFlavor) *CoverageDataWriter { 36 r := &CoverageDataWriter{ 37 stab: &stringtab.Writer{}, 38 w: bufio.NewWriter(w), 39 40 tmp: make([]byte, 64), 41 cflavor: flav, 42 } 43 r.stab.InitWriter() 44 r.stab.Lookup("") 45 return r 46 } 47 48 // CounterVisitor describes a helper object used during counter file 49 // writing; when writing counter data files, clients pass a 50 // CounterVisitor to the write/emit routines. The writers will then 51 // first invoke the visitor's NumFuncs() method to find out how many 52 // function's worth of data to write, then it will invoke VisitFuncs. 53 // The expectation is that the VisitFuncs method will then invoke the 54 // callback "f" with data for each function to emit to the file. 55 type CounterVisitor interface { 56 NumFuncs() (int, error) 57 VisitFuncs(f CounterVisitorFn) error 58 } 59 60 // CounterVisitorFn describes a callback function invoked when writing 61 // coverage counter data. 62 type CounterVisitorFn func(pkid uint32, funcid uint32, counters []uint32) error 63 64 // Write writes the contents of the count-data file to the writer 65 // previously supplied to NewCoverageDataWriter. Returns an error 66 // if something went wrong somewhere with the write. 67 func (cfw *CoverageDataWriter) Write(metaFileHash [16]byte, args map[string]string, visitor CounterVisitor) error { 68 if err := cfw.writeHeader(metaFileHash); err != nil { 69 return err 70 } 71 return cfw.AppendSegment(args, visitor) 72 } 73 74 func padToFourByteBoundary(ws *slicewriter.WriteSeeker) error { 75 sz := len(ws.BytesWritten()) 76 zeros := []byte{0, 0, 0, 0} 77 rem := uint32(sz) % 4 78 if rem != 0 { 79 pad := zeros[:(4 - rem)] 80 if nw, err := ws.Write(pad); err != nil { 81 return err 82 } else if nw != len(pad) { 83 return fmt.Errorf("error: short write") 84 } 85 } 86 return nil 87 } 88 89 func (cfw *CoverageDataWriter) writeSegmentPreamble(args map[string]string, visitor CounterVisitor) error { 90 var csh coverage.CounterSegmentHeader 91 if nf, err := visitor.NumFuncs(); err != nil { 92 return err 93 } else { 94 csh.FcnEntries = uint64(nf) 95 } 96 97 // Write string table and args to a byte slice (since we need 98 // to capture offsets at various points), then emit the slice 99 // once we are done. 100 cfw.stab.Freeze() 101 ws := &slicewriter.WriteSeeker{} 102 if err := cfw.stab.Write(ws); err != nil { 103 return err 104 } 105 csh.StrTabLen = uint32(len(ws.BytesWritten())) 106 107 akeys := make([]string, 0, len(args)) 108 for k := range args { 109 akeys = append(akeys, k) 110 } 111 sort.Strings(akeys) 112 113 wrULEB128 := func(v uint) error { 114 cfw.tmp = cfw.tmp[:0] 115 cfw.tmp = uleb128.AppendUleb128(cfw.tmp, v) 116 if _, err := ws.Write(cfw.tmp); err != nil { 117 return err 118 } 119 return nil 120 } 121 122 // Count of arg pairs. 123 if err := wrULEB128(uint(len(args))); err != nil { 124 return err 125 } 126 // Arg pairs themselves. 127 for _, k := range akeys { 128 ki := uint(cfw.stab.Lookup(k)) 129 if err := wrULEB128(ki); err != nil { 130 return err 131 } 132 v := args[k] 133 vi := uint(cfw.stab.Lookup(v)) 134 if err := wrULEB128(vi); err != nil { 135 return err 136 } 137 } 138 if err := padToFourByteBoundary(ws); err != nil { 139 return err 140 } 141 csh.ArgsLen = uint32(len(ws.BytesWritten())) - csh.StrTabLen 142 143 if cfw.debug { 144 fmt.Fprintf(os.Stderr, "=-= counter segment header: %+v", csh) 145 fmt.Fprintf(os.Stderr, " FcnEntries=0x%x StrTabLen=0x%x ArgsLen=0x%x\n", 146 csh.FcnEntries, csh.StrTabLen, csh.ArgsLen) 147 } 148 149 // At this point we can now do the actual write. 150 if err := binary.Write(cfw.w, binary.LittleEndian, csh); err != nil { 151 return err 152 } 153 if err := cfw.writeBytes(ws.BytesWritten()); err != nil { 154 return err 155 } 156 return nil 157 } 158 159 // AppendSegment appends a new segment to a counter data, with a new 160 // args section followed by a payload of counter data clauses. 161 func (cfw *CoverageDataWriter) AppendSegment(args map[string]string, visitor CounterVisitor) error { 162 cfw.stab = &stringtab.Writer{} 163 cfw.stab.InitWriter() 164 cfw.stab.Lookup("") 165 166 var err error 167 for k, v := range args { 168 cfw.stab.Lookup(k) 169 cfw.stab.Lookup(v) 170 } 171 172 if err = cfw.writeSegmentPreamble(args, visitor); err != nil { 173 return err 174 } 175 if err = cfw.writeCounters(visitor); err != nil { 176 return err 177 } 178 if err = cfw.writeFooter(); err != nil { 179 return err 180 } 181 if err := cfw.w.Flush(); err != nil { 182 return fmt.Errorf("write error: %v", err) 183 } 184 cfw.stab = nil 185 return nil 186 } 187 188 func (cfw *CoverageDataWriter) writeHeader(metaFileHash [16]byte) error { 189 // Emit file header. 190 ch := coverage.CounterFileHeader{ 191 Magic: coverage.CovCounterMagic, 192 Version: coverage.CounterFileVersion, 193 MetaHash: metaFileHash, 194 CFlavor: cfw.cflavor, 195 BigEndian: false, 196 } 197 if err := binary.Write(cfw.w, binary.LittleEndian, ch); err != nil { 198 return err 199 } 200 return nil 201 } 202 203 func (cfw *CoverageDataWriter) writeBytes(b []byte) error { 204 if len(b) == 0 { 205 return nil 206 } 207 nw, err := cfw.w.Write(b) 208 if err != nil { 209 return fmt.Errorf("error writing counter data: %v", err) 210 } 211 if len(b) != nw { 212 return fmt.Errorf("error writing counter data: short write") 213 } 214 return nil 215 } 216 217 func (cfw *CoverageDataWriter) writeCounters(visitor CounterVisitor) error { 218 // Notes: 219 // - this version writes everything little-endian, which means 220 // a call is needed to encode every value (expensive) 221 // - we may want to move to a model in which we just blast out 222 // all counters, or possibly mmap the file and do the write 223 // implicitly. 224 ctrb := make([]byte, 4) 225 wrval := func(val uint32) error { 226 var buf []byte 227 var towr int 228 if cfw.cflavor == coverage.CtrRaw { 229 binary.LittleEndian.PutUint32(ctrb, val) 230 buf = ctrb 231 towr = 4 232 } else if cfw.cflavor == coverage.CtrULeb128 { 233 cfw.tmp = cfw.tmp[:0] 234 cfw.tmp = uleb128.AppendUleb128(cfw.tmp, uint(val)) 235 buf = cfw.tmp 236 towr = len(buf) 237 } else { 238 panic("internal error: bad counter flavor") 239 } 240 if sz, err := cfw.w.Write(buf); err != nil { 241 return err 242 } else if sz != towr { 243 return fmt.Errorf("writing counters: short write") 244 } 245 return nil 246 } 247 248 // Write out entries for each live function. 249 emitter := func(pkid uint32, funcid uint32, counters []uint32) error { 250 if err := wrval(uint32(len(counters))); err != nil { 251 return err 252 } 253 254 if err := wrval(pkid); err != nil { 255 return err 256 } 257 258 if err := wrval(funcid); err != nil { 259 return err 260 } 261 for _, val := range counters { 262 if err := wrval(val); err != nil { 263 return err 264 } 265 } 266 return nil 267 } 268 if err := visitor.VisitFuncs(emitter); err != nil { 269 return err 270 } 271 return nil 272 } 273 274 func (cfw *CoverageDataWriter) writeFooter() error { 275 cfw.segs++ 276 cf := coverage.CounterFileFooter{ 277 Magic: coverage.CovCounterMagic, 278 NumSegments: cfw.segs, 279 } 280 if err := binary.Write(cfw.w, binary.LittleEndian, cf); err != nil { 281 return err 282 } 283 return nil 284 }