forked from rosa/hakurei
internal/pkg: move IR primitives out of cache
These are memory management and caching primitives. Having them as part of Cache is cumbersome and requires a temporary directory that is never used. This change isolates them from Cache to enable independent use. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -3,7 +3,6 @@ package pkg
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -11,6 +10,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unique"
|
"unique"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -39,22 +39,45 @@ func panicToError(errP *error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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()}
|
||||||
|
}
|
||||||
|
|
||||||
// IContext is passed to [Artifact.Params] and provides methods for writing
|
// IContext is passed to [Artifact.Params] and provides methods for writing
|
||||||
// values to the IR writer. It does not expose the underlying [io.Writer].
|
// values to the IR writer. It does not expose the underlying [io.Writer].
|
||||||
//
|
//
|
||||||
// IContext is valid until [Artifact.Params] returns.
|
// IContext is valid until [Artifact.Params] returns.
|
||||||
type IContext struct {
|
type IContext struct {
|
||||||
// Address of underlying [Cache], should be zeroed or made unusable after
|
// Address of underlying irCache, should be zeroed or made unusable after
|
||||||
// [Artifact.Params] returns and must not be exposed directly.
|
// [Artifact.Params] returns and must not be exposed directly.
|
||||||
cache *Cache
|
ic *irCache
|
||||||
// Written to by various methods, should be zeroed after [Artifact.Params]
|
// Written to by various methods, should be zeroed after [Artifact.Params]
|
||||||
// returns and must not be exposed directly.
|
// returns and must not be exposed directly.
|
||||||
w io.Writer
|
w io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap returns the underlying [context.Context].
|
|
||||||
func (i *IContext) Unwrap() context.Context { return i.cache.ctx }
|
|
||||||
|
|
||||||
// irZero is a zero IR word.
|
// irZero is a zero IR word.
|
||||||
var irZero [wordSize]byte
|
var irZero [wordSize]byte
|
||||||
|
|
||||||
@@ -136,11 +159,11 @@ func (i *IContext) mustWrite(p []byte) {
|
|||||||
// WriteIdent is not defined for an [Artifact] not part of the slice returned by
|
// WriteIdent is not defined for an [Artifact] not part of the slice returned by
|
||||||
// [Artifact.Dependencies].
|
// [Artifact.Dependencies].
|
||||||
func (i *IContext) WriteIdent(a Artifact) {
|
func (i *IContext) WriteIdent(a Artifact) {
|
||||||
buf := i.cache.getIdentBuf()
|
buf := i.ic.getIdentBuf()
|
||||||
defer i.cache.putIdentBuf(buf)
|
defer i.ic.putIdentBuf(buf)
|
||||||
|
|
||||||
IRKindIdent.encodeHeader(0).put(buf[:])
|
IRKindIdent.encodeHeader(0).put(buf[:])
|
||||||
*(*ID)(buf[wordSize:]) = i.cache.Ident(a).Value()
|
*(*ID)(buf[wordSize:]) = i.ic.Ident(a).Value()
|
||||||
i.mustWrite(buf[:])
|
i.mustWrite(buf[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,19 +206,19 @@ func (i *IContext) WriteString(s string) {
|
|||||||
|
|
||||||
// Encode writes a deterministic, efficient representation of a to w and returns
|
// Encode writes a deterministic, efficient representation of a to w and returns
|
||||||
// the first non-nil error encountered while writing to w.
|
// the first non-nil error encountered while writing to w.
|
||||||
func (c *Cache) Encode(w io.Writer, a Artifact) (err error) {
|
func (ic *irCache) Encode(w io.Writer, a Artifact) (err error) {
|
||||||
deps := a.Dependencies()
|
deps := a.Dependencies()
|
||||||
idents := make([]*extIdent, len(deps))
|
idents := make([]*extIdent, len(deps))
|
||||||
for i, d := range deps {
|
for i, d := range deps {
|
||||||
dbuf, did := c.unsafeIdent(d, true)
|
dbuf, did := ic.unsafeIdent(d, true)
|
||||||
if dbuf == nil {
|
if dbuf == nil {
|
||||||
dbuf = c.getIdentBuf()
|
dbuf = ic.getIdentBuf()
|
||||||
binary.LittleEndian.PutUint64(dbuf[:], uint64(d.Kind()))
|
binary.LittleEndian.PutUint64(dbuf[:], uint64(d.Kind()))
|
||||||
*(*ID)(dbuf[wordSize:]) = did.Value()
|
*(*ID)(dbuf[wordSize:]) = did.Value()
|
||||||
} else {
|
} else {
|
||||||
c.storeIdent(d, dbuf)
|
ic.storeIdent(d, dbuf)
|
||||||
}
|
}
|
||||||
defer c.putIdentBuf(dbuf)
|
defer ic.putIdentBuf(dbuf)
|
||||||
idents[i] = dbuf
|
idents[i] = dbuf
|
||||||
}
|
}
|
||||||
slices.SortFunc(idents, func(a, b *extIdent) int {
|
slices.SortFunc(idents, func(a, b *extIdent) int {
|
||||||
@@ -221,10 +244,10 @@ func (c *Cache) Encode(w io.Writer, a Artifact) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
i := IContext{c, w}
|
i := IContext{ic, w}
|
||||||
|
|
||||||
defer panicToError(&err)
|
defer panicToError(&err)
|
||||||
defer func() { i.cache, i.w = nil, nil }()
|
defer func() { i.ic, i.w = nil, nil }()
|
||||||
|
|
||||||
a.Params(&i)
|
a.Params(&i)
|
||||||
}()
|
}()
|
||||||
@@ -233,7 +256,7 @@ func (c *Cache) Encode(w io.Writer, a Artifact) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var f IREndFlag
|
var f IREndFlag
|
||||||
kcBuf := c.getIdentBuf()
|
kcBuf := ic.getIdentBuf()
|
||||||
sz := wordSize
|
sz := wordSize
|
||||||
if kc, ok := a.(KnownChecksum); ok {
|
if kc, ok := a.(KnownChecksum); ok {
|
||||||
f |= IREndKnownChecksum
|
f |= IREndKnownChecksum
|
||||||
@@ -243,13 +266,13 @@ func (c *Cache) Encode(w io.Writer, a Artifact) (err error) {
|
|||||||
IRKindEnd.encodeHeader(uint32(f)).put(kcBuf[:])
|
IRKindEnd.encodeHeader(uint32(f)).put(kcBuf[:])
|
||||||
|
|
||||||
_, err = w.Write(kcBuf[:sz])
|
_, err = w.Write(kcBuf[:sz])
|
||||||
c.putIdentBuf(kcBuf)
|
ic.putIdentBuf(kcBuf)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeAll implements EncodeAll by recursively encoding dependencies and
|
// encodeAll implements EncodeAll by recursively encoding dependencies and
|
||||||
// performs deduplication by value via the encoded map.
|
// performs deduplication by value via the encoded map.
|
||||||
func (c *Cache) encodeAll(
|
func (ic *irCache) encodeAll(
|
||||||
w io.Writer,
|
w io.Writer,
|
||||||
a Artifact,
|
a Artifact,
|
||||||
encoded map[Artifact]struct{},
|
encoded map[Artifact]struct{},
|
||||||
@@ -259,13 +282,13 @@ func (c *Cache) encodeAll(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range a.Dependencies() {
|
for _, d := range a.Dependencies() {
|
||||||
if err = c.encodeAll(w, d, encoded); err != nil {
|
if err = ic.encodeAll(w, d, encoded); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
encoded[a] = struct{}{}
|
encoded[a] = struct{}{}
|
||||||
return c.Encode(w, a)
|
return ic.Encode(w, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeAll writes a self-describing IR stream of a to w and returns the first
|
// EncodeAll writes a self-describing IR stream of a to w and returns the first
|
||||||
@@ -283,8 +306,8 @@ func (c *Cache) encodeAll(
|
|||||||
// the ident cache, nor does it contribute identifiers it computes back to the
|
// the ident cache, nor does it contribute identifiers it computes back to the
|
||||||
// ident cache. Because of this, multiple invocations of EncodeAll will have
|
// ident cache. Because of this, multiple invocations of EncodeAll will have
|
||||||
// similar cost and does not amortise when combined with a call to Cure.
|
// similar cost and does not amortise when combined with a call to Cure.
|
||||||
func (c *Cache) EncodeAll(w io.Writer, a Artifact) error {
|
func (ic *irCache) EncodeAll(w io.Writer, a Artifact) error {
|
||||||
return c.encodeAll(w, a, make(map[Artifact]struct{}))
|
return ic.encodeAll(w, a, make(map[Artifact]struct{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrRemainingIR is returned for a [IRReadFunc] that failed to call
|
// ErrRemainingIR is returned for a [IRReadFunc] that failed to call
|
||||||
|
|||||||
@@ -559,10 +559,8 @@ type Cache struct {
|
|||||||
// Immutable job count, when applicable.
|
// Immutable job count, when applicable.
|
||||||
jobs int
|
jobs int
|
||||||
|
|
||||||
// Artifact to [unique.Handle] of identifier cache.
|
// Must not be exposed directly.
|
||||||
artifact sync.Map
|
irCache
|
||||||
// Identifier free list, must not be accessed directly.
|
|
||||||
identPool sync.Pool
|
|
||||||
|
|
||||||
// Synchronises access to dirChecksum.
|
// Synchronises access to dirChecksum.
|
||||||
checksumMu sync.RWMutex
|
checksumMu sync.RWMutex
|
||||||
@@ -594,24 +592,24 @@ type Cache struct {
|
|||||||
type extIdent [wordSize + len(ID{})]byte
|
type extIdent [wordSize + len(ID{})]byte
|
||||||
|
|
||||||
// getIdentBuf returns the address of an extIdent for Ident.
|
// getIdentBuf returns the address of an extIdent for Ident.
|
||||||
func (c *Cache) getIdentBuf() *extIdent { return c.identPool.Get().(*extIdent) }
|
func (ic *irCache) getIdentBuf() *extIdent { return ic.identPool.Get().(*extIdent) }
|
||||||
|
|
||||||
// putIdentBuf adds buf to identPool.
|
// putIdentBuf adds buf to identPool.
|
||||||
func (c *Cache) putIdentBuf(buf *extIdent) { c.identPool.Put(buf) }
|
func (ic *irCache) putIdentBuf(buf *extIdent) { ic.identPool.Put(buf) }
|
||||||
|
|
||||||
// storeIdent adds an [Artifact] to the artifact cache.
|
// storeIdent adds an [Artifact] to the artifact cache.
|
||||||
func (c *Cache) storeIdent(a Artifact, buf *extIdent) unique.Handle[ID] {
|
func (ic *irCache) storeIdent(a Artifact, buf *extIdent) unique.Handle[ID] {
|
||||||
idu := unique.Make(ID(buf[wordSize:]))
|
idu := unique.Make(ID(buf[wordSize:]))
|
||||||
c.artifact.Store(a, idu)
|
ic.artifact.Store(a, idu)
|
||||||
return idu
|
return idu
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ident returns the identifier of an [Artifact].
|
// Ident returns the identifier of an [Artifact].
|
||||||
func (c *Cache) Ident(a Artifact) unique.Handle[ID] {
|
func (ic *irCache) Ident(a Artifact) unique.Handle[ID] {
|
||||||
buf, idu := c.unsafeIdent(a, false)
|
buf, idu := ic.unsafeIdent(a, false)
|
||||||
if buf != nil {
|
if buf != nil {
|
||||||
idu = c.storeIdent(a, buf)
|
idu = ic.storeIdent(a, buf)
|
||||||
c.putIdentBuf(buf)
|
ic.putIdentBuf(buf)
|
||||||
}
|
}
|
||||||
return idu
|
return idu
|
||||||
}
|
}
|
||||||
@@ -619,17 +617,17 @@ func (c *Cache) Ident(a Artifact) unique.Handle[ID] {
|
|||||||
// unsafeIdent implements Ident but returns the underlying buffer for a newly
|
// unsafeIdent implements Ident but returns the underlying buffer for a newly
|
||||||
// computed identifier. Callers must return this buffer to identPool. encodeKind
|
// computed identifier. Callers must return this buffer to identPool. encodeKind
|
||||||
// is only a hint, kind may still be encoded in the buffer.
|
// is only a hint, kind may still be encoded in the buffer.
|
||||||
func (c *Cache) unsafeIdent(a Artifact, encodeKind bool) (
|
func (ic *irCache) unsafeIdent(a Artifact, encodeKind bool) (
|
||||||
buf *extIdent,
|
buf *extIdent,
|
||||||
idu unique.Handle[ID],
|
idu unique.Handle[ID],
|
||||||
) {
|
) {
|
||||||
if id, ok := c.artifact.Load(a); ok {
|
if id, ok := ic.artifact.Load(a); ok {
|
||||||
idu = id.(unique.Handle[ID])
|
idu = id.(unique.Handle[ID])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ki, ok := a.(KnownIdent); ok {
|
if ki, ok := a.(KnownIdent); ok {
|
||||||
buf = c.getIdentBuf()
|
buf = ic.getIdentBuf()
|
||||||
if encodeKind {
|
if encodeKind {
|
||||||
binary.LittleEndian.PutUint64(buf[:], uint64(a.Kind()))
|
binary.LittleEndian.PutUint64(buf[:], uint64(a.Kind()))
|
||||||
}
|
}
|
||||||
@@ -637,9 +635,9 @@ func (c *Cache) unsafeIdent(a Artifact, encodeKind bool) (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
buf = c.getIdentBuf()
|
buf = ic.getIdentBuf()
|
||||||
h := sha512.New384()
|
h := sha512.New384()
|
||||||
if err := c.Encode(h, a); err != nil {
|
if err := ic.Encode(h, a); err != nil {
|
||||||
// unreachable
|
// unreachable
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -1876,7 +1874,7 @@ func open(
|
|||||||
msg: msg,
|
msg: msg,
|
||||||
base: base,
|
base: base,
|
||||||
|
|
||||||
identPool: sync.Pool{New: func() any { return new(extIdent) }},
|
irCache: zeroIRCache(),
|
||||||
|
|
||||||
ident: make(map[unique.Handle[ID]]unique.Handle[Checksum]),
|
ident: make(map[unique.Handle[ID]]unique.Handle[Checksum]),
|
||||||
identErr: make(map[unique.Handle[ID]]error),
|
identErr: make(map[unique.Handle[ID]]error),
|
||||||
|
|||||||
Reference in New Issue
Block a user