diff options
Diffstat (limited to 'internal/pkg/ir.go')
| -rw-r--r-- | internal/pkg/ir.go | 865 |
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 -} |
