CRITICAL fixes: - #1 vm.go: Mutex on libModules/dynamicFuncs global slices RegisterLibModule/RegisterDynamicFunc now thread-safe RegisterLibModules copies under lock, clears, releases - #4 shutdown.go: Signal handler goroutine leak fixed Uses done channel + select for clean exit on normal shutdown HIGH fixes: - #7-8 gobridge.go: Remove dead if/else branches (both identical) - #13-14 main.go: Remove hardcoded /mnt/d/harbour-core paths Use HB_INC env var + standard /usr/local/include/harbour only - #15 main.go: Remove unused frbModSeq variable MEDIUM fixes: - #22 expr.go: Remove unused parts variable in parseInterpolatedString - #51 macro.go: Remove var _ = fmt.Sprintf import guard - macroeval.go: Remove unused lexer import and guard Total fixed this session: 12/53 issues resolved Remaining: 41 (CRITICAL: 1, HIGH: 9, MEDIUM: 16, LOW: 16) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
178 lines
4.2 KiB
Go
178 lines
4.2 KiB
Go
// 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]
|
|
}
|
|
|
|
// 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
|
|
}
|