blob: b07b3a4a480573b8a2279bf9dce2df1445608e22 [file] [log] [blame]
// UNREVIEWED
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package noder
import (
"bytes"
"encoding/binary"
"fmt"
"go/constant"
"io"
"math/big"
"runtime"
"cmd/compile/internal/base"
)
type pkgEncoder struct {
elems [numRelocs][]string
stringsIdx map[string]int
}
func newPkgEncoder() pkgEncoder {
return pkgEncoder{
stringsIdx: make(map[string]int),
}
}
func (pw *pkgEncoder) dump(out io.Writer) {
writeUint32 := func(x uint32) {
assert(binary.Write(out, binary.LittleEndian, x) == nil)
}
var sum uint32
for _, elems := range &pw.elems {
sum += uint32(len(elems))
writeUint32(sum)
}
sum = 0
for _, elems := range &pw.elems {
for _, elem := range elems {
sum += uint32(len(elem))
writeUint32(sum)
}
}
for _, elems := range &pw.elems {
for _, elem := range elems {
_, err := io.WriteString(out, elem)
assert(err == nil)
}
}
}
func (pw *pkgEncoder) stringIdx(s string) int {
if idx, ok := pw.stringsIdx[s]; ok {
assert(pw.elems[relocString][idx] == s)
return idx
}
idx := len(pw.elems[relocString])
pw.elems[relocString] = append(pw.elems[relocString], s)
pw.stringsIdx[s] = idx
return idx
}
func (pw *pkgEncoder) newEncoder(k reloc, marker syncMarker) encoder {
e := pw.newEncoderRaw(k)
e.sync(marker)
return e
}
func (pw *pkgEncoder) newEncoderRaw(k reloc) encoder {
idx := len(pw.elems[k])
pw.elems[k] = append(pw.elems[k], "") // placeholder
return encoder{
p: pw,
k: k,
idx: idx,
}
}
// Encoders
type encoder struct {
p *pkgEncoder
relocs []relocEnt
data bytes.Buffer
encodingRelocHeader bool
k reloc
idx int
}
func (w *encoder) flush() int {
var sb bytes.Buffer // TODO(mdempsky): strings.Builder after #44505 is resolved
// Backup the data so we write the relocations at the front.
var tmp bytes.Buffer
io.Copy(&tmp, &w.data)
// TODO(mdempsky): Consider writing these out separately so they're
// easier to strip, along with function bodies, so that we can prune
// down to just the data that's relevant to go/types.
if w.encodingRelocHeader {
base.Fatalf("encodingRelocHeader already true; recursive flush?")
}
w.encodingRelocHeader = true
w.sync(syncRelocs)
w.len(len(w.relocs))
for _, rent := range w.relocs {
w.sync(syncReloc)
w.len(int(rent.kind))
w.len(rent.idx)
}
io.Copy(&sb, &w.data)
io.Copy(&sb, &tmp)
w.p.elems[w.k][w.idx] = sb.String()
return w.idx
}
func (w *encoder) checkErr(err error) {
if err != nil {
base.Fatalf("unexpected error: %v", err)
}
}
func (w *encoder) rawUvarint(x uint64) {
var buf [binary.MaxVarintLen64]byte
n := binary.PutUvarint(buf[:], x)
_, err := w.data.Write(buf[:n])
w.checkErr(err)
}
func (w *encoder) rawVarint(x int64) {
// Zig-zag encode.
ux := uint64(x) << 1
if x < 0 {
ux = ^ux
}
w.rawUvarint(ux)
}
func (w *encoder) rawReloc(r reloc, idx int) int {
// TODO(mdempsky): Use map for lookup.
for i, rent := range w.relocs {
if rent.kind == r && rent.idx == idx {
return i
}
}
i := len(w.relocs)
w.relocs = append(w.relocs, relocEnt{r, idx})
return i
}
func (w *encoder) sync(m syncMarker) {
if !enableSync {
return
}
// Writing out stack frame string references requires working
// relocations, but writing out the relocations themselves involves
// sync markers. To prevent infinite recursion, we simply trim the
// stack frame for sync markers within the relocation header.
var frames []string
if !w.encodingRelocHeader && base.Debug.SyncFrames > 0 {
pcs := make([]uintptr, base.Debug.SyncFrames)
n := runtime.Callers(2, pcs)
frames = fmtFrames(pcs[:n]...)
}
// TODO(mdempsky): Save space by writing out stack frames as a
// linked list so we can share common stack frames.
w.rawUvarint(uint64(m))
w.rawUvarint(uint64(len(frames)))
for _, frame := range frames {
w.rawUvarint(uint64(w.rawReloc(relocString, w.p.stringIdx(frame))))
}
}
func (w *encoder) bool(b bool) bool {
w.sync(syncBool)
var x byte
if b {
x = 1
}
err := w.data.WriteByte(x)
w.checkErr(err)
return b
}
func (w *encoder) int64(x int64) {
w.sync(syncInt64)
w.rawVarint(x)
}
func (w *encoder) uint64(x uint64) {
w.sync(syncUint64)
w.rawUvarint(x)
}
func (w *encoder) len(x int) { assert(x >= 0); w.uint64(uint64(x)) }
func (w *encoder) int(x int) { w.int64(int64(x)) }
func (w *encoder) uint(x uint) { w.uint64(uint64(x)) }
func (w *encoder) reloc(r reloc, idx int) {
w.sync(syncUseReloc)
w.len(w.rawReloc(r, idx))
}
func (w *encoder) code(c code) {
w.sync(c.marker())
w.len(c.value())
}
func (w *encoder) string(s string) {
w.sync(syncString)
w.reloc(relocString, w.p.stringIdx(s))
}
func (w *encoder) strings(ss []string) {
w.len(len(ss))
for _, s := range ss {
w.string(s)
}
}
func (w *encoder) value(val constant.Value) {
w.sync(syncValue)
if w.bool(val.Kind() == constant.Complex) {
w.scalar(constant.Real(val))
w.scalar(constant.Imag(val))
} else {
w.scalar(val)
}
}
func (w *encoder) scalar(val constant.Value) {
switch v := constant.Val(val).(type) {
default:
panic(fmt.Sprintf("unhandled %v (%v)", val, val.Kind()))
case bool:
w.code(valBool)
w.bool(v)
case string:
w.code(valString)
w.string(v)
case int64:
w.code(valInt64)
w.int64(v)
case *big.Int:
w.code(valBigInt)
w.bigInt(v)
case *big.Rat:
w.code(valBigRat)
w.bigInt(v.Num())
w.bigInt(v.Denom())
case *big.Float:
w.code(valBigFloat)
w.bigFloat(v)
}
}
func (w *encoder) bigInt(v *big.Int) {
b := v.Bytes()
w.string(string(b)) // TODO: More efficient encoding.
w.bool(v.Sign() < 0)
}
func (w *encoder) bigFloat(v *big.Float) {
b := v.Append(nil, 'p', -1)
w.string(string(b)) // TODO: More efficient encoding.
}