// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. package hbrt import "sync" // VM is the shared state across all threads. type VM struct { mu sync.RWMutex modules []*Module symbols map[string]*Symbol statics map[string][]Value threads []*Thread // all threads created (for shutdown cleanup) waFactory func() interface{} // creates WorkAreaManager for new threads onExit func() // called when Run() finishes (restore terminal etc.) Debugger *Debugger // nil = no debugging; set by five debug command } // SetWAFactory sets the factory for creating WorkAreaManagers. func (vm *VM) SetWAFactory(f func() interface{}) { vm.waFactory = f } // SetOnExit sets a callback for when Run() finishes. func (vm *VM) SetOnExit(f func()) { vm.onExit = f } // Library modules registered via init() — protected by mutex for FRB concurrent loading. var ( libModules []*Module dynamicFuncs []Symbol // from HB_FUNC() in #pragma BEGINDUMP libRegistryMu sync.Mutex ) // RegisterLibModule registers a module from a library PRG file. // Called by init() in generated library code. func RegisterLibModule(m *Module) { libRegistryMu.Lock() libModules = append(libModules, m) libRegistryMu.Unlock() } // RegisterDynamicFunc registers a Go function callable from PRG. // Called from init() in #pragma BEGINDUMP code via HB_FUNC(). func RegisterDynamicFunc(name string, fn func(*Thread)) { libRegistryMu.Lock() dynamicFuncs = append(dynamicFuncs, Symbol{ Name: name, Scope: FsPublic | FsLocal, Func: fn, }) libRegistryMu.Unlock() } // RegisterLibModules registers any pending lib modules and dynamic functions. func (vm *VM) RegisterLibModules() { libRegistryMu.Lock() mods := libModules libModules = nil dyns := dynamicFuncs dynamicFuncs = nil libRegistryMu.Unlock() for _, m := range mods { vm.RegisterModule(m) } for i := range dyns { sym := &dyns[i] vm.RegisterSymbol(sym) } dynamicFuncs = nil } // NewVM creates a new VM instance. func NewVM() *VM { return &VM{ modules: make([]*Module, 0), symbols: make(map[string]*Symbol), statics: make(map[string][]Value), } } // RegisterModule registers a module's symbols with the VM. func (vm *VM) RegisterModule(m *Module) { vm.mu.Lock() defer vm.mu.Unlock() vm.modules = append(vm.modules, m) for i := range m.Symbols { sym := &m.Symbols[i] vm.symbols[sym.Name] = sym } } // RegisterSymbol registers a single symbol. func (vm *VM) RegisterSymbol(sym *Symbol) { vm.mu.Lock() defer vm.mu.Unlock() vm.symbols[sym.Name] = sym } // UnregisterSymbol removes a symbol by name. Returns the old symbol if any. func (vm *VM) UnregisterSymbol(name string) *Symbol { vm.mu.Lock() defer vm.mu.Unlock() old := vm.symbols[name] delete(vm.symbols, name) return old } // SymbolNames returns all registered symbol names. func (vm *VM) SymbolNames() []string { vm.mu.RLock() defer vm.mu.RUnlock() names := make([]string, 0, len(vm.symbols)) for n := range vm.symbols { names = append(names, n) } return names } // FindSymbol looks up a symbol by name. func (vm *VM) FindSymbol(name string) *Symbol { vm.mu.RLock() defer vm.mu.RUnlock() return vm.symbols[name] } // GetSym returns the cached Symbol, performing a one-time FindSymbol // lookup on first access and stashing the pointer in *cache for all // subsequent calls. Generated code (gengo) declares a package-level // `var _sym_NAME *Symbol` per unique call target and routes every // PushSymbol through this helper so the hot path becomes a single // non-nil check instead of vm.symbols map + RWMutex per invocation. func (t *Thread) GetSym(cache **Symbol, name string) *Symbol { if s := *cache; s != nil { return s } s := t.vm.FindSymbol(name) if s != nil { // Only cache successful resolutions — nil might be due to // init-order (another module's registrations pending); // retry on next call once those complete. *cache = s } return s } // NewThread creates a new Thread attached to this VM. func (vm *VM) NewThread() *Thread { t := NewThread(vm) vm.mu.Lock() vm.threads = append(vm.threads, t) vm.mu.Unlock() return t } // Run starts execution from the named function. func (vm *VM) Run(funcName string) Value { // Register any library modules from init() for _, m := range libModules { vm.RegisterModule(m) } libModules = nil sym := vm.FindSymbol(funcName) if sym == nil { panic("function not found: " + funcName) } if sym.Func == nil { panic("function has no implementation: " + funcName) } t := vm.NewThread() // Auto-initialize WorkAreaManager if not set if t.WA == nil && vm.waFactory != nil { t.WA = vm.waFactory() } // Copy statics to thread vm.mu.RLock() for k, v := range vm.statics { t.statics[k] = v } vm.mu.RUnlock() // Install signal handlers for clean shutdown vm.InstallSignalHandlers() // Call the function, ensure full shutdown on exit defer vm.Shutdown() sym.Func(t) return t.retVal }