877 lines
30 KiB
C
877 lines
30 KiB
C
#ifndef wren_value_h
|
|
#define wren_value_h
|
|
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#include "wren_common.h"
|
|
#include "wren_utils.h"
|
|
|
|
// This defines the built-in types and their core representations in memory.
|
|
// Since Wren is dynamically typed, any variable can hold a value of any type,
|
|
// and the type can change at runtime. Implementing this efficiently is
|
|
// critical for performance.
|
|
//
|
|
// The main type exposed by this is [Value]. A C variable of that type is a
|
|
// storage location that can hold any Wren value. The stack, module variables,
|
|
// and instance fields are all implemented in C as variables of type Value.
|
|
//
|
|
// The built-in types for booleans, numbers, and null are unboxed: their value
|
|
// is stored directly in the Value, and copying a Value copies the value. Other
|
|
// types--classes, instances of classes, functions, lists, and strings--are all
|
|
// reference types. They are stored on the heap and the Value just stores a
|
|
// pointer to it. Copying the Value copies a reference to the same object. The
|
|
// Wren implementation calls these "Obj", or objects, though to a user, all
|
|
// values are objects.
|
|
//
|
|
// There is also a special singleton value "undefined". It is used internally
|
|
// but never appears as a real value to a user. It has two uses:
|
|
//
|
|
// - It is used to identify module variables that have been implicitly declared
|
|
// by use in a forward reference but not yet explicitly declared. These only
|
|
// exist during compilation and do not appear at runtime.
|
|
//
|
|
// - It is used to represent unused map entries in an ObjMap.
|
|
//
|
|
// There are two supported Value representations. The main one uses a technique
|
|
// called "NaN tagging" (explained in detail below) to store a number, any of
|
|
// the value types, or a pointer, all inside one double-precision floating
|
|
// point number. A larger, slower, Value type that uses a struct to store these
|
|
// is also supported, and is useful for debugging the VM.
|
|
//
|
|
// The representation is controlled by the `WREN_NAN_TAGGING` define. If that's
|
|
// defined, Nan tagging is used.
|
|
|
|
// These macros cast a Value to one of the specific object types. These do *not*
|
|
// perform any validation, so must only be used after the Value has been
|
|
// ensured to be the right type.
|
|
#define AS_CLASS(value) ((ObjClass*)AS_OBJ(value)) // ObjClass*
|
|
#define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value)) // ObjClosure*
|
|
#define AS_FIBER(v) ((ObjFiber*)AS_OBJ(v)) // ObjFiber*
|
|
#define AS_FN(value) ((ObjFn*)AS_OBJ(value)) // ObjFn*
|
|
#define AS_FOREIGN(v) ((ObjForeign*)AS_OBJ(v)) // ObjForeign*
|
|
#define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) // ObjInstance*
|
|
#define AS_LIST(value) ((ObjList*)AS_OBJ(value)) // ObjList*
|
|
#define AS_MAP(value) ((ObjMap*)AS_OBJ(value)) // ObjMap*
|
|
#define AS_MODULE(value) ((ObjModule*)AS_OBJ(value)) // ObjModule*
|
|
#define AS_NUM(value) (wrenValueToNum(value)) // double
|
|
#define AS_RANGE(v) ((ObjRange*)AS_OBJ(v)) // ObjRange*
|
|
#define AS_STRING(v) ((ObjString*)AS_OBJ(v)) // ObjString*
|
|
#define AS_CSTRING(v) (AS_STRING(v)->value) // const char*
|
|
|
|
// These macros promote a primitive C value to a full Wren Value. There are
|
|
// more defined below that are specific to the Nan tagged or other
|
|
// representation.
|
|
#define BOOL_VAL(boolean) ((boolean) ? TRUE_VAL : FALSE_VAL) // boolean
|
|
#define NUM_VAL(num) (wrenNumToValue(num)) // double
|
|
#define OBJ_VAL(obj) (wrenObjectToValue((Obj*)(obj))) // Any Obj___*
|
|
|
|
// These perform type tests on a Value, returning `true` if the Value is of the
|
|
// given type.
|
|
#define IS_BOOL(value) (wrenIsBool(value)) // Bool
|
|
#define IS_CLASS(value) (wrenIsObjType(value, OBJ_CLASS)) // ObjClass
|
|
#define IS_CLOSURE(value) (wrenIsObjType(value, OBJ_CLOSURE)) // ObjClosure
|
|
#define IS_FIBER(value) (wrenIsObjType(value, OBJ_FIBER)) // ObjFiber
|
|
#define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn
|
|
#define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign
|
|
#define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance
|
|
#define IS_LIST(value) (wrenIsObjType(value, OBJ_LIST)) // ObjList
|
|
#define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange
|
|
#define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString
|
|
|
|
// Creates a new string object from [text], which should be a bare C string
|
|
// literal. This determines the length of the string automatically at compile
|
|
// time based on the size of the character array (-1 for the terminating '\0').
|
|
#define CONST_STRING(vm, text) wrenNewStringLength((vm), (text), sizeof(text) - 1)
|
|
|
|
// Identifies which specific type a heap-allocated object is.
|
|
typedef enum {
|
|
OBJ_CLASS,
|
|
OBJ_CLOSURE,
|
|
OBJ_FIBER,
|
|
OBJ_FN,
|
|
OBJ_FOREIGN,
|
|
OBJ_INSTANCE,
|
|
OBJ_LIST,
|
|
OBJ_MAP,
|
|
OBJ_MODULE,
|
|
OBJ_RANGE,
|
|
OBJ_STRING,
|
|
OBJ_UPVALUE
|
|
} ObjType;
|
|
|
|
typedef struct sObjClass ObjClass;
|
|
|
|
// Base struct for all heap-allocated objects.
|
|
typedef struct sObj Obj;
|
|
struct sObj
|
|
{
|
|
ObjType type;
|
|
bool isDark;
|
|
|
|
// The object's class.
|
|
ObjClass* classObj;
|
|
|
|
// The next object in the linked list of all currently allocated objects.
|
|
struct sObj* next;
|
|
};
|
|
|
|
#if WREN_NAN_TAGGING
|
|
|
|
typedef uint64_t Value;
|
|
|
|
#else
|
|
|
|
typedef enum
|
|
{
|
|
VAL_FALSE,
|
|
VAL_NULL,
|
|
VAL_NUM,
|
|
VAL_TRUE,
|
|
VAL_UNDEFINED,
|
|
VAL_OBJ
|
|
} ValueType;
|
|
|
|
typedef struct
|
|
{
|
|
ValueType type;
|
|
union
|
|
{
|
|
double num;
|
|
Obj* obj;
|
|
} as;
|
|
} Value;
|
|
|
|
#endif
|
|
|
|
DECLARE_BUFFER(Value, Value);
|
|
|
|
// A heap-allocated string object.
|
|
struct sObjString
|
|
{
|
|
Obj obj;
|
|
|
|
// Number of bytes in the string, not including the null terminator.
|
|
uint32_t length;
|
|
|
|
// The hash value of the string's contents.
|
|
uint32_t hash;
|
|
|
|
// Inline array of the string's bytes followed by a null terminator.
|
|
char value[FLEXIBLE_ARRAY];
|
|
};
|
|
|
|
// The dynamically allocated data structure for a variable that has been used
|
|
// by a closure. Whenever a function accesses a variable declared in an
|
|
// enclosing function, it will get to it through this.
|
|
//
|
|
// An upvalue can be either "closed" or "open". An open upvalue points directly
|
|
// to a [Value] that is still stored on the fiber's stack because the local
|
|
// variable is still in scope in the function where it's declared.
|
|
//
|
|
// When that local variable goes out of scope, the upvalue pointing to it will
|
|
// be closed. When that happens, the value gets copied off the stack into the
|
|
// upvalue itself. That way, it can have a longer lifetime than the stack
|
|
// variable.
|
|
typedef struct sObjUpvalue
|
|
{
|
|
// The object header. Note that upvalues have this because they are garbage
|
|
// collected, but they are not first class Wren objects.
|
|
Obj obj;
|
|
|
|
// Pointer to the variable this upvalue is referencing.
|
|
Value* value;
|
|
|
|
// If the upvalue is closed (i.e. the local variable it was pointing too has
|
|
// been popped off the stack) then the closed-over value will be hoisted out
|
|
// of the stack into here. [value] will then be changed to point to this.
|
|
Value closed;
|
|
|
|
// Open upvalues are stored in a linked list by the fiber. This points to the
|
|
// next upvalue in that list.
|
|
struct sObjUpvalue* next;
|
|
} ObjUpvalue;
|
|
|
|
// The type of a primitive function.
|
|
//
|
|
// Primitives are similiar to foreign functions, but have more direct access to
|
|
// VM internals. It is passed the arguments in [args]. If it returns a value,
|
|
// it places it in `args[0]` and returns `true`. If it causes a runtime error
|
|
// or modifies the running fiber, it returns `false`.
|
|
typedef bool (*Primitive)(WrenVM* vm, Value* args);
|
|
|
|
// TODO: See if it's actually a perf improvement to have this in a separate
|
|
// struct instead of in ObjFn.
|
|
// Stores debugging information for a function used for things like stack
|
|
// traces.
|
|
typedef struct
|
|
{
|
|
// The name of the function. Heap allocated and owned by the FnDebug.
|
|
char* name;
|
|
|
|
// An array of line numbers. There is one element in this array for each
|
|
// bytecode in the function's bytecode array. The value of that element is
|
|
// the line in the source code that generated that instruction.
|
|
IntBuffer sourceLines;
|
|
} FnDebug;
|
|
|
|
// A loaded module and the top-level variables it defines.
|
|
//
|
|
// While this is an Obj and is managed by the GC, it never appears as a
|
|
// first-class object in Wren.
|
|
typedef struct
|
|
{
|
|
Obj obj;
|
|
|
|
// The currently defined top-level variables.
|
|
ValueBuffer variables;
|
|
|
|
// Symbol table for the names of all module variables. Indexes here directly
|
|
// correspond to entries in [variables].
|
|
SymbolTable variableNames;
|
|
|
|
// The name of the module.
|
|
ObjString* name;
|
|
} ObjModule;
|
|
|
|
// A function object. It wraps and owns the bytecode and other debug information
|
|
// for a callable chunk of code.
|
|
//
|
|
// Function objects are not passed around and invoked directly. Instead, they
|
|
// are always referenced by an [ObjClosure] which is the real first-class
|
|
// representation of a function. This isn't strictly necessary if they function
|
|
// has no upvalues, but lets the rest of the VM assume all called objects will
|
|
// be closures.
|
|
typedef struct
|
|
{
|
|
Obj obj;
|
|
|
|
ByteBuffer code;
|
|
ValueBuffer constants;
|
|
|
|
// The module where this function was defined.
|
|
ObjModule* module;
|
|
|
|
// The maximum number of stack slots this function may use.
|
|
int maxSlots;
|
|
|
|
// The number of upvalues this function closes over.
|
|
int numUpvalues;
|
|
|
|
// The number of parameters this function expects. Used to ensure that .call
|
|
// handles a mismatch between number of parameters and arguments. This will
|
|
// only be set for fns, and not ObjFns that represent methods or scripts.
|
|
int arity;
|
|
FnDebug* debug;
|
|
} ObjFn;
|
|
|
|
// An instance of a first-class function and the environment it has closed over.
|
|
// Unlike [ObjFn], this has captured the upvalues that the function accesses.
|
|
typedef struct
|
|
{
|
|
Obj obj;
|
|
|
|
// The function that this closure is an instance of.
|
|
ObjFn* fn;
|
|
|
|
// The upvalues this function has closed over.
|
|
ObjUpvalue* upvalues[FLEXIBLE_ARRAY];
|
|
} ObjClosure;
|
|
|
|
typedef struct
|
|
{
|
|
// Pointer to the current (really next-to-be-executed) instruction in the
|
|
// function's bytecode.
|
|
uint8_t* ip;
|
|
|
|
// The closure being executed.
|
|
ObjClosure* closure;
|
|
|
|
// Pointer to the first stack slot used by this call frame. This will contain
|
|
// the receiver, followed by the function's parameters, then local variables
|
|
// and temporaries.
|
|
Value* stackStart;
|
|
} CallFrame;
|
|
|
|
// Tracks how this fiber has been invoked, aside from the ways that can be
|
|
// detected from the state of other fields in the fiber.
|
|
typedef enum
|
|
{
|
|
// The fiber is being run from another fiber using a call to `try()`.
|
|
FIBER_TRY,
|
|
|
|
// The fiber was directly invoked by `runInterpreter()`. This means it's the
|
|
// initial fiber used by a call to `wrenCall()` or `wrenInterpret()`.
|
|
FIBER_ROOT,
|
|
|
|
// The fiber is invoked some other way. If [caller] is `NULL` then the fiber
|
|
// was invoked using `call()`. If [numFrames] is zero, then the fiber has
|
|
// finished running and is done. If [numFrames] is one and that frame's `ip`
|
|
// points to the first byte of code, the fiber has not been started yet.
|
|
FIBER_OTHER,
|
|
} FiberState;
|
|
|
|
typedef struct sObjFiber
|
|
{
|
|
Obj obj;
|
|
|
|
// The stack of value slots. This is used for holding local variables and
|
|
// temporaries while the fiber is executing. It heap-allocated and grown as
|
|
// needed.
|
|
Value* stack;
|
|
|
|
// A pointer to one past the top-most value on the stack.
|
|
Value* stackTop;
|
|
|
|
// The number of allocated slots in the stack array.
|
|
int stackCapacity;
|
|
|
|
// The stack of call frames. This is a dynamic array that grows as needed but
|
|
// never shrinks.
|
|
CallFrame* frames;
|
|
|
|
// The number of frames currently in use in [frames].
|
|
int numFrames;
|
|
|
|
// The number of [frames] allocated.
|
|
int frameCapacity;
|
|
|
|
// Pointer to the first node in the linked list of open upvalues that are
|
|
// pointing to values still on the stack. The head of the list will be the
|
|
// upvalue closest to the top of the stack, and then the list works downwards.
|
|
ObjUpvalue* openUpvalues;
|
|
|
|
// The fiber that ran this one. If this fiber is yielded, control will resume
|
|
// to this one. May be `NULL`.
|
|
struct sObjFiber* caller;
|
|
|
|
// If the fiber failed because of a runtime error, this will contain the
|
|
// error object. Otherwise, it will be null.
|
|
Value error;
|
|
|
|
FiberState state;
|
|
} ObjFiber;
|
|
|
|
typedef enum
|
|
{
|
|
// A primitive method implemented in C in the VM. Unlike foreign methods,
|
|
// this can directly manipulate the fiber's stack.
|
|
METHOD_PRIMITIVE,
|
|
|
|
// A externally-defined C method.
|
|
METHOD_FOREIGN,
|
|
|
|
// A normal user-defined method.
|
|
METHOD_BLOCK,
|
|
|
|
// No method for the given symbol.
|
|
METHOD_NONE
|
|
} MethodType;
|
|
|
|
typedef struct
|
|
{
|
|
MethodType type;
|
|
|
|
// The method function itself. The [type] determines which field of the union
|
|
// is used.
|
|
union
|
|
{
|
|
Primitive primitive;
|
|
WrenForeignMethodFn foreign;
|
|
ObjClosure* closure;
|
|
} as;
|
|
} Method;
|
|
|
|
DECLARE_BUFFER(Method, Method);
|
|
|
|
struct sObjClass
|
|
{
|
|
Obj obj;
|
|
ObjClass* superclass;
|
|
|
|
// The number of fields needed for an instance of this class, including all
|
|
// of its superclass fields.
|
|
int numFields;
|
|
|
|
// The table of methods that are defined in or inherited by this class.
|
|
// Methods are called by symbol, and the symbol directly maps to an index in
|
|
// this table. This makes method calls fast at the expense of empty cells in
|
|
// the list for methods the class doesn't support.
|
|
//
|
|
// You can think of it as a hash table that never has collisions but has a
|
|
// really low load factor. Since methods are pretty small (just a type and a
|
|
// pointer), this should be a worthwhile trade-off.
|
|
MethodBuffer methods;
|
|
|
|
// The name of the class.
|
|
ObjString* name;
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
Obj obj;
|
|
uint8_t data[FLEXIBLE_ARRAY];
|
|
} ObjForeign;
|
|
|
|
typedef struct
|
|
{
|
|
Obj obj;
|
|
Value fields[FLEXIBLE_ARRAY];
|
|
} ObjInstance;
|
|
|
|
typedef struct
|
|
{
|
|
Obj obj;
|
|
|
|
// The elements in the list.
|
|
ValueBuffer elements;
|
|
} ObjList;
|
|
|
|
typedef struct
|
|
{
|
|
// The entry's key, or UNDEFINED_VAL if the entry is not in use.
|
|
Value key;
|
|
|
|
// The value associated with the key. If the key is UNDEFINED_VAL, this will
|
|
// be false to indicate an open available entry or true to indicate a
|
|
// tombstone -- an entry that was previously in use but was then deleted.
|
|
Value value;
|
|
} MapEntry;
|
|
|
|
// A hash table mapping keys to values.
|
|
//
|
|
// We use something very simple: open addressing with linear probing. The hash
|
|
// table is an array of entries. Each entry is a key-value pair. If the key is
|
|
// the special UNDEFINED_VAL, it indicates no value is currently in that slot.
|
|
// Otherwise, it's a valid key, and the value is the value associated with it.
|
|
//
|
|
// When entries are added, the array is dynamically scaled by GROW_FACTOR to
|
|
// keep the number of filled slots under MAP_LOAD_PERCENT. Likewise, if the map
|
|
// gets empty enough, it will be resized to a smaller array. When this happens,
|
|
// all existing entries are rehashed and re-added to the new array.
|
|
//
|
|
// When an entry is removed, its slot is replaced with a "tombstone". This is an
|
|
// entry whose key is UNDEFINED_VAL and whose value is TRUE_VAL. When probing
|
|
// for a key, we will continue past tombstones, because the desired key may be
|
|
// found after them if the key that was removed was part of a prior collision.
|
|
// When the array gets resized, all tombstones are discarded.
|
|
typedef struct
|
|
{
|
|
Obj obj;
|
|
|
|
// The number of entries allocated.
|
|
uint32_t capacity;
|
|
|
|
// The number of entries in the map.
|
|
uint32_t count;
|
|
|
|
// Pointer to a contiguous array of [capacity] entries.
|
|
MapEntry* entries;
|
|
} ObjMap;
|
|
|
|
typedef struct
|
|
{
|
|
Obj obj;
|
|
|
|
// The beginning of the range.
|
|
double from;
|
|
|
|
// The end of the range. May be greater or less than [from].
|
|
double to;
|
|
|
|
// True if [to] is included in the range.
|
|
bool isInclusive;
|
|
} ObjRange;
|
|
|
|
// An IEEE 754 double-precision float is a 64-bit value with bits laid out like:
|
|
//
|
|
// 1 Sign bit
|
|
// | 11 Exponent bits
|
|
// | | 52 Mantissa (i.e. fraction) bits
|
|
// | | |
|
|
// S[Exponent-][Mantissa------------------------------------------]
|
|
//
|
|
// The details of how these are used to represent numbers aren't really
|
|
// relevant here as long we don't interfere with them. The important bit is NaN.
|
|
//
|
|
// An IEEE double can represent a few magical values like NaN ("not a number"),
|
|
// Infinity, and -Infinity. A NaN is any value where all exponent bits are set:
|
|
//
|
|
// v--NaN bits
|
|
// -11111111111----------------------------------------------------
|
|
//
|
|
// Here, "-" means "doesn't matter". Any bit sequence that matches the above is
|
|
// a NaN. With all of those "-", it obvious there are a *lot* of different
|
|
// bit patterns that all mean the same thing. NaN tagging takes advantage of
|
|
// this. We'll use those available bit patterns to represent things other than
|
|
// numbers without giving up any valid numeric values.
|
|
//
|
|
// NaN values come in two flavors: "signalling" and "quiet". The former are
|
|
// intended to halt execution, while the latter just flow through arithmetic
|
|
// operations silently. We want the latter. Quiet NaNs are indicated by setting
|
|
// the highest mantissa bit:
|
|
//
|
|
// v--Highest mantissa bit
|
|
// -[NaN ]1---------------------------------------------------
|
|
//
|
|
// If all of the NaN bits are set, it's not a number. Otherwise, it is.
|
|
// That leaves all of the remaining bits as available for us to play with. We
|
|
// stuff a few different kinds of things here: special singleton values like
|
|
// "true", "false", and "null", and pointers to objects allocated on the heap.
|
|
// We'll use the sign bit to distinguish singleton values from pointers. If
|
|
// it's set, it's a pointer.
|
|
//
|
|
// v--Pointer or singleton?
|
|
// S[NaN ]1---------------------------------------------------
|
|
//
|
|
// For singleton values, we just enumerate the different values. We'll use the
|
|
// low bits of the mantissa for that, and only need a few:
|
|
//
|
|
// 3 Type bits--v
|
|
// 0[NaN ]1------------------------------------------------[T]
|
|
//
|
|
// For pointers, we are left with 51 bits of mantissa to store an address.
|
|
// That's more than enough room for a 32-bit address. Even 64-bit machines
|
|
// only actually use 48 bits for addresses, so we've got plenty. We just stuff
|
|
// the address right into the mantissa.
|
|
//
|
|
// Ta-da, double precision numbers, pointers, and a bunch of singleton values,
|
|
// all stuffed into a single 64-bit sequence. Even better, we don't have to
|
|
// do any masking or work to extract number values: they are unmodified. This
|
|
// means math on numbers is fast.
|
|
#if WREN_NAN_TAGGING
|
|
|
|
// A mask that selects the sign bit.
|
|
#define SIGN_BIT ((uint64_t)1 << 63)
|
|
|
|
// The bits that must be set to indicate a quiet NaN.
|
|
#define QNAN ((uint64_t)0x7ffc000000000000)
|
|
|
|
// If the NaN bits are set, it's not a number.
|
|
#define IS_NUM(value) (((value) & QNAN) != QNAN)
|
|
|
|
// An object pointer is a NaN with a set sign bit.
|
|
#define IS_OBJ(value) (((value) & (QNAN | SIGN_BIT)) == (QNAN | SIGN_BIT))
|
|
|
|
#define IS_FALSE(value) ((value) == FALSE_VAL)
|
|
#define IS_NULL(value) ((value) == NULL_VAL)
|
|
#define IS_UNDEFINED(value) ((value) == UNDEFINED_VAL)
|
|
|
|
// Masks out the tag bits used to identify the singleton value.
|
|
#define MASK_TAG (7)
|
|
|
|
// Tag values for the different singleton values.
|
|
#define TAG_NAN (0)
|
|
#define TAG_NULL (1)
|
|
#define TAG_FALSE (2)
|
|
#define TAG_TRUE (3)
|
|
#define TAG_UNDEFINED (4)
|
|
#define TAG_UNUSED2 (5)
|
|
#define TAG_UNUSED3 (6)
|
|
#define TAG_UNUSED4 (7)
|
|
|
|
// Value -> 0 or 1.
|
|
#define AS_BOOL(value) ((value) == TRUE_VAL)
|
|
|
|
// Value -> Obj*.
|
|
#define AS_OBJ(value) ((Obj*)(uintptr_t)((value) & ~(SIGN_BIT | QNAN)))
|
|
|
|
// Singleton values.
|
|
#define NULL_VAL ((Value)(uint64_t)(QNAN | TAG_NULL))
|
|
#define FALSE_VAL ((Value)(uint64_t)(QNAN | TAG_FALSE))
|
|
#define TRUE_VAL ((Value)(uint64_t)(QNAN | TAG_TRUE))
|
|
#define UNDEFINED_VAL ((Value)(uint64_t)(QNAN | TAG_UNDEFINED))
|
|
|
|
// Gets the singleton type tag for a Value (which must be a singleton).
|
|
#define GET_TAG(value) ((int)((value) & MASK_TAG))
|
|
|
|
#else
|
|
|
|
// Value -> 0 or 1.
|
|
#define AS_BOOL(value) ((value).type == VAL_TRUE)
|
|
|
|
// Value -> Obj*.
|
|
#define AS_OBJ(v) ((v).as.obj)
|
|
|
|
// Determines if [value] is a garbage-collected object or not.
|
|
#define IS_OBJ(value) ((value).type == VAL_OBJ)
|
|
|
|
#define IS_FALSE(value) ((value).type == VAL_FALSE)
|
|
#define IS_NULL(value) ((value).type == VAL_NULL)
|
|
#define IS_NUM(value) ((value).type == VAL_NUM)
|
|
#define IS_UNDEFINED(value) ((value).type == VAL_UNDEFINED)
|
|
|
|
// Singleton values.
|
|
#define FALSE_VAL ((Value){ VAL_FALSE, { 0 } })
|
|
#define NULL_VAL ((Value){ VAL_NULL, { 0 } })
|
|
#define TRUE_VAL ((Value){ VAL_TRUE, { 0 } })
|
|
#define UNDEFINED_VAL ((Value){ VAL_UNDEFINED, { 0 } })
|
|
|
|
#endif
|
|
|
|
// A union to let us reinterpret a double as raw bits and back.
|
|
typedef union
|
|
{
|
|
uint64_t bits64;
|
|
uint32_t bits32[2];
|
|
double num;
|
|
} DoubleBits;
|
|
|
|
// Creates a new "raw" class. It has no metaclass or superclass whatsoever.
|
|
// This is only used for bootstrapping the initial Object and Class classes,
|
|
// which are a little special.
|
|
ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name);
|
|
|
|
// Makes [superclass] the superclass of [subclass], and causes subclass to
|
|
// inherit its methods. This should be called before any methods are defined
|
|
// on subclass.
|
|
void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass);
|
|
|
|
// Creates a new class object as well as its associated metaclass.
|
|
ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields,
|
|
ObjString* name);
|
|
|
|
void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method);
|
|
|
|
// Creates a new closure object that invokes [fn]. Allocates room for its
|
|
// upvalues, but assumes outside code will populate it.
|
|
ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn);
|
|
|
|
// Creates a new fiber object that will invoke [closure].
|
|
ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure);
|
|
|
|
// Adds a new [CallFrame] to [fiber] invoking [closure] whose stack starts at
|
|
// [stackStart].
|
|
static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber,
|
|
ObjClosure* closure, Value* stackStart)
|
|
{
|
|
// The caller should have ensured we already have enough capacity.
|
|
ASSERT(fiber->frameCapacity > fiber->numFrames, "No memory for call frame.");
|
|
|
|
CallFrame* frame = &fiber->frames[fiber->numFrames++];
|
|
frame->stackStart = stackStart;
|
|
frame->closure = closure;
|
|
frame->ip = closure->fn->code.data;
|
|
}
|
|
|
|
// Ensures [fiber]'s stack has at least [needed] slots.
|
|
void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed);
|
|
|
|
static inline bool wrenHasError(const ObjFiber* fiber)
|
|
{
|
|
return !IS_NULL(fiber->error);
|
|
}
|
|
|
|
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size);
|
|
|
|
// Creates a new empty function. Before being used, it must have code,
|
|
// constants, etc. added to it.
|
|
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots);
|
|
|
|
void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length);
|
|
|
|
// Creates a new instance of the given [classObj].
|
|
Value wrenNewInstance(WrenVM* vm, ObjClass* classObj);
|
|
|
|
// Creates a new list with [numElements] elements (which are left
|
|
// uninitialized.)
|
|
ObjList* wrenNewList(WrenVM* vm, uint32_t numElements);
|
|
|
|
// Inserts [value] in [list] at [index], shifting down the other elements.
|
|
void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index);
|
|
|
|
// Removes and returns the item at [index] from [list].
|
|
Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index);
|
|
|
|
// Creates a new empty map.
|
|
ObjMap* wrenNewMap(WrenVM* vm);
|
|
|
|
// Looks up [key] in [map]. If found, returns the value. Otherwise, returns
|
|
// `UNDEFINED_VAL`.
|
|
Value wrenMapGet(ObjMap* map, Value key);
|
|
|
|
// Associates [key] with [value] in [map].
|
|
void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value);
|
|
|
|
void wrenMapClear(WrenVM* vm, ObjMap* map);
|
|
|
|
// Removes [key] from [map], if present. Returns the value for the key if found
|
|
// or `NULL_VAL` otherwise.
|
|
Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key);
|
|
|
|
// Creates a new module.
|
|
ObjModule* wrenNewModule(WrenVM* vm, ObjString* name);
|
|
|
|
// Creates a new range from [from] to [to].
|
|
Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive);
|
|
|
|
// Creates a new string object and copies [text] into it.
|
|
//
|
|
// [text] must be non-NULL.
|
|
Value wrenNewString(WrenVM* vm, const char* text);
|
|
|
|
// Creates a new string object of [length] and copies [text] into it.
|
|
//
|
|
// [text] may be NULL if [length] is zero.
|
|
Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length);
|
|
|
|
// Creates a new string object by taking a range of characters from [source].
|
|
// The range starts at [start], contains [count] bytes, and increments by
|
|
// [step].
|
|
Value wrenNewStringFromRange(WrenVM* vm, ObjString* source, int start,
|
|
uint32_t count, int step);
|
|
|
|
// Produces a string representation of [value].
|
|
Value wrenNumToString(WrenVM* vm, double value);
|
|
|
|
// Creates a new formatted string from [format] and any additional arguments
|
|
// used in the format string.
|
|
//
|
|
// This is a very restricted flavor of formatting, intended only for internal
|
|
// use by the VM. Two formatting characters are supported, each of which reads
|
|
// the next argument as a certain type:
|
|
//
|
|
// $ - A C string.
|
|
// @ - A Wren string object.
|
|
Value wrenStringFormat(WrenVM* vm, const char* format, ...);
|
|
|
|
// Creates a new string containing the UTF-8 encoding of [value].
|
|
Value wrenStringFromCodePoint(WrenVM* vm, int value);
|
|
|
|
// Creates a new string from the integer representation of a byte
|
|
Value wrenStringFromByte(WrenVM* vm, uint8_t value);
|
|
|
|
// Creates a new string containing the code point in [string] starting at byte
|
|
// [index]. If [index] points into the middle of a UTF-8 sequence, returns an
|
|
// empty string.
|
|
Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index);
|
|
|
|
// Search for the first occurence of [needle] within [haystack] and returns its
|
|
// zero-based offset. Returns `UINT32_MAX` if [haystack] does not contain
|
|
// [needle].
|
|
uint32_t wrenStringFind(ObjString* haystack, ObjString* needle,
|
|
uint32_t startIndex);
|
|
|
|
// Returns true if [a] and [b] represent the same string.
|
|
static inline bool wrenStringEqualsCString(const ObjString* a,
|
|
const char* b, size_t length)
|
|
{
|
|
return a->length == length && memcmp(a->value, b, length) == 0;
|
|
}
|
|
|
|
// Creates a new open upvalue pointing to [value] on the stack.
|
|
ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value);
|
|
|
|
// Mark [obj] as reachable and still in use. This should only be called
|
|
// during the sweep phase of a garbage collection.
|
|
void wrenGrayObj(WrenVM* vm, Obj* obj);
|
|
|
|
// Mark [value] as reachable and still in use. This should only be called
|
|
// during the sweep phase of a garbage collection.
|
|
void wrenGrayValue(WrenVM* vm, Value value);
|
|
|
|
// Mark the values in [buffer] as reachable and still in use. This should only
|
|
// be called during the sweep phase of a garbage collection.
|
|
void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer);
|
|
|
|
// Processes every object in the gray stack until all reachable objects have
|
|
// been marked. After that, all objects are either white (freeable) or black
|
|
// (in use and fully traversed).
|
|
void wrenBlackenObjects(WrenVM* vm);
|
|
|
|
// Releases all memory owned by [obj], including [obj] itself.
|
|
void wrenFreeObj(WrenVM* vm, Obj* obj);
|
|
|
|
// Returns the class of [value].
|
|
//
|
|
// Unlike wrenGetClassInline in wren_vm.h, this is not inlined. Inlining helps
|
|
// performance (significantly) in some cases, but degrades it in others. The
|
|
// ones used by the implementation were chosen to give the best results in the
|
|
// benchmarks.
|
|
ObjClass* wrenGetClass(WrenVM* vm, Value value);
|
|
|
|
// Returns true if [a] and [b] are strictly the same value. This is identity
|
|
// for object values, and value equality for unboxed values.
|
|
static inline bool wrenValuesSame(Value a, Value b)
|
|
{
|
|
#if WREN_NAN_TAGGING
|
|
// Value types have unique bit representations and we compare object types
|
|
// by identity (i.e. pointer), so all we need to do is compare the bits.
|
|
return a == b;
|
|
#else
|
|
if (a.type != b.type) return false;
|
|
if (a.type == VAL_NUM) return a.as.num == b.as.num;
|
|
return a.as.obj == b.as.obj;
|
|
#endif
|
|
}
|
|
|
|
// Returns true if [a] and [b] are equivalent. Immutable values (null, bools,
|
|
// numbers, ranges, and strings) are equal if they have the same data. All
|
|
// other values are equal if they are identical objects.
|
|
bool wrenValuesEqual(Value a, Value b);
|
|
|
|
// Returns true if [value] is a bool. Do not call this directly, instead use
|
|
// [IS_BOOL].
|
|
static inline bool wrenIsBool(Value value)
|
|
{
|
|
#if WREN_NAN_TAGGING
|
|
return value == TRUE_VAL || value == FALSE_VAL;
|
|
#else
|
|
return value.type == VAL_FALSE || value.type == VAL_TRUE;
|
|
#endif
|
|
}
|
|
|
|
// Returns true if [value] is an object of type [type]. Do not call this
|
|
// directly, instead use the [IS___] macro for the type in question.
|
|
static inline bool wrenIsObjType(Value value, ObjType type)
|
|
{
|
|
return IS_OBJ(value) && AS_OBJ(value)->type == type;
|
|
}
|
|
|
|
// Converts the raw object pointer [obj] to a [Value].
|
|
static inline Value wrenObjectToValue(Obj* obj)
|
|
{
|
|
#if WREN_NAN_TAGGING
|
|
// The triple casting is necessary here to satisfy some compilers:
|
|
// 1. (uintptr_t) Convert the pointer to a number of the right size.
|
|
// 2. (uint64_t) Pad it up to 64 bits in 32-bit builds.
|
|
// 3. Or in the bits to make a tagged Nan.
|
|
// 4. Cast to a typedef'd value.
|
|
return (Value)(SIGN_BIT | QNAN | (uint64_t)(uintptr_t)(obj));
|
|
#else
|
|
Value value;
|
|
value.type = VAL_OBJ;
|
|
value.as.obj = obj;
|
|
return value;
|
|
#endif
|
|
}
|
|
|
|
// Interprets [value] as a [double].
|
|
static inline double wrenValueToNum(Value value)
|
|
{
|
|
#if WREN_NAN_TAGGING
|
|
DoubleBits data;
|
|
data.bits64 = value;
|
|
return data.num;
|
|
#else
|
|
return value.as.num;
|
|
#endif
|
|
}
|
|
|
|
// Converts [num] to a [Value].
|
|
static inline Value wrenNumToValue(double num)
|
|
{
|
|
#if WREN_NAN_TAGGING
|
|
DoubleBits data;
|
|
data.num = num;
|
|
return data.bits64;
|
|
#else
|
|
Value value;
|
|
value.type = VAL_NUM;
|
|
value.as.num = num;
|
|
return value;
|
|
#endif
|
|
}
|
|
|
|
#endif
|