github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/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/wasilibs/wazerox/api" 14 "github.com/wasilibs/wazerox/internal/filecache" 15 "github.com/wasilibs/wazerox/internal/version" 16 "github.com/wasilibs/wazerox/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 }