github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/runtime/runtime.go (about) 1 package runtime 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "runtime" 9 10 "github.com/arnodel/golua/runtime/internal/luagc" 11 ) 12 13 // A Runtime is a Lua runtime. It contains all the global state of the runtime 14 // (in particular a reference to the global environment and the main thread). 15 type Runtime struct { 16 globalEnv *Table // The global table 17 18 stringMeta *Table // Metatable for all string values 19 numberMeta *Table // Metatable for all numeric values 20 boolMeta *Table // Metatable for all boolan values 21 nilMeta *Table // Metatable for nil 22 23 Stdout io.Writer // This is useful for testing / repls 24 mainThread *Thread // An initialised Runtimes comes with this thread 25 gcThread *Thread // Thread for running Lua finalizers 26 registry *Table // The registry table can store data global to the runtime 27 28 warner Warner // Lua 5.4 introduces a warning system, implemented by this 29 30 // This has an almost empty implementation when the noquotas build tag is 31 // set. It should allow the compiler to compile away almost all runtime 32 // context manager methods. 33 runtimeContextManager 34 35 // Object pools used to minimise the overhead of Go memory management. 36 37 // Register pools, disabled with the noregpool build tag. 38 regPool valuePool 39 argsPool valuePool 40 cellPool cellPool 41 42 // Continuation pools, disable with the nocontpool build tag. 43 luaContPool luaContPool 44 goContPool goContPool 45 } 46 47 type runtimeOptions struct { 48 regPoolSize uint 49 regSetMaxAge uint 50 runtimeContextDef *RuntimeContextDef 51 } 52 53 var defaultRuntimeOptions = runtimeOptions{ 54 regPoolSize: 10, 55 regSetMaxAge: 10, 56 } 57 58 // A RuntimeOption configures the Runtime. 59 type RuntimeOption func(*runtimeOptions) 60 61 // WithRegPoolSize set the size of register pool when creating a new Runtime. 62 // The default register pool size is 10. 63 func WithRegPoolSize(sz uint) RuntimeOption { 64 return func(rtOpts *runtimeOptions) { 65 rtOpts.regPoolSize = sz 66 } 67 } 68 69 // WithRegSetMaxAge sets the max age of a register set when creating a new 70 // Runtime. The default max age is 10. 71 func WithRegSetMaxAge(age uint) RuntimeOption { 72 return func(rtOpts *runtimeOptions) { 73 rtOpts.regSetMaxAge = age 74 } 75 } 76 77 func WithRuntimeContext(def RuntimeContextDef) RuntimeOption { 78 return func(rtOpts *runtimeOptions) { 79 rtOpts.runtimeContextDef = &def 80 } 81 } 82 83 // New returns a new pointer to a Runtime with the given stdout. 84 func New(stdout io.Writer, opts ...RuntimeOption) *Runtime { 85 rtOpts := defaultRuntimeOptions 86 for _, opt := range opts { 87 opt(&rtOpts) 88 } 89 r := &Runtime{ 90 globalEnv: NewTable(), 91 Stdout: stdout, 92 registry: NewTable(), 93 warner: NewLogWarner(os.Stderr, "Lua warning: "), 94 regPool: mkValuePool(rtOpts.regPoolSize, rtOpts.regSetMaxAge), 95 argsPool: mkValuePool(rtOpts.regPoolSize, rtOpts.regSetMaxAge), 96 cellPool: mkCellPool(rtOpts.regPoolSize, rtOpts.regSetMaxAge), 97 } 98 99 mainThread := NewThread(r) 100 mainThread.status = ThreadOK 101 r.mainThread = mainThread 102 103 gcThread := NewThread(r) 104 gcThread.status = ThreadOK 105 r.gcThread = gcThread 106 107 r.runtimeContextManager.initRoot() 108 109 if rtOpts.runtimeContextDef != nil { 110 r.PushContext(*rtOpts.runtimeContextDef) 111 } 112 113 runtime.SetFinalizer(r, func(r *Runtime) { r.Close(nil) }) 114 return r 115 } 116 117 // GlobalEnv returns the global environment of the runtime. 118 func (r *Runtime) GlobalEnv() *Table { 119 return r.globalEnv 120 } 121 122 // Registry returns the Value associated with key in the runtime's registry. 123 func (r *Runtime) Registry(key Value) Value { 124 return r.registry.Get(key) 125 } 126 127 // SetRegistry sets the value associated with the key k to v in the registry. 128 func (r *Runtime) SetRegistry(k, v Value) { 129 r.SetTable(r.registry, k, v) 130 } 131 132 // MainThread returns the runtime's main thread. 133 func (r *Runtime) MainThread() *Thread { 134 return r.mainThread 135 } 136 137 // SetStringMeta sets the runtime's string metatable (all strings in a runtime 138 // have the same metatable). 139 func (r *Runtime) SetStringMeta(meta *Table) { 140 r.stringMeta = meta 141 } 142 143 // SetWarner replaces the current warner (Lua 5.4) 144 func (r *Runtime) SetWarner(warner Warner) { 145 r.warner = warner 146 } 147 148 // Warn emits a warning with the given message (Lua 5.4). The default warner is 149 // off to start with. It can be switch on / off by sending it a message "@on" / 150 // "@off". 151 func (r *Runtime) Warn(msgs ...string) { 152 if r.warner != nil { 153 r.warner.Warn(msgs...) 154 } 155 } 156 157 // RawMetatable returns the raw metatable for a value (that is, not looking at 158 // the metatable's '__metatable' key). 159 func (r *Runtime) RawMetatable(v Value) *Table { 160 if v.IsNil() { 161 return r.nilMeta 162 } 163 switch v.Type() { 164 case StringType: 165 return r.stringMeta 166 case FloatType, IntType: 167 return r.numberMeta 168 case BoolType: 169 return r.boolMeta 170 case TableType: 171 return v.AsTable().Metatable() 172 case UserDataType: 173 return v.AsUserData().Metatable() 174 default: 175 return nil 176 } 177 } 178 179 // SetRawMetatable sets the metatable for value v to meta. 180 func (r *Runtime) SetRawMetatable(v Value, meta *Table) { 181 if v.IsNil() { 182 r.nilMeta = meta 183 } 184 switch v.Type() { 185 case StringType: 186 r.stringMeta = meta 187 case FloatType, IntType: 188 r.numberMeta = meta 189 case BoolType: 190 r.boolMeta = meta 191 case TableType: 192 tbl := v.AsTable() 193 tbl.SetMetatable(meta) 194 if !RawGet(meta, MetaFieldGcValue).IsNil() { 195 r.addFinalizer(tbl, luagc.Finalize) 196 } 197 case UserDataType: 198 udata := v.AsUserData() 199 udata.SetMetatable(meta) 200 r.addFinalizer(udata, udata.MarkFlags()) 201 default: 202 // Should there be an error here? 203 } 204 } 205 206 func (r *Runtime) addFinalizer(ref luagc.Value, flags luagc.MarkFlags) { 207 if flags != 0 { 208 r.weakRefPool.Mark(ref, flags) 209 } 210 } 211 212 func (r *Runtime) runPendingFinalizers() { 213 214 // Running finalizers may panic if we run out of resources 215 pendingFinalize := r.weakRefPool.ExtractPendingFinalize() 216 if len(pendingFinalize) > 0 { 217 r.runFinalizers(pendingFinalize) 218 } 219 220 // If we get there, releasing resources should not panic 221 pendingRelease := r.weakRefPool.ExtractPendingRelease() 222 if len(pendingRelease) > 0 { 223 releaseResources(pendingRelease) 224 } 225 } 226 227 func (r *Runtime) runFinalizers(refs []luagc.Value) { 228 for _, ref := range refs { 229 term := NewTerminationWith(nil, 0, false) 230 v := AsValue(ref) 231 err, _ := Metacall(r.gcThread, v, MetaFieldGcString, []Value{v}, term) 232 if err != nil { 233 r.Warn(fmt.Sprintf("error in finalizer: %s", err)) 234 } 235 } 236 } 237 238 func (t *Thread) CollectGarbage() { 239 if t != t.gcThread { 240 runtime.GC() 241 t.runPendingFinalizers() 242 } 243 } 244 245 func (r *Runtime) Close(err *error) { 246 runtime.SetFinalizer(r, nil) 247 if r := recover(); r != nil { 248 ctErr, ok := r.(ContextTerminationError) 249 if !ok { 250 panic(r) 251 } 252 if err != nil && *err == nil { 253 *err = ctErr 254 } 255 } 256 defer func() { 257 if r.PopContext() != nil { 258 r.Close(err) 259 } else { 260 releaseResources(r.weakRefPool.ExtractAllMarkedRelease()) 261 } 262 if r := recover(); r != nil { 263 ctErr, ok := r.(ContextTerminationError) 264 if !ok { 265 panic(r) 266 } 267 if err != nil && *err == nil { 268 *err = ctErr 269 } 270 } 271 }() 272 r.runFinalizers(r.weakRefPool.ExtractAllMarkedFinalize()) 273 return 274 } 275 276 // Metatable returns the metatalbe of v (looking for '__metatable' in the raw 277 // metatable). 278 func (r *Runtime) Metatable(v Value) Value { 279 meta := r.RawMetatable(v) 280 if meta == nil { 281 return NilValue 282 } 283 metam := RawGet(meta, StringValue("__metatable")) 284 if metam != NilValue { 285 return metam 286 } 287 return TableValue(meta) 288 } 289 290 // Set a value in a table, requiring memory if needed, and always consuming >0 291 // CPU. 292 func (r *Runtime) SetTable(t *Table, k, v Value) { 293 r.RequireCPU(1) 294 r.RequireMem(t.Set(k, v)) 295 } 296 297 var errTableIndexIsNil = errors.New("table index is nil") 298 var errTableIndexIsNaN = errors.New("table index is NaN") 299 300 // SetTableCheck sets k => v in table T if possible, returning an error if k is 301 // nil or NaN. 302 func (r *Runtime) SetTableCheck(t *Table, k, v Value) error { 303 if k.IsNil() { 304 return errTableIndexIsNil 305 } 306 if k.IsNaN() { 307 return errTableIndexIsNaN 308 } 309 r.SetTable(t, k, v) 310 return nil 311 } 312 313 func (r *Runtime) metaGetS(v Value, k string) Value { 314 meta := r.RawMetatable(v) 315 return RawGet(meta, StringValue(k)) 316 }