diff --git a/cmd/mbf/main.go b/cmd/mbf/main.go index 1910a100..86a3aa1e 100644 --- a/cmd/mbf/main.go +++ b/cmd/mbf/main.go @@ -84,15 +84,15 @@ func main() { flagArch string flagCheck bool flagLTO bool - flagET bool + flagPT bool flagCrossOverride int addr net.UnixAddr ) c := command.New(os.Stderr, log.Printf, "mbf", func([]string) error { - if flagET { - log.Println("evaluated in", rosa.EvalTime()) + if flagPT { + log.Println("parsed in", rosa.ParseTime()) } msg.SwapVerbose(!flagQuiet) @@ -189,9 +189,9 @@ func main() { "socket", command.StringFlag("$MBF_DAEMON_SOCKET"), "Pathname of socket to bind to", ).Flag( - &flagET, - "eval-time", command.BoolFlag(false), - "Print duration of the initial azalea evaluation", + &flagPT, + "parse-time", command.BoolFlag(false), + "Print duration of the initial azalea parse", ) c.NewCommand( diff --git a/internal/rosa/rosa.go b/internal/rosa/rosa.go index dde85e00..209b8942 100644 --- a/internal/rosa/rosa.go +++ b/internal/rosa/rosa.go @@ -614,11 +614,11 @@ var native S // Native returns the global [S]. func Native() *S { return &native } -// evalTime is the duration of the initial built-in evaluation. -var evalTime time.Duration +// parseTime is the duration of early parsing of built-in azalea expressions. +var parseTime time.Duration -// EvalTime returns the duration of the initial built-in evaluation. -func EvalTime() time.Duration { return evalTime } +// ParseTime returns the time taken by early parsing of built-in azalea expressions. +func ParseTime() time.Duration { return parseTime } // nativeB is the backing directory of built-in azalea-based [Artifact] // implementations. @@ -632,9 +632,9 @@ func init() { panic(err) } t := time.Now() - if err = native.EvaluateFS(sub); err != nil { + if err = native.RegisterFS(sub); err != nil { println(err.Error()) os.Exit(1) } - evalTime = time.Since(t) + parseTime = time.Since(t) } diff --git a/internal/rosa/state.go b/internal/rosa/state.go index c2f0650b..7b858ed9 100644 --- a/internal/rosa/state.go +++ b/internal/rosa/state.go @@ -189,9 +189,11 @@ type S struct { archOnce sync.Once // Built-in functions. - s []azalea.Frame - // For initialising s. - sOnce sync.Once + frame azalea.Frame + // For initialising frame. + frameOnce sync.Once + // Must only be accessed after a call to getFrame. + spool sync.Pool // Options for [pkg.Artifact] created against [S]. opts int @@ -402,10 +404,15 @@ type deferredGit struct { checksum string } -// getS must be called before accessing s. This value is not currently safe for -// concurrent use, but the underlying frame is immutable. -func (s *S) getS() []azalea.Frame { - s.sOnce.Do(func() { +// getFrame must be called before accessing s. +func (s *S) getFrame() azalea.Frame { + s.frameOnce.Do(func() { + s.spool.New = func() any { + v := make([]azalea.Frame, 1, 1<<4) + v[0] = s.getFrame() + return v + } + s.wantsArch() k := func(name string) unique.Handle[azalea.Ident] { return unique.Make(azalea.Ident(name)) @@ -416,14 +423,13 @@ func (s *S) getS() []azalea.Frame { identArch = k(s.arch) ) - s.s = make([]azalea.Frame, 1, 1<<4) - s.s[0].Val = map[unique.Handle[azalea.Ident]]any{ + s.frame.Val = map[unique.Handle[azalea.Ident]]any{ k("jobsE"): jobsE, k("jobsFlagE"): jobsFlagE, k("jobsLE"): jobsLE, k("jobsLFlagE"): jobsLFlagE, } - s.s[0].Func = map[unique.Handle[azalea.Ident]]azalea.F{ + s.frame.Func = map[unique.Handle[azalea.Ident]]azalea.F{ // intenral/pkg built-ins @@ -551,9 +557,18 @@ func (s *S) getS() []azalea.Frame { }}, } }) - return s.s + return s.frame } +// getStack returns a new stack for azalea evaluation. +func (s *S) getStack() []azalea.Frame { + s.getFrame() + return s.spool.Get().([]azalea.Frame) +} + +// putStack returns a stack to spool. +func (s *S) putStack(v []azalea.Frame) { s.spool.Put(v) } + // toHandles makes handles out of an [azalea.Array] of identifiers. func toHandles(idents azalea.Array) (P, error) { handles := make(P, len(idents)) @@ -576,11 +591,18 @@ func toHandles(idents azalea.Array) (P, error) { return handles, nil } -// evalContext holds per-reader context. -type evalContext struct{ b fs.FS } +// evalContext holds per-artifact context. +type evalContext struct { + // Backing filesystem. + b fs.FS + // Pending azalea function. + expr *azalea.Func + // Current toolchain. + t Toolchain +} -// f implements [azalea.PF]. -func (ctx *evalContext) f( +// pf implements [azalea.PF]. +func (ctx *evalContext) pf( name azalea.Ident, args azalea.FArgs, ) (v any, set bool, err error) { @@ -649,31 +671,29 @@ func (ctx *evalContext) f( } meta.ID = int(anitya) - v = Artifact(func(t Toolchain) (*Metadata, pkg.Artifact) { - var source pkg.Artifact - switch p := sourceA.(type) { - case pkg.Artifact: - source = p + var source pkg.Artifact + switch p := sourceA.(type) { + case pkg.Artifact: + source = p - case deferredGit: - source = t.newTagRemote(p.url, p.tag, p.checksum) + case deferredGit: + source = ctx.t.newTagRemote(p.url, p.tag, p.checksum) - default: - panic(azalea.TypeError{ - Concrete: reflect.TypeOf(sourceA), - Asserted: reflect.TypeFor[pkg.Artifact](), - }) - } + default: + panic(azalea.TypeError{ + Concrete: reflect.TypeOf(sourceA), + Asserted: reflect.TypeFor[pkg.Artifact](), + }) + } - return &meta, t.NewPackage( - meta.Name, - meta.Version, - source, - &attr, - helper, - inputsH..., - ) - }) + v = cachedArtifact{&meta, ctx.t.NewPackage( + meta.Name, + meta.Version, + source, + &attr, + helper, + inputsH..., + )} set = true return } @@ -684,9 +704,9 @@ var ( ErrToplevel = errors.New("top level must only contain package declarations") ) -// Evaluate defines all package declarations from r. The backing filesystem is -// directly exposed to azalea pathnames. -func (s *S) Evaluate(r io.Reader, b fs.FS) error { +// RegisterAzalea registers all package declarations from r. The backing +// filesystem is directly exposed to azalea pathnames. +func (s *S) RegisterAzalea(r io.Reader, b fs.FS) error { var pending []*azalea.Func if expressions, err := azalea.Parse(r); err != nil { return err @@ -701,26 +721,31 @@ func (s *S) Evaluate(r io.Reader, b fs.FS) error { } } - ctx := evalContext{b} for _, f := range pending { - lf, set, err := azalea.Evaluate[Artifact](ctx.f, s.getS(), *f) - if err != nil { - return err - } else if !set { - return errors.New("unexpected unset") - } - if !s.Register(string(f.Ident), lf) { + if !s.Register(string(f.Ident), func(t Toolchain) (*Metadata, pkg.Artifact) { + v, set, err := azalea.Evaluate[cachedArtifact]( + (&evalContext{b, f, t}).pf, + s.getStack(), + *f, + ) + if err != nil { + panic(err) + } else if !set { + panic(errors.New("unexpected unset")) + } + return v.meta, v.a + }) { return RegisterError(H(string(f.Ident))) } } return nil } -// EvaluateFS evaluates azalea files discovered in fsys. A file is evaluated if -// it is at the top level and its name has the suffix ".az", or it is in a -// top-level directory with the exact file name "package.az". The backing -// filesystem is directly exposed to azalea pathnames. -func (s *S) EvaluateFS(fsys fs.FS) error { +// RegisterFS registers from azalea files discovered in fsys. A file is +// evaluated if it is at the top level and its name has the suffix ".az", or it +// is in a top-level directory with the exact file name "package.az". The +// backing filesystem is directly exposed to azalea pathnames. +func (s *S) RegisterFS(fsys fs.FS) error { dents, err := fs.ReadDir(fsys, ".") if err != nil { return err @@ -743,7 +768,7 @@ func (s *S) EvaluateFS(fsys fs.FS) error { return err } - err = s.Evaluate(r, sub) + err = s.RegisterAzalea(r, sub) if _err := r.Close(); err == nil { err = _err } @@ -763,7 +788,7 @@ func (s *S) EvaluateFS(fsys fs.FS) error { return err } - err = s.Evaluate(r, fsys) + err = s.RegisterAzalea(r, fsys) if _err := r.Close(); err == nil { err = _err }