From a110b93f5cd7bf624fcda839f12f5cc295a9eb4d Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Thu, 17 Jul 2025 13:48:56 -0500 Subject: [PATCH] introduce bytecode caching, update state management to use it --- state/bytecode.go | 93 +++++++++++++++++++++++++++++++++++++++++++++++ state/state.go | 16 +++++--- 2 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 state/bytecode.go diff --git a/state/bytecode.go b/state/bytecode.go new file mode 100644 index 0000000..325e955 --- /dev/null +++ b/state/bytecode.go @@ -0,0 +1,93 @@ +package state + +import ( + "crypto/sha256" + "fmt" + "sync" + + luajit "git.sharkk.net/Sky/LuaJIT-to-Go" +) + +// BytecodeEntry holds compiled bytecode with metadata +type BytecodeEntry struct { + Bytecode []byte + Name string + Hash [32]byte +} + +// Global bytecode cache +var ( + bytecodeCache = struct { + sync.RWMutex + entries map[string]*BytecodeEntry + }{ + entries: make(map[string]*BytecodeEntry), + } +) + +// CompileAndCache compiles code to bytecode and stores it globally +func CompileAndCache(state *luajit.State, code, name string) (*BytecodeEntry, error) { + hash := sha256.Sum256([]byte(code)) + cacheKey := fmt.Sprintf("%x", hash) + + // Check cache first + bytecodeCache.RLock() + if entry, exists := bytecodeCache.entries[cacheKey]; exists { + bytecodeCache.RUnlock() + return entry, nil + } + bytecodeCache.RUnlock() + + // Compile bytecode + bytecode, err := state.CompileBytecode(code, name) + if err != nil { + return nil, err + } + + // Store in cache + entry := &BytecodeEntry{ + Bytecode: bytecode, + Name: name, + Hash: hash, + } + + bytecodeCache.Lock() + bytecodeCache.entries[cacheKey] = entry + bytecodeCache.Unlock() + + return entry, nil +} + +// GetCached retrieves bytecode from cache by code hash +func GetCached(code string) (*BytecodeEntry, bool) { + hash := sha256.Sum256([]byte(code)) + cacheKey := fmt.Sprintf("%x", hash) + + bytecodeCache.RLock() + defer bytecodeCache.RUnlock() + + entry, exists := bytecodeCache.entries[cacheKey] + return entry, exists +} + +// ClearCache removes all cached bytecode entries +func ClearCache() { + bytecodeCache.Lock() + defer bytecodeCache.Unlock() + + bytecodeCache.entries = make(map[string]*BytecodeEntry) +} + +// CacheStats returns cache statistics +func CacheStats() (int, int64) { + bytecodeCache.RLock() + defer bytecodeCache.RUnlock() + + count := len(bytecodeCache.entries) + var totalSize int64 + for _, entry := range bytecodeCache.entries { + totalSize += int64(len(entry.Bytecode)) + } + + return count, totalSize +} diff --git a/state/state.go b/state/state.go index 3b237a2..fd0b4cd 100644 --- a/state/state.go +++ b/state/state.go @@ -159,15 +159,14 @@ func (s *State) ExecuteFile(scriptPath string) error { return s.ExecuteString(string(scriptContent), scriptPath) } -// ExecuteString compiles and runs Lua code +// ExecuteString compiles and runs Lua code with bytecode caching func (s *State) ExecuteString(code, name string) error { - // Use bytecode compilation for better performance - bytecode, err := s.CompileBytecode(code, name) + entry, err := CompileAndCache(s.State, code, name) if err != nil { return fmt.Errorf("compilation error in '%s': %w", name, err) } - if err := s.LoadAndRunBytecode(bytecode, name); err != nil { + if err := s.LoadAndRunBytecode(entry.Bytecode, name); err != nil { return fmt.Errorf("execution error in '%s': %w", name, err) } @@ -188,16 +187,21 @@ func (s *State) ExecuteFileWithResults(scriptPath string) ([]any, error) { return s.ExecuteStringWithResults(string(scriptContent), scriptPath) } -// ExecuteStringWithResults executes code and returns all results +// ExecuteStringWithResults executes code and returns all results with bytecode caching func (s *State) ExecuteStringWithResults(code, name string) ([]any, error) { baseTop := s.GetTop() defer s.SetTop(baseTop) - nresults, err := s.Execute(code) + entry, err := CompileAndCache(s.State, code, name) if err != nil { + return nil, fmt.Errorf("compilation error in '%s': %w", name, err) + } + + if err := s.LoadAndRunBytecodeWithResults(entry.Bytecode, name, -1); err != nil { return nil, fmt.Errorf("execution error in '%s': %w", name, err) } + nresults := s.GetTop() - baseTop if nresults == 0 { return nil, nil }