github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/cache.go (about)

     1  package wazero
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"path"
     9  	"path/filepath"
    10  	goruntime "runtime"
    11  	"sync"
    12  
    13  	"github.com/bananabytelabs/wazero/api"
    14  	"github.com/bananabytelabs/wazero/internal/filecache"
    15  	"github.com/bananabytelabs/wazero/internal/version"
    16  	"github.com/bananabytelabs/wazero/internal/wasm"
    17  )
    18  
    19  // CompilationCache reduces time spent compiling (Runtime.CompileModule) the same wasm module.
    20  //
    21  // # Notes
    22  //
    23  //   - This is an interface for decoupling, not third-party implementations.
    24  //     All implementations are in wazero.
    25  //   - Instances of this can be reused across multiple runtimes, if configured
    26  //     via RuntimeConfig.
    27  type CompilationCache interface{ api.Closer }
    28  
    29  // NewCompilationCache returns a new CompilationCache to be passed to RuntimeConfig.
    30  // This configures only in-memory cache, and doesn't persist to the file system. See wazero.NewCompilationCacheWithDir for detail.
    31  //
    32  // The returned CompilationCache can be used to share the in-memory compilation results across multiple instances of wazero.Runtime.
    33  func NewCompilationCache() CompilationCache {
    34  	return &cache{}
    35  }
    36  
    37  // NewCompilationCacheWithDir is like wazero.NewCompilationCache except the result also writes
    38  // state into the directory specified by `dirname` parameter.
    39  //
    40  // If the dirname doesn't exist, this creates it or returns an error.
    41  //
    42  // Those running wazero as a CLI or frequently restarting a process using the same wasm should
    43  // use this feature to reduce time waiting to compile the same module a second time.
    44  //
    45  // The contents written into dirname are wazero-version specific, meaning different versions of
    46  // wazero will duplicate entries for the same input wasm.
    47  //
    48  // Note: The embedder must safeguard this directory from external changes.
    49  func NewCompilationCacheWithDir(dirname string) (CompilationCache, error) {
    50  	c := &cache{}
    51  	err := c.ensuresFileCache(dirname, version.GetWazeroVersion())
    52  	return c, err
    53  }
    54  
    55  // cache implements Cache interface.
    56  type cache struct {
    57  	// eng is the engine for this cache. If the cache is configured, the engine is shared across multiple instances of
    58  	// Runtime, and its lifetime is not bound to them. Instead, the engine is alive until Cache.Close is called.
    59  	engs      [engineKindCount]wasm.Engine
    60  	fileCache filecache.Cache
    61  	initOnces [engineKindCount]sync.Once
    62  }
    63  
    64  func (c *cache) initEngine(ek engineKind, ne newEngine, ctx context.Context, features api.CoreFeatures) wasm.Engine {
    65  	c.initOnces[ek].Do(func() { c.engs[ek] = ne(ctx, features, c.fileCache) })
    66  	return c.engs[ek]
    67  }
    68  
    69  // Close implements the same method on the Cache interface.
    70  func (c *cache) Close(_ context.Context) (err error) {
    71  	for _, eng := range c.engs {
    72  		if eng != nil {
    73  			if err = eng.Close(); err != nil {
    74  				return
    75  			}
    76  		}
    77  	}
    78  	return
    79  }
    80  
    81  func (c *cache) ensuresFileCache(dir string, wazeroVersion string) error {
    82  	// Resolve a potentially relative directory into an absolute one.
    83  	var err error
    84  	dir, err = filepath.Abs(dir)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	// Ensure the user-supplied directory.
    90  	if err = mkdir(dir); err != nil {
    91  		return err
    92  	}
    93  
    94  	// Create a version-specific directory to avoid conflicts.
    95  	dirname := path.Join(dir, "wazero-"+wazeroVersion+"-"+goruntime.GOARCH+"-"+goruntime.GOOS)
    96  	if err = mkdir(dirname); err != nil {
    97  		return err
    98  	}
    99  
   100  	c.fileCache = filecache.New(dirname)
   101  	return nil
   102  }
   103  
   104  func mkdir(dirname string) error {
   105  	if st, err := os.Stat(dirname); errors.Is(err, os.ErrNotExist) {
   106  		// If the directory not found, create the cache dir.
   107  		if err = os.MkdirAll(dirname, 0o700); err != nil {
   108  			return fmt.Errorf("create directory %s: %v", dirname, err)
   109  		}
   110  	} else if err != nil {
   111  		return err
   112  	} else if !st.IsDir() {
   113  		return fmt.Errorf("%s is not dir", dirname)
   114  	}
   115  	return nil
   116  }