237 lines
7.9 KiB
C
237 lines
7.9 KiB
C
#ifndef wren_vm_h
|
|
#define wren_vm_h
|
|
|
|
#include "wren_common.h"
|
|
#include "wren_compiler.h"
|
|
#include "wren_value.h"
|
|
#include "wren_utils.h"
|
|
|
|
// The maximum number of temporary objects that can be made visible to the GC
|
|
// at one time.
|
|
#define WREN_MAX_TEMP_ROOTS 5
|
|
|
|
typedef enum
|
|
{
|
|
#define OPCODE(name, _) CODE_##name,
|
|
#include "wren_opcodes.h"
|
|
#undef OPCODE
|
|
} Code;
|
|
|
|
// A handle to a value, basically just a linked list of extra GC roots.
|
|
//
|
|
// Note that even non-heap-allocated values can be stored here.
|
|
struct WrenHandle
|
|
{
|
|
Value value;
|
|
|
|
WrenHandle* prev;
|
|
WrenHandle* next;
|
|
};
|
|
|
|
struct WrenVM
|
|
{
|
|
ObjClass* boolClass;
|
|
ObjClass* classClass;
|
|
ObjClass* fiberClass;
|
|
ObjClass* fnClass;
|
|
ObjClass* listClass;
|
|
ObjClass* mapClass;
|
|
ObjClass* nullClass;
|
|
ObjClass* numClass;
|
|
ObjClass* objectClass;
|
|
ObjClass* rangeClass;
|
|
ObjClass* stringClass;
|
|
|
|
// The fiber that is currently running.
|
|
ObjFiber* fiber;
|
|
|
|
// The loaded modules. Each key is an ObjString (except for the main module,
|
|
// whose key is null) for the module's name and the value is the ObjModule
|
|
// for the module.
|
|
ObjMap* modules;
|
|
|
|
// The most recently imported module. More specifically, the module whose
|
|
// code has most recently finished executing.
|
|
//
|
|
// Not treated like a GC root since the module is already in [modules].
|
|
ObjModule* lastModule;
|
|
|
|
// Memory management data:
|
|
|
|
// The number of bytes that are known to be currently allocated. Includes all
|
|
// memory that was proven live after the last GC, as well as any new bytes
|
|
// that were allocated since then. Does *not* include bytes for objects that
|
|
// were freed since the last GC.
|
|
size_t bytesAllocated;
|
|
|
|
// The number of total allocated bytes that will trigger the next GC.
|
|
size_t nextGC;
|
|
|
|
// The first object in the linked list of all currently allocated objects.
|
|
Obj* first;
|
|
|
|
// The "gray" set for the garbage collector. This is the stack of unprocessed
|
|
// objects while a garbage collection pass is in process.
|
|
Obj** gray;
|
|
int grayCount;
|
|
int grayCapacity;
|
|
|
|
// The list of temporary roots. This is for temporary or new objects that are
|
|
// not otherwise reachable but should not be collected.
|
|
//
|
|
// They are organized as a stack of pointers stored in this array. This
|
|
// implies that temporary roots need to have stack semantics: only the most
|
|
// recently pushed object can be released.
|
|
Obj* tempRoots[WREN_MAX_TEMP_ROOTS];
|
|
|
|
int numTempRoots;
|
|
|
|
// Pointer to the first node in the linked list of active handles or NULL if
|
|
// there are none.
|
|
WrenHandle* handles;
|
|
|
|
// Pointer to the bottom of the range of stack slots available for use from
|
|
// the C API. During a foreign method, this will be in the stack of the fiber
|
|
// that is executing a method.
|
|
//
|
|
// If not in a foreign method, this is initially NULL. If the user requests
|
|
// slots by calling wrenEnsureSlots(), a stack is created and this is
|
|
// initialized.
|
|
Value* apiStack;
|
|
|
|
WrenConfiguration config;
|
|
|
|
// Compiler and debugger data:
|
|
|
|
// The compiler that is currently compiling code. This is used so that heap
|
|
// allocated objects used by the compiler can be found if a GC is kicked off
|
|
// in the middle of a compile.
|
|
Compiler* compiler;
|
|
|
|
// There is a single global symbol table for all method names on all classes.
|
|
// Method calls are dispatched directly by index in this table.
|
|
SymbolTable methodNames;
|
|
};
|
|
|
|
// A generic allocation function that handles all explicit memory management.
|
|
// It's used like so:
|
|
//
|
|
// - To allocate new memory, [memory] is NULL and [oldSize] is zero. It should
|
|
// return the allocated memory or NULL on failure.
|
|
//
|
|
// - To attempt to grow an existing allocation, [memory] is the memory,
|
|
// [oldSize] is its previous size, and [newSize] is the desired size.
|
|
// It should return [memory] if it was able to grow it in place, or a new
|
|
// pointer if it had to move it.
|
|
//
|
|
// - To shrink memory, [memory], [oldSize], and [newSize] are the same as above
|
|
// but it will always return [memory].
|
|
//
|
|
// - To free memory, [memory] will be the memory to free and [newSize] and
|
|
// [oldSize] will be zero. It should return NULL.
|
|
void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize);
|
|
|
|
// Invoke the finalizer for the foreign object referenced by [foreign].
|
|
void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign);
|
|
|
|
// Creates a new [WrenHandle] for [value].
|
|
WrenHandle* wrenMakeHandle(WrenVM* vm, Value value);
|
|
|
|
// Compile [source] in the context of [module] and wrap in a fiber that can
|
|
// execute it.
|
|
//
|
|
// Returns NULL if a compile error occurred.
|
|
ObjClosure* wrenCompileSource(WrenVM* vm, const char* module,
|
|
const char* source, bool isExpression,
|
|
bool printErrors);
|
|
|
|
// Looks up a variable from a previously-loaded module.
|
|
//
|
|
// Aborts the current fiber if the module or variable could not be found.
|
|
Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName);
|
|
|
|
// Returns the value of the module-level variable named [name] in the main
|
|
// module.
|
|
Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name);
|
|
|
|
// Adds a new implicitly declared top-level variable named [name] to [module]
|
|
// based on a use site occurring on [line].
|
|
//
|
|
// Does not check to see if a variable with that name is already declared or
|
|
// defined. Returns the symbol for the new variable or -2 if there are too many
|
|
// variables defined.
|
|
int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name,
|
|
size_t length, int line);
|
|
|
|
// Adds a new top-level variable named [name] to [module].
|
|
//
|
|
// Returns the symbol for the new variable, -1 if a variable with the given name
|
|
// is already defined, or -2 if there are too many variables defined.
|
|
int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name,
|
|
size_t length, Value value);
|
|
|
|
// Pushes [closure] onto [fiber]'s callstack to invoke it. Expects [numArgs]
|
|
// arguments (including the receiver) to be on the top of the stack already.
|
|
static inline void wrenCallFunction(WrenVM* vm, ObjFiber* fiber,
|
|
ObjClosure* closure, int numArgs)
|
|
{
|
|
// Grow the call frame array if needed.
|
|
if (fiber->numFrames + 1 > fiber->frameCapacity)
|
|
{
|
|
int max = fiber->frameCapacity * 2;
|
|
fiber->frames = (CallFrame*)wrenReallocate(vm, fiber->frames,
|
|
sizeof(CallFrame) * fiber->frameCapacity, sizeof(CallFrame) * max);
|
|
fiber->frameCapacity = max;
|
|
}
|
|
|
|
// Grow the stack if needed.
|
|
int stackSize = (int)(fiber->stackTop - fiber->stack);
|
|
int needed = stackSize + closure->fn->maxSlots;
|
|
wrenEnsureStack(vm, fiber, needed);
|
|
|
|
wrenAppendCallFrame(vm, fiber, closure, fiber->stackTop - numArgs);
|
|
}
|
|
|
|
// Marks [obj] as a GC root so that it doesn't get collected.
|
|
void wrenPushRoot(WrenVM* vm, Obj* obj);
|
|
|
|
// Removes the most recently pushed temporary root.
|
|
void wrenPopRoot(WrenVM* vm);
|
|
|
|
// Returns the class of [value].
|
|
//
|
|
// Defined here instead of in wren_value.h because it's critical that this be
|
|
// inlined. That means it must be defined in the header, but the wren_value.h
|
|
// header doesn't have a full definitely of WrenVM yet.
|
|
static inline ObjClass* wrenGetClassInline(WrenVM* vm, Value value)
|
|
{
|
|
if (IS_NUM(value)) return vm->numClass;
|
|
if (IS_OBJ(value)) return AS_OBJ(value)->classObj;
|
|
|
|
#if WREN_NAN_TAGGING
|
|
switch (GET_TAG(value))
|
|
{
|
|
case TAG_FALSE: return vm->boolClass; break;
|
|
case TAG_NAN: return vm->numClass; break;
|
|
case TAG_NULL: return vm->nullClass; break;
|
|
case TAG_TRUE: return vm->boolClass; break;
|
|
case TAG_UNDEFINED: UNREACHABLE();
|
|
}
|
|
#else
|
|
switch (value.type)
|
|
{
|
|
case VAL_FALSE: return vm->boolClass;
|
|
case VAL_NULL: return vm->nullClass;
|
|
case VAL_NUM: return vm->numClass;
|
|
case VAL_TRUE: return vm->boolClass;
|
|
case VAL_OBJ: return AS_OBJ(value)->classObj;
|
|
case VAL_UNDEFINED: UNREACHABLE();
|
|
}
|
|
#endif
|
|
|
|
UNREACHABLE();
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|