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  }