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  }