aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/pkg/ir.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-08-29 18:58:06 +0900
committerOphestra <cat@gensokyo.uk>2026-08-29 18:58:06 +0900
commitc2d900ec74e03e9c782cfe7b7ce06ff62cda6601 (patch)
treefb13320bfdc62d810aa59769e8809dc2f707919e /internal/pkg/ir.go
parentbd4f29909e0750a4660eaf48e3169777a265b0ab (diff)
pkg: move from internalHEADstagingmasterdevelop
Closes #43. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/pkg/ir.go')
-rw-r--r--internal/pkg/ir.go865
1 files changed, 0 insertions, 865 deletions
diff --git a/internal/pkg/ir.go b/internal/pkg/ir.go
deleted file mode 100644
index d9f984a7..00000000
--- a/internal/pkg/ir.go
+++ /dev/null
@@ -1,865 +0,0 @@
-package pkg
-
-import (
- "bufio"
- "bytes"
- "crypto/sha512"
- "encoding/binary"
- "errors"
- "fmt"
- "io"
- "iter"
- "slices"
- "strconv"
- "sync"
- "syscall"
- "unique"
- "unsafe"
-)
-
-// wordSize is the boundary which binary segments are always aligned to.
-const wordSize = 8
-
-// alignSize returns the padded size for aligning sz.
-func alignSize[T int | uint64](sz T) T {
- return sz + (wordSize-(sz)%wordSize)%wordSize
-}
-
-// panicToError recovers from a panic and replaces a nil error with the panicked
-// error value. If the value does not implement error, it is re-panicked.
-func panicToError(errP *error) {
- r := recover()
- if r == nil {
- return
- }
-
- if err, ok := r.(error); !ok {
- panic(r)
- } else if *errP == nil {
- *errP = err
- }
-}
-
-// irCache implements [IRCache].
-type irCache struct {
- // Artifact to [unique.Handle] of identifier cache.
- artifact sync.Map
- // Identifier free list, must not be accessed directly.
- identPool sync.Pool
-}
-
-// zeroIRCache returns the initialised value of irCache.
-func zeroIRCache() irCache {
- return irCache{
- identPool: sync.Pool{New: func() any { return new(extIdent) }},
- }
-}
-
-// IRCache provides memory management and caching primitives for IR and
-// identifier operations against [Artifact] implementations.
-//
-// The zero value is not safe for use.
-type IRCache struct{ irCache }
-
-// NewIR returns the address of a new [IRCache].
-func NewIR() *IRCache {
- return &IRCache{zeroIRCache()}
-}
-
-// Inputs returns an iterator over direct and transitive inputs of an [Artifact]
-// in randomised order.
-func Inputs(a Artifact) iter.Seq2[Artifact, unique.Handle[ID]] {
- ic := NewIR()
- ic.Ident(a)
- return func(yield func(Artifact, unique.Handle[ID]) bool) {
- ic.artifact.Range(func(key, value any) bool {
- return yield(key.(Artifact), value.(unique.Handle[ID]))
- })
- }
-}
-
-// IContext is passed to [Artifact.Params] and provides methods for writing
-// values to the IR writer. It does not expose the underlying [io.Writer].
-//
-// IContext is valid until [Artifact.Params] returns.
-type IContext struct {
- // Address of underlying irCache, should be zeroed or made unusable after
- // [Artifact.Params] returns and must not be exposed directly.
- ic *irCache
- // Written to by various methods, should be zeroed after [Artifact.Params]
- // returns and must not be exposed directly.
- w io.Writer
- // Optional [Artifact] to cureRes cache, replaces [IRKindIdent] with
- // checksum values if non-nil. The pathname field may not be populated.
- inputs map[Artifact]cureRes
-}
-
-// irZero is a zero IR word.
-var irZero [wordSize]byte
-
-// IRValueKind denotes the kind of encoded value.
-type IRValueKind uint32
-
-const (
- // IRKindEnd denotes the end of the current parameters stream. The ancillary
- // value is interpreted as [IREndFlag].
- IRKindEnd IRValueKind = iota
- // IRKindIdent denotes the identifier of a dependency [Artifact]. The
- // ancillary value is reserved for future use.
- IRKindIdent
- // IRKindUint32 denotes an inlined uint32 value.
- IRKindUint32
- // IRKindString denotes a string with its true length encoded in header
- // ancillary data. Its wire length is always aligned to 8 byte boundary.
- IRKindString
-
- irHeaderShift = 32
- irHeaderMask = 0xffffffff
-)
-
-// String returns a user-facing name of k.
-func (k IRValueKind) String() string {
- switch k {
- case IRKindEnd:
- return "terminator"
- case IRKindIdent:
- return "ident"
- case IRKindUint32:
- return "uint32"
- case IRKindString:
- return "string"
- default:
- return "invalid kind " + strconv.Itoa(int(k))
- }
-}
-
-// irValueHeader encodes [IRValueKind] and a 32-bit ancillary value.
-type irValueHeader uint64
-
-// encodeHeader returns irValueHeader encoding [IRValueKind] and ancillary data.
-func (k IRValueKind) encodeHeader(v uint32) irValueHeader {
- return irValueHeader(v)<<irHeaderShift | irValueHeader(k)
-}
-
-// put stores h in b[0:8].
-func (h irValueHeader) put(b []byte) {
- binary.LittleEndian.PutUint64(b[:], uint64(h))
-}
-
-// append appends the bytes of h to b and returns the appended slice.
-func (h irValueHeader) append(b []byte) []byte {
- return binary.LittleEndian.AppendUint64(b, uint64(h))
-}
-
-// IREndFlag is ancillary data encoded in the header of an [IRKindEnd] value and
-// specifies the presence of optional fields in the remaining [IRKindEnd] data.
-// Order of present fields is the order of their corresponding constants defined
-// below.
-type IREndFlag uint32
-
-const (
- // IREndKnownChecksum denotes a [KnownChecksum] artifact. For an [IRKindEnd]
- // value with this flag set, the remaining data contains the [Checksum].
- IREndKnownChecksum IREndFlag = 1 << iota
-)
-
-// mustWrite writes to IContext.w and panics on error. The panic is recovered
-// from by the caller and used as the return value.
-func (i *IContext) mustWrite(p []byte) {
- if _, err := i.w.Write(p); err != nil {
- panic(err)
- }
-}
-
-// WriteIdent writes the identifier of [Artifact] to the IR. The behaviour of
-// WriteIdent is not defined for an [Artifact] not part of the slice returned by
-// [Artifact.Inputs].
-func (i *IContext) WriteIdent(a Artifact) {
- buf := i.ic.getIdentBuf()
- defer i.ic.putIdentBuf(buf)
-
- IRKindIdent.encodeHeader(0).put(buf[:])
- if i.inputs != nil {
- res, ok := i.inputs[a]
- if !ok {
- panic(InvalidLookupError(i.ic.Ident(a).Value()))
- }
- *(*ID)(buf[wordSize:]) = res.checksum.Value()
- } else {
- *(*ID)(buf[wordSize:]) = i.ic.Ident(a).Value()
- }
- i.mustWrite(buf[:])
-}
-
-// WriteUint32 writes a uint32 value to the IR.
-func (i *IContext) WriteUint32(v uint32) {
- i.mustWrite(IRKindUint32.encodeHeader(v).append(nil))
-}
-
-// irMaxStringLength is the maximum acceptable wire size of [IRKindString].
-const irMaxStringLength = 1 << 24
-
-// IRStringError is a string value too big to encode in IR.
-type IRStringError string
-
-func (IRStringError) Error() string {
- return "params value too big to encode in IR"
-}
-
-// Write writes p as a string value to the IR.
-func (i *IContext) Write(p []byte) {
- sz := alignSize(len(p))
- if len(p) > irMaxStringLength || sz > irMaxStringLength {
- panic(IRStringError(p))
- }
-
- i.mustWrite(IRKindString.encodeHeader(uint32(len(p))).append(nil))
- i.mustWrite(p)
-
- psz := sz - len(p)
- if psz > 0 {
- i.mustWrite(irZero[:psz])
- }
-}
-
-// WriteString writes s as a string value to the IR.
-func (i *IContext) WriteString(s string) {
- p := unsafe.Slice(unsafe.StringData(s), len(s))
- i.Write(p)
-}
-
-// Encode writes a deterministic, efficient representation of a to w and returns
-// the first non-nil error encountered while writing to w.
-func (ic *irCache) Encode(w io.Writer, a Artifact) (err error) {
- return ic.encode(w, a, nil)
-}
-
-// encode implements Encode but replaces identifiers with their cured checksums
-// for a non-nil ident. The pathname field is unused. Caller must acquire
-// Cache.identMu.
-func (ic *irCache) encode(
- w io.Writer,
- a Artifact,
- inputs map[Artifact]cureRes,
-) (err error) {
- deps := a.Inputs()
- idents := make([]*extIdent, len(deps))
- if inputs == nil {
- for i, d := range deps {
- dbuf, did := ic.unsafeIdent(d, true)
- if dbuf == nil {
- dbuf = ic.getIdentBuf()
- binary.LittleEndian.PutUint64(dbuf[:], uint64(d.Kind()))
- *(*ID)(dbuf[wordSize:]) = did.Value()
- } else {
- ic.storeIdent(d, dbuf)
- }
- defer ic.putIdentBuf(dbuf)
- idents[i] = dbuf
- }
- } else {
- for i, d := range deps {
- res, ok := inputs[d]
- if !ok {
- return InvalidLookupError(ic.Ident(d).Value())
- }
-
- dbuf := ic.getIdentBuf()
- binary.LittleEndian.PutUint64(dbuf[:], uint64(d.Kind()))
- *(*ID)(dbuf[wordSize:]) = res.checksum.Value()
- defer ic.putIdentBuf(dbuf)
- idents[i] = dbuf
- }
- }
- slices.SortFunc(idents, func(a, b *extIdent) int {
- return bytes.Compare(a[:], b[:])
- })
- idents = slices.CompactFunc(idents, func(a, b *extIdent) bool {
- return *a == *b
- })
-
- // kind uint64 | rev uint64 | deps_sz uint64
- var buf [wordSize * 3]byte
- binary.LittleEndian.PutUint64(buf[:], uint64(a.Kind()))
- binary.LittleEndian.PutUint64(buf[wordSize:], GetRevision(a))
- binary.LittleEndian.PutUint64(buf[wordSize*2:], uint64(len(idents)))
- if _, err = w.Write(buf[:]); err != nil {
- return
- }
-
- for _, dn := range idents {
- // kind uint64 | ident ID
- if _, err = w.Write(dn[:]); err != nil {
- return
- }
- }
-
- func() {
- i := IContext{ic, w, inputs}
-
- defer panicToError(&err)
- defer func() { i.ic, i.w = nil, nil }()
-
- a.Params(&i)
- }()
- if err != nil {
- return
- }
-
- var f IREndFlag
- kcBuf := ic.getIdentBuf()
- sz := wordSize
- if kc, ok := a.(KnownChecksum); ok {
- f |= IREndKnownChecksum
- *(*Checksum)(kcBuf[wordSize:]) = kc.Checksum()
- sz += len(Checksum{})
- }
- IRKindEnd.encodeHeader(uint32(f)).put(kcBuf[:])
-
- _, err = w.Write(kcBuf[:sz])
- ic.putIdentBuf(kcBuf)
- return
-}
-
-// encodeAll implements EncodeAll by recursively encoding dependencies and
-// performs deduplication by value via the encoded map.
-func (ic *irCache) encodeAll(
- w io.Writer,
- a Artifact,
- encoded map[Artifact]struct{},
-) (err error) {
- if _, ok := encoded[a]; ok {
- return
- }
-
- for _, d := range a.Inputs() {
- if err = ic.encodeAll(w, d, encoded); err != nil {
- return
- }
- }
-
- encoded[a] = struct{}{}
- return ic.Encode(w, a)
-}
-
-// EncodeAll writes a self-describing IR stream of a to w and returns the first
-// non-nil error encountered while writing to w.
-//
-// EncodeAll tries to avoid encoding the same [Artifact] more than once, however
-// it will fail to do so if they do not compare equal by value, as that will
-// require buffering and greatly reduce performance. It is therefore up to the
-// caller to avoid causing dependencies to be represented in a way such that
-// two equivalent artifacts do not compare equal. While an IR stream with
-// repeated artifacts is valid, it is somewhat inefficient, and the reference
-// [IRDecoder] implementation produces a warning for it.
-//
-// Note that while EncodeAll makes use of the ident free list, it does not use
-// the ident cache, nor does it contribute identifiers it computes back to the
-// ident cache. Because of this, multiple invocations of EncodeAll will have
-// similar cost and does not amortise when combined with a call to Cure.
-func (ic *irCache) EncodeAll(w io.Writer, a Artifact) error {
- return ic.encodeAll(w, a, make(map[Artifact]struct{}))
-}
-
-// ErrRemainingIR is returned for a [IRReadFunc] that failed to call
-// [IRReader.Finalise] before returning.
-var ErrRemainingIR = errors.New("implementation did not consume final value")
-
-// DanglingIdentError is an identifier in a [IRKindIdent] value that was never
-// described in the IR stream before it was encountered.
-type DanglingIdentError unique.Handle[ID]
-
-func (e DanglingIdentError) Error() string {
- return "artifact " + Encode(unique.Handle[ID](e).Value()) +
- " was never described"
-}
-
-type (
- // IRDecoder decodes [Artifact] from an IR stream. The stream is read to
- // EOF and the final [Artifact] is returned. Previous artifacts may be
- // looked up by their identifier.
- //
- // An [Artifact] may appear more than once in the same IR stream. A
- // repeating [Artifact] generates a warning via [Cache] and will appear if
- // verbose logging is enabled. Artifacts may only depend on artifacts
- // previously described in the IR stream.
- //
- // IRDecoder rejects an IR stream on the first decoding error, it does not
- // check against nonzero reserved ancillary data or incorrectly ordered or
- // redundant unstructured dependencies. An invalid IR stream as such will
- // yield [Artifact] values with identifiers disagreeing with those computed
- // by IRDecoder. For this reason, IRDecoder does not access the ident cache
- // to avoid putting [Cache] into an inconsistent state.
- //
- // Methods of IRDecoder are not safe for concurrent use.
- IRDecoder struct {
- // Address of underlying [Cache], must not be exposed directly.
- c *Cache
-
- // Underlying IR reader. Methods of [IRReader] must not use this as it
- // bypasses ident measurement.
- r io.Reader
- // Artifacts already seen in the IR stream.
- ident map[unique.Handle[ID]]Artifact
-
- // Whether Decode returned, and the entire IR stream was decoded.
- done, ok bool
- }
-
- // IRReader provides methods to decode the IR wire format and read values
- // from the reader embedded in the underlying [IRDecoder]. It is
- // deliberately impossible to obtain the [IRValueKind] of the next value,
- // and callers must never recover from panics in any read method.
- //
- // It is the responsibility of the caller to call Finalise after all IR
- // values have been read. Failure to call Finalise causes the resulting
- // [Artifact] to be rejected with [ErrRemainingIR].
- //
- // For an [Artifact] expected to have dependencies, the caller must consume
- // all dependencies by calling Next until all dependencies are depleted, or
- // call DiscardAll to explicitly discard them and rely on values encoded as
- // [IRKindIdent] instead. Failure to consume all unstructured dependencies
- // causes the resulting [Artifact] to be rejected with [MissedDependencyError].
- //
- // Requesting the value of an unstructured dependency not yet described in
- // the IR stream via Next, or reading an [IRKindIdent] value not part of
- // unstructured dependencies via ReadIdent may cause the resulting
- // [Artifact] to be rejected with [DanglingIdentError], however either
- // method may return a non-nil [Artifact] implementation of unspecified
- // value.
- IRReader struct {
- // Address of underlying [IRDecoder], should be zeroed or made unusable
- // after finalisation and must not be exposed directly.
- d *IRDecoder
- // Common buffer for word-sized reads.
- buf [wordSize]byte
-
- // Inputs sent before params, sorted by identifier. Resliced on
- // each call to Next and checked to be depleted during Finalise.
- inputs []*extIdent
-
- // Number of values already read, -1 denotes a finalised IRReader.
- count int
- // Header of value currently being read.
- h irValueHeader
-
- // Measured IR reader. All reads for the current [Artifact] must go
- // through this to produce a correct ident.
- r io.Reader
- // Buffers measure writes. Flushed and returned to d during Finalise.
- ibw *bufio.Writer
- }
-
- // IRReadFunc reads IR values written by [Artifact.Params] to produce an
- // instance of [Artifact] identical to the one to produce these values.
- IRReadFunc func(r *IRReader) Artifact
-)
-
-// kind returns the [IRValueKind] encoded in h.
-func (h irValueHeader) kind() IRValueKind {
- return IRValueKind(h & irHeaderMask)
-}
-
-// value returns ancillary data encoded in h.
-func (h irValueHeader) value() uint32 {
- return uint32(h >> irHeaderShift)
-}
-
-// irArtifact refers to artifact IR interpretation functions and must not be
-// written to directly.
-var irArtifact = make(map[Kind]IRReadFunc)
-
-// InvalidKindError is an unregistered [Kind] value.
-type InvalidKindError Kind
-
-func (e InvalidKindError) Error() string {
- return "invalid artifact kind " + strconv.Itoa(int(e))
-}
-
-// register records the [IRReadFunc] of an implementation of [Artifact] under
-// the specified [Kind]. Expecting to be used only during initialization, it
-// panics if the mapping between [Kind] and [IRReadFunc] is not a bijection.
-//
-// register is not safe for concurrent use. register must not be called after
-// the first instance of [Cache] has been opened.
-func register(k Kind, f IRReadFunc) {
- openMu.Lock()
- defer openMu.Unlock()
-
- if opened {
- panic("attempting to register after open")
- }
- if _, ok := irArtifact[k]; ok {
- panic("attempting to register " + strconv.Itoa(int(k)) + " twice")
- }
- irArtifact[k] = f
-}
-
-// Register records the [IRReadFunc] of a custom implementation of [Artifact]
-// under the specified [Kind]. Expecting to be used only during initialization,
-// it panics if the mapping between [Kind] and [IRReadFunc] is not a bijection,
-// or the specified [Kind] is below [KindCustomOffset].
-//
-// Register is not safe for concurrent use. Register must not be called after
-// the first instance of [Cache] has been opened.
-func Register(k Kind, f IRReadFunc) {
- if k < KindCustomOffset {
- panic("attempting to register within internal kind range")
- }
- register(k, f)
-}
-
-// NewDecoder returns a new [IRDecoder] that reads from the [io.Reader].
-func (c *Cache) NewDecoder(r io.Reader) *IRDecoder {
- return &IRDecoder{c, r, make(map[unique.Handle[ID]]Artifact), false, false}
-}
-
-const (
- // irMaxValues is the arbitrary maximum number of values allowed to be
- // written by [Artifact.Params] and subsequently read via [IRReader].
- irMaxValues = 1 << 12
-
- // irMaxDeps is the arbitrary maximum number of direct dependencies allowed
- // to be returned by [Artifact.Inputs] and subsequently decoded by
- // [IRDecoder].
- irMaxDeps = 1 << 10
-)
-
-var (
- // ErrIRValues is returned for an [Artifact] with too many parameter values.
- ErrIRValues = errors.New("artifact has too many IR parameter values")
-
- // ErrIRDepend is returned for an [Artifact] with too many dependencies.
- ErrIRDepend = errors.New("artifact has too many dependencies")
-
- // ErrAlreadyFinalised is returned when attempting to use an [IRReader] that
- // has already been finalised.
- ErrAlreadyFinalised = errors.New("reader has already finalised")
-)
-
-// enterReader panics with an appropriate error for an out-of-bounds count and
-// must be called at some point in any exported method.
-func (ir *IRReader) enterReader(read bool) {
- if ir.count < 0 {
- panic(ErrAlreadyFinalised)
- }
- if ir.count >= irMaxValues {
- panic(ErrIRValues)
- }
-
- if read {
- ir.count++
- }
-}
-
-// IRKindError describes an attempt to read an IR value of unexpected kind.
-type IRKindError struct {
- Got, Want IRValueKind
- Ancillary uint32
-}
-
-func (e *IRKindError) Error() string {
- return fmt.Sprintf(
- "got %s IR value (%#x) instead of %s",
- e.Got, e.Ancillary, e.Want,
- )
-}
-
-// readFull reads until either p is filled or an error is encountered.
-func (ir *IRReader) readFull(p []byte) (n int, err error) {
- for n < len(p) && err == nil {
- var nn int
- nn, err = ir.r.Read(p[n:])
- n += nn
- }
- return
-}
-
-// mustRead reads from the underlying measured reader and panics on error. If
-// an [io.EOF] is encountered and n != len(p), the error is promoted to a
-// [io.ErrUnexpectedEOF], if n == 0, [io.EOF] is kept as is, otherwise it is
-// zeroed.
-func (ir *IRReader) mustRead(p []byte) {
- n, err := ir.readFull(p)
- if err == nil {
- return
- }
-
- if errors.Is(err, io.EOF) {
- if n == len(p) {
- return
- }
- err = io.ErrUnexpectedEOF
- }
- panic(err)
-}
-
-// mustReadHeader reads the next header via d and checks its kind.
-func (ir *IRReader) mustReadHeader(k IRValueKind) {
- ir.mustRead(ir.buf[:])
- ir.h = irValueHeader(binary.LittleEndian.Uint64(ir.buf[:]))
- if wk := ir.h.kind(); wk != k {
- panic(&IRKindError{wk, k, ir.h.value()})
- }
-}
-
-// putAll returns all dependency buffers to the underlying [Cache].
-func (ir *IRReader) putAll() {
- for _, buf := range ir.inputs {
- ir.d.c.putIdentBuf(buf)
- }
- ir.inputs = nil
-}
-
-// DiscardAll discards all unstructured dependencies. This is useful to
-// implementations that encode dependencies as [IRKindIdent] which are read back
-// via ReadIdent.
-func (ir *IRReader) DiscardAll() {
- if ir.inputs == nil {
- panic("attempting to discard dependencies twice")
- }
- ir.putAll()
-}
-
-// ErrDependencyDepleted is returned when attempting to advance to the next
-// unstructured dependency when there are none left.
-var ErrDependencyDepleted = errors.New("reading past end of dependencies")
-
-// Next returns the next unstructured dependency.
-func (ir *IRReader) Next() Artifact {
- if len(ir.inputs) == 0 {
- panic(ErrDependencyDepleted)
- }
-
- id := unique.Make(ID(ir.inputs[0][wordSize:]))
- ir.d.c.putIdentBuf(ir.inputs[0])
- ir.inputs = ir.inputs[1:]
-
- if a, ok := ir.d.ident[id]; !ok {
- ir.putAll()
- panic(DanglingIdentError(id))
- } else {
- return a
- }
-}
-
-// MissedDependencyError is the number of unstructured dependencies remaining
-// in [IRReader] that was never requested or explicitly discarded before
-// finalisation.
-type MissedDependencyError int
-
-func (e MissedDependencyError) Error() string {
- return "missed " + strconv.Itoa(int(e)) + " unstructured dependencies"
-}
-
-var (
- // ErrUnexpectedChecksum is returned by a [IRReadFunc] that does not expect
- // a checksum but received one in [IRKindEnd] anyway.
- ErrUnexpectedChecksum = errors.New("checksum specified on unsupported artifact")
- // ErrExpectedChecksum is returned by a [IRReadFunc] that expects a checksum
- // but did not receive one in [IRKindEnd].
- ErrExpectedChecksum = errors.New("checksum required but not specified")
-)
-
-// Finalise reads the final [IRKindEnd] value and marks r as finalised. Methods
-// of r are invalid upon entry into Finalise. If a [Checksum] is available via
-// [IREndKnownChecksum], its handle is returned and the caller must store its
-// value in the resulting [Artifact].
-func (ir *IRReader) Finalise() (checksum unique.Handle[Checksum], ok bool) {
- ir.enterReader(true)
- ir.count = -1
-
- ir.mustReadHeader(IRKindEnd)
- f := IREndFlag(ir.h.value())
-
- if f&IREndKnownChecksum != 0 {
- buf := ir.d.c.getIdentBuf()
- defer ir.d.c.putIdentBuf(buf)
-
- ir.mustRead(buf[wordSize:])
- checksum = unique.Make(Checksum(buf[wordSize:]))
- ok = true
- }
-
- if err := ir.ibw.Flush(); err != nil {
- panic(err)
- }
- ir.r, ir.ibw = nil, nil
-
- if len(ir.inputs) != 0 {
- panic(MissedDependencyError(len(ir.inputs)))
- }
-
- return
-}
-
-// ReadIdent reads the next value as [IRKindIdent].
-func (ir *IRReader) ReadIdent() Artifact {
- ir.enterReader(true)
- ir.mustReadHeader(IRKindIdent)
-
- buf := ir.d.c.getIdentBuf()
- defer ir.d.c.putIdentBuf(buf)
-
- ir.mustRead(buf[wordSize:])
- id := unique.Make(ID(buf[wordSize:]))
-
- if a, ok := ir.d.ident[id]; !ok {
- panic(DanglingIdentError(id))
- } else {
- return a
- }
-}
-
-// ReadUint32 reads the next value as [IRKindUint32].
-func (ir *IRReader) ReadUint32() uint32 {
- ir.enterReader(true)
- ir.mustReadHeader(IRKindUint32)
- return ir.h.value()
-}
-
-// ReadStringBytes reads the next value as [IRKindString] but returns it as a
-// byte slice instead.
-func (ir *IRReader) ReadStringBytes() []byte {
- ir.enterReader(true)
- ir.mustReadHeader(IRKindString)
-
- sz := int(ir.h.value())
- szWire := alignSize(sz)
- if szWire > irMaxStringLength {
- panic(IRStringError("\x00"))
- }
-
- p := make([]byte, szWire)
- ir.mustRead(p)
- return p[:sz]
-}
-
-// ReadString reads the next value as [IRKindString].
-func (ir *IRReader) ReadString() string {
- p := ir.ReadStringBytes()
- return unsafe.String(unsafe.SliceData(p), len(p))
-}
-
-// A RevisionError describes the first [Artifact] in an IR stream claiming a
-// revision not supported by the registered implementation.
-type RevisionError [2]uint64
-
-func (e RevisionError) Error() string {
- return "claimed revision " + strconv.FormatUint(e[0], 10) +
- " differs from implementation value " + strconv.FormatUint(e[1], 10)
-}
-
-// decode decodes the next [Artifact] in the IR stream and returns any buffer
-// originating from [Cache] before returning. decode returns [io.EOF] if and
-// only if the underlying [io.Reader] is already read to EOF.
-func (d *IRDecoder) decode() (a Artifact, err error) {
- defer panicToError(&err)
- var ir IRReader
-
- defer func() { ir.d = nil }()
- ir.d = d
-
- h := sha512.New384()
- ir.ibw = d.c.getWriter(h)
- defer d.c.putWriter(ir.ibw)
- ir.r = io.TeeReader(d.r, ir.ibw)
-
- if n, _err := ir.readFull(ir.buf[:]); _err != nil {
- if errors.Is(_err, io.EOF) {
- if n != 0 {
- _err = io.ErrUnexpectedEOF
- }
- }
-
- err = _err
- return
- }
- ak := Kind(binary.LittleEndian.Uint64(ir.buf[:]))
- f, ok := irArtifact[ak]
- if !ok {
- err = InvalidKindError(ak)
- return
- }
-
- ir.mustRead(ir.buf[:])
- rev := binary.LittleEndian.Uint64(ir.buf[:])
-
- defer ir.putAll()
- ir.mustRead(ir.buf[:])
- sz := binary.LittleEndian.Uint64(ir.buf[:])
- if sz > irMaxDeps {
- err = ErrIRDepend
- return
- }
- ir.inputs = make([]*extIdent, sz)
- for i := range ir.inputs {
- ir.inputs[i] = d.c.getIdentBuf()
- }
- for _, buf := range ir.inputs {
- ir.mustRead(buf[:])
- }
-
- a = f(&ir)
- if a == nil {
- err = syscall.ENOTRECOVERABLE
- return
- }
- if _rev := GetRevision(a); _rev != rev {
- err = RevisionError{rev, _rev}
- return
- }
-
- if ir.count != -1 {
- err = ErrRemainingIR
- return
- }
-
- buf := d.c.getIdentBuf()
- h.Sum(buf[wordSize:wordSize])
- id := unique.Make(ID(buf[wordSize:]))
- d.c.putIdentBuf(buf)
- if _, ok = d.ident[id]; !ok {
- d.ident[id] = a
- } else {
- d.c.msg.Verbosef(
- "artifact %s%s%s appeared more than once in IR stream",
- d.c.sgrIdent, Encode(id.Value()), d.c.sgrRes,
- )
- }
-
- return
-}
-
-// Decode consumes the IR stream to EOF and returns the final [Artifact]. After
-// Decode returns, Lookup is available and Decode must not be called again.
-func (d *IRDecoder) Decode() (a Artifact, err error) {
- if d.done {
- panic("attempting to decode an IR stream twice")
- }
- defer func() { d.done = true }()
-
- var cur Artifact
-next:
- a, err = d.decode()
-
- if err == nil {
- cur = a
- goto next
- }
-
- if errors.Is(err, io.EOF) {
- a, err = cur, nil
- d.ok = true
- }
- return
-}
-
-// Lookup looks up an [Artifact] described by the IR stream by its identifier.
-func (d *IRDecoder) Lookup(id unique.Handle[ID]) (a Artifact, ok bool) {
- if !d.ok {
- panic("attempting to look up artifact without full IR stream")
- }
- a, ok = d.ident[id]
- return
-}