// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. // RDD-related RTL functions: EOF(), BOF(), Found(), RecNo(), RecCount(), Deleted(). // Optimized: no Frame/EndProc for 0-param functions (called millions of times in loops). package hbrtl import ( "five/hbrt" "five/hbrdd" ) func rtlEOF(t *hbrt.Thread) { t.Frame(0, 0) defer t.EndProcFast() if wa := getWA(t); wa != nil { if area := wa.Current(); area != nil { t.PushBool(area.EOF()) t.RetValue() return } } t.PushBool(true) t.RetValue() } func rtlBOF(t *hbrt.Thread) { t.Frame(0, 0) defer t.EndProcFast() if wa := getWA(t); wa != nil { if area := wa.Current(); area != nil { t.PushBool(area.BOF()) t.RetValue() return } } t.PushBool(true) t.RetValue() } func rtlFound(t *hbrt.Thread) { t.Frame(0, 0) defer t.EndProcFast() if wa := getWA(t); wa != nil { if area := wa.Current(); area != nil { t.PushBool(area.Found()) t.RetValue() return } } t.PushBool(false) t.RetValue() } func rtlRecNo(t *hbrt.Thread) { t.Frame(0, 0) defer t.EndProcFast() if wa := getWA(t); wa != nil { if area := wa.Current(); area != nil { t.RetInt(int64(area.RecNo())) return } } t.RetInt(0) } func rtlRecCount(t *hbrt.Thread) { t.Frame(0, 0) defer t.EndProcFast() if wa := getWA(t); wa != nil { if area := wa.Current(); area != nil { rc, _ := area.RecCount() t.RetInt(int64(rc)) return } } t.RetInt(0) } func rtlDeleted(t *hbrt.Thread) { t.Frame(0, 0) defer t.EndProcFast() if wa := getWA(t); wa != nil { if area := wa.Current(); area != nil { t.PushBool(area.Deleted()) t.RetValue() return } } t.PushBool(false) t.RetValue() } func rtlFieldGet(t *hbrt.Thread) { t.Frame(1, 0) defer t.EndProcFast() n := int(t.Local(1).AsNumInt()) if wa := getWA(t); wa != nil { if area := wa.Current(); area != nil { val, err := area.GetValue(n - 1) // 1-based → 0-based if err == nil { t.PushValue(val) t.RetValue() return } } } t.PushNil() t.RetValue() } // getWA resolves the WorkAreaManager attached to this thread. // // The previous version cached the last-seen interface→*WAM pair in // a process-global struct to skip the type assertion. That cache // was the worst-of-both-worlds under multi-pgserver-connection // load: each connection's thread has its own WAM, so the cache // missed on every call and immediately re-wrote two shared // `interface{}` fields. Go's interface is a two-word value, so a // concurrent write+read produced torn pointers — different // goroutines saw the WRONG WAM as their own, leading to the // FieldPosCache + WAM.aliases "concurrent map writes" panics. // // The type assertion itself is fast (~ns). Drop the cache; if the // micro-bench matters again, replace with a sync/atomic.Pointer // or sync.Map keyed by thread, not a single global slot. func getWA(t *hbrt.Thread) *hbrdd.WorkAreaManager { if t.WA == nil { return nil } wa, _ := t.WA.(*hbrdd.WorkAreaManager) return wa }