github.com/bir3/gocompiler@v0.9.2202/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 csh coverage.CounterSegmentHeader 30 tmp []byte 31 cflavor coverage.CounterFlavor 32 segs uint32 33 debug bool 34 } 35 36 func NewCoverageDataWriter(w io.Writer, flav coverage.CounterFlavor) *CoverageDataWriter { 37 r := &CoverageDataWriter{ 38 stab: &stringtab.Writer{}, 39 w: bufio.NewWriter(w), 40 41 tmp: make([]byte, 64), 42 cflavor: flav, 43 } 44 r.stab.InitWriter() 45 r.stab.Lookup("") 46 return r 47 } 48 49 // CounterVisitor describes a helper object used during counter file 50 // writing; when writing counter data files, clients pass a 51 // CounterVisitor to the write/emit routines, then the expectation is 52 // that the VisitFuncs method will then invoke the callback "f" with 53 // data for each function to emit to the file. 54 type CounterVisitor interface { 55 VisitFuncs(f CounterVisitorFn) error 56 } 57 58 // CounterVisitorFn describes a callback function invoked when writing 59 // coverage counter data. 60 type CounterVisitorFn func(pkid uint32, funcid uint32, counters []uint32) error 61 62 // Write writes the contents of the count-data file to the writer 63 // previously supplied to NewCoverageDataWriter. Returns an error 64 // if something went wrong somewhere with the write. 65 func (cfw *CoverageDataWriter) Write(metaFileHash [16]byte, args map[string]string, visitor CounterVisitor) error { 66 if err := cfw.writeHeader(metaFileHash); err != nil { 67 return err 68 } 69 return cfw.AppendSegment(args, visitor) 70 } 71 72 func padToFourByteBoundary(ws *slicewriter.WriteSeeker) error { 73 sz := len(ws.BytesWritten()) 74 zeros := []byte{0, 0, 0, 0} 75 rem := uint32(sz) % 4 76 if rem != 0 { 77 pad := zeros[:(4 - rem)] 78 if nw, err := ws.Write(pad); err != nil { 79 return err 80 } else if nw != len(pad) { 81 return fmt.Errorf("error: short write") 82 } 83 } 84 return nil 85 } 86 87 func (cfw *CoverageDataWriter) patchSegmentHeader(ws *slicewriter.WriteSeeker) error { 88 // record position 89 off, err := ws.Seek(0, io.SeekCurrent) 90 if err != nil { 91 return fmt.Errorf("error seeking in patchSegmentHeader: %v", err) 92 } 93 // seek back to start so that we can update the segment header 94 if _, err := ws.Seek(0, io.SeekStart); err != nil { 95 return fmt.Errorf("error seeking in patchSegmentHeader: %v", err) 96 } 97 if cfw.debug { 98 fmt.Fprintf(os.Stderr, "=-= writing counter segment header: %+v", cfw.csh) 99 } 100 if err := binary.Write(ws, binary.LittleEndian, cfw.csh); err != nil { 101 return err 102 } 103 // ... and finally return to the original offset. 104 if _, err := ws.Seek(off, io.SeekStart); err != nil { 105 return fmt.Errorf("error seeking in patchSegmentHeader: %v", err) 106 } 107 return nil 108 } 109 110 func (cfw *CoverageDataWriter) writeSegmentPreamble(args map[string]string, ws *slicewriter.WriteSeeker) error { 111 if err := binary.Write(ws, binary.LittleEndian, cfw.csh); err != nil { 112 return err 113 } 114 hdrsz := uint32(len(ws.BytesWritten())) 115 116 // Write string table and args to a byte slice (since we need 117 // to capture offsets at various points), then emit the slice 118 // once we are done. 119 cfw.stab.Freeze() 120 if err := cfw.stab.Write(ws); err != nil { 121 return err 122 } 123 cfw.csh.StrTabLen = uint32(len(ws.BytesWritten())) - hdrsz 124 125 akeys := make([]string, 0, len(args)) 126 for k := range args { 127 akeys = append(akeys, k) 128 } 129 sort.Strings(akeys) 130 131 wrULEB128 := func(v uint) error { 132 cfw.tmp = cfw.tmp[:0] 133 cfw.tmp = uleb128.AppendUleb128(cfw.tmp, v) 134 if _, err := ws.Write(cfw.tmp); err != nil { 135 return err 136 } 137 return nil 138 } 139 140 // Count of arg pairs. 141 if err := wrULEB128(uint(len(args))); err != nil { 142 return err 143 } 144 // Arg pairs themselves. 145 for _, k := range akeys { 146 ki := uint(cfw.stab.Lookup(k)) 147 if err := wrULEB128(ki); err != nil { 148 return err 149 } 150 v := args[k] 151 vi := uint(cfw.stab.Lookup(v)) 152 if err := wrULEB128(vi); err != nil { 153 return err 154 } 155 } 156 if err := padToFourByteBoundary(ws); err != nil { 157 return err 158 } 159 cfw.csh.ArgsLen = uint32(len(ws.BytesWritten())) - (cfw.csh.StrTabLen + hdrsz) 160 161 return nil 162 } 163 164 // AppendSegment appends a new segment to a counter data, with a new 165 // args section followed by a payload of counter data clauses. 166 func (cfw *CoverageDataWriter) AppendSegment(args map[string]string, visitor CounterVisitor) error { 167 cfw.stab = &stringtab.Writer{} 168 cfw.stab.InitWriter() 169 cfw.stab.Lookup("") 170 171 var err error 172 for k, v := range args { 173 cfw.stab.Lookup(k) 174 cfw.stab.Lookup(v) 175 } 176 177 ws := &slicewriter.WriteSeeker{} 178 if err = cfw.writeSegmentPreamble(args, ws); err != nil { 179 return err 180 } 181 if err = cfw.writeCounters(visitor, ws); err != nil { 182 return err 183 } 184 if err = cfw.patchSegmentHeader(ws); err != nil { 185 return err 186 } 187 if err := cfw.writeBytes(ws.BytesWritten()); err != nil { 188 return err 189 } 190 if err = cfw.writeFooter(); err != nil { 191 return err 192 } 193 if err := cfw.w.Flush(); err != nil { 194 return fmt.Errorf("write error: %v", err) 195 } 196 cfw.stab = nil 197 return nil 198 } 199 200 func (cfw *CoverageDataWriter) writeHeader(metaFileHash [16]byte) error { 201 // Emit file header. 202 ch := coverage.CounterFileHeader{ 203 Magic: coverage.CovCounterMagic, 204 Version: coverage.CounterFileVersion, 205 MetaHash: metaFileHash, 206 CFlavor: cfw.cflavor, 207 BigEndian: false, 208 } 209 if err := binary.Write(cfw.w, binary.LittleEndian, ch); err != nil { 210 return err 211 } 212 return nil 213 } 214 215 func (cfw *CoverageDataWriter) writeBytes(b []byte) error { 216 if len(b) == 0 { 217 return nil 218 } 219 nw, err := cfw.w.Write(b) 220 if err != nil { 221 return fmt.Errorf("error writing counter data: %v", err) 222 } 223 if len(b) != nw { 224 return fmt.Errorf("error writing counter data: short write") 225 } 226 return nil 227 } 228 229 func (cfw *CoverageDataWriter) writeCounters(visitor CounterVisitor, ws *slicewriter.WriteSeeker) error { 230 // Notes: 231 // - this version writes everything little-endian, which means 232 // a call is needed to encode every value (expensive) 233 // - we may want to move to a model in which we just blast out 234 // all counters, or possibly mmap the file and do the write 235 // implicitly. 236 ctrb := make([]byte, 4) 237 wrval := func(val uint32) error { 238 var buf []byte 239 var towr int 240 if cfw.cflavor == coverage.CtrRaw { 241 binary.LittleEndian.PutUint32(ctrb, val) 242 buf = ctrb 243 towr = 4 244 } else if cfw.cflavor == coverage.CtrULeb128 { 245 cfw.tmp = cfw.tmp[:0] 246 cfw.tmp = uleb128.AppendUleb128(cfw.tmp, uint(val)) 247 buf = cfw.tmp 248 towr = len(buf) 249 } else { 250 panic("internal error: bad counter flavor") 251 } 252 if sz, err := ws.Write(buf); err != nil { 253 return err 254 } else if sz != towr { 255 return fmt.Errorf("writing counters: short write") 256 } 257 return nil 258 } 259 260 // Write out entries for each live function. 261 emitter := func(pkid uint32, funcid uint32, counters []uint32) error { 262 cfw.csh.FcnEntries++ 263 if err := wrval(uint32(len(counters))); err != nil { 264 return err 265 } 266 267 if err := wrval(pkid); err != nil { 268 return err 269 } 270 271 if err := wrval(funcid); err != nil { 272 return err 273 } 274 for _, val := range counters { 275 if err := wrval(val); err != nil { 276 return err 277 } 278 } 279 return nil 280 } 281 if err := visitor.VisitFuncs(emitter); err != nil { 282 return err 283 } 284 return nil 285 } 286 287 func (cfw *CoverageDataWriter) writeFooter() error { 288 cfw.segs++ 289 cf := coverage.CounterFileFooter{ 290 Magic: coverage.CovCounterMagic, 291 NumSegments: cfw.segs, 292 } 293 if err := binary.Write(cfw.w, binary.LittleEndian, cf); err != nil { 294 return err 295 } 296 return nil 297 }