Reorganize source files.
This makes it clear which files are part of the VM (i.e. the Wren library) and which are part of the CLI. Makes a directory for the latter so it has some room to grow. This probably totally broke the VS project. If you can fix that, send me a PR!
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
#ifndef wren_common_h
|
||||
#define wren_common_h
|
||||
|
||||
// This header contains macros and defines used across the entire Wren
|
||||
// implementation. In particular, it contains "configuration" defines that
|
||||
// control how Wren works. Some of these are only used while hacking on
|
||||
// debugging Wren itself.
|
||||
//
|
||||
// This header is *not* intended to be included by code outside of Wren itself.
|
||||
|
||||
// Wren pervasively uses the C99 integer types (uint16_t, etc.) along with some
|
||||
// of the associated limit constants (UINT32_MAX, etc.). The constants are not
|
||||
// part of standard C++, so aren't included by default by C++ compilers when you
|
||||
// include <stdint> unless __STDC_LIMIT_MACROS is defined.
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
|
||||
// These flags let you control some details of the interpreter's implementation.
|
||||
// Usually they trade-off a bit of portability for speed. They default to the
|
||||
// most efficient behavior.
|
||||
|
||||
// If true, then Wren will use a NaN-tagged double for its core value
|
||||
// representation. Otherwise, it will use a larger more conventional struct.
|
||||
// The former is significantly faster and more compact. The latter is useful for
|
||||
// debugging and may be more portable.
|
||||
//
|
||||
// Defaults to on.
|
||||
#ifndef WREN_NAN_TAGGING
|
||||
#define WREN_NAN_TAGGING 1
|
||||
#endif
|
||||
|
||||
// If true, the VM's interpreter loop uses computed gotos. See this for more:
|
||||
// http://gcc.gnu.org/onlinedocs/gcc-3.1.1/gcc/Labels-as-Values.html
|
||||
// Enabling this speeds up the main dispatch loop a bit, but requires compiler
|
||||
// support.
|
||||
//
|
||||
// Defaults to on on supported compilers.
|
||||
#ifndef WREN_COMPUTED_GOTO
|
||||
#ifdef _MSC_VER
|
||||
// No computed gotos in Visual Studio.
|
||||
#define WREN_COMPUTED_GOTO 0
|
||||
#else
|
||||
#define WREN_COMPUTED_GOTO 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// If true, loads the "IO" class in the standard library.
|
||||
//
|
||||
// Defaults to on.
|
||||
#ifndef WREN_USE_LIB_IO
|
||||
#define WREN_USE_LIB_IO 1
|
||||
#endif
|
||||
|
||||
// These flags are useful for debugging and hacking on Wren itself. They are not
|
||||
// intended to be used for production code. They default to off.
|
||||
|
||||
// Set this to true to stress test the GC. It will perform a collection before
|
||||
// every allocation. This is useful to ensure that memory is always correctly
|
||||
// reachable.
|
||||
#define WREN_DEBUG_GC_STRESS 0
|
||||
|
||||
// Set this to true to log memory operations as they occur.
|
||||
#define WREN_DEBUG_TRACE_MEMORY 0
|
||||
|
||||
// Set this to true to log garbage collections as they occur.
|
||||
#define WREN_DEBUG_TRACE_GC 0
|
||||
|
||||
// Set this to true to print out the compiled bytecode of each function.
|
||||
#define WREN_DEBUG_DUMP_COMPILED_CODE 0
|
||||
|
||||
// Set this to trace each instruction as it's executed.
|
||||
#define WREN_DEBUG_TRACE_INSTRUCTIONS 0
|
||||
|
||||
// The maximum number of module-level variables that may be defined at one time.
|
||||
// This limitation comes from the 16 bits used for the arguments to
|
||||
// `CODE_LOAD_MODULE_VAR` and `CODE_STORE_MODULE_VAR`.
|
||||
#define MAX_MODULE_VARS 65536
|
||||
|
||||
// The maximum number of arguments that can be passed to a method. Note that
|
||||
// this limitation is hardcoded in other places in the VM, in particular, the
|
||||
// `CODE_CALL_XX` instructions assume a certain maximum number.
|
||||
#define MAX_PARAMETERS 16
|
||||
|
||||
// The maximum name of a method, not including the signature. This is an
|
||||
// arbitrary but enforced maximum just so we know how long the method name
|
||||
// strings need to be in the parser.
|
||||
#define MAX_METHOD_NAME 64
|
||||
|
||||
// The maximum length of a method signature. Signatures look like:
|
||||
//
|
||||
// foo // Getter.
|
||||
// foo() // No-argument method.
|
||||
// foo(_) // One-argument method.
|
||||
// foo(_,_) // Two-argument method.
|
||||
//
|
||||
// The maximum signature length takes into account the longest method name, the
|
||||
// maximum number of parameters with separators between them, and "()".
|
||||
#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + (MAX_PARAMETERS * 2) + 1)
|
||||
|
||||
// The maximum length of an identifier. The only real reason for this limitation
|
||||
// is so that error messages mentioning variables can be stack allocated.
|
||||
#define MAX_VARIABLE_NAME 64
|
||||
|
||||
// The maximum number of fields a class can have, including inherited fields.
|
||||
// This is explicit in the bytecode since `CODE_CLASS` and `CODE_SUBCLASS` take
|
||||
// a single byte for the number of fields. Note that it's 255 and not 256
|
||||
// because creating a class takes the *number* of fields, not the *highest
|
||||
// field index*.
|
||||
#define MAX_FIELDS 255
|
||||
|
||||
// Use the VM's allocator to allocate an object of [type].
|
||||
#define ALLOCATE(vm, type) \
|
||||
((type*)wrenReallocate(vm, NULL, 0, sizeof(type)))
|
||||
|
||||
// Use the VM's allocator to allocate an object of [mainType] containing a
|
||||
// flexible array of [count] objects of [arrayType].
|
||||
#define ALLOCATE_FLEX(vm, mainType, arrayType, count) \
|
||||
((mainType*)wrenReallocate(vm, NULL, 0, \
|
||||
sizeof(mainType) + sizeof(arrayType) * count))
|
||||
|
||||
// Use the VM's allocator to allocate an array of [count] elements of [type].
|
||||
#define ALLOCATE_ARRAY(vm, type, count) \
|
||||
((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * count))
|
||||
|
||||
// Use the VM's allocator to free the previously allocated memory at [pointer].
|
||||
#define DEALLOCATE(vm, pointer) wrenReallocate(vm, pointer, 0, 0)
|
||||
|
||||
// The Microsoft compiler does not support the "inline" modifier when compiling
|
||||
// as plain C.
|
||||
#if defined( _MSC_VER ) && !defined(__cplusplus)
|
||||
#define inline _inline
|
||||
#endif
|
||||
|
||||
// This is used to clearly mark flexible-sized arrays that appear at the end of
|
||||
// some dynamically-allocated structs, known as the "struct hack".
|
||||
#if __STDC_VERSION__ >= 199901L
|
||||
// In C99, a flexible array member is just "[]".
|
||||
#define FLEXIBLE_ARRAY
|
||||
#else
|
||||
// Elsewhere, use a zero-sized array. It's technically undefined behavior, but
|
||||
// works reliably in most known compilers.
|
||||
#define FLEXIBLE_ARRAY 0
|
||||
#endif
|
||||
|
||||
// Assertions are used to validate program invariants. They indicate things the
|
||||
// program expects to be true about its internal state during execution. If an
|
||||
// assertion fails, there is a bug in Wren.
|
||||
//
|
||||
// Assertions add significant overhead, so are only enabled in debug builds.
|
||||
#ifdef DEBUG
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define ASSERT(condition, message) \
|
||||
do \
|
||||
{ \
|
||||
if (!(condition)) \
|
||||
{ \
|
||||
fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", \
|
||||
__FILE__, __LINE__, __func__, message); \
|
||||
abort(); \
|
||||
} \
|
||||
} \
|
||||
while(0)
|
||||
|
||||
// Assertion to indicate that the given point in the code should never be
|
||||
// reached.
|
||||
#define UNREACHABLE() \
|
||||
do \
|
||||
{ \
|
||||
fprintf(stderr, "This line should not be reached.\n"); \
|
||||
abort(); \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
#else
|
||||
|
||||
#define ASSERT(condition, message) do { } while (0)
|
||||
#define UNREACHABLE() do { } while (0)
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
||||
#ifndef wren_parser_h
|
||||
#define wren_parser_h
|
||||
|
||||
#include "wren.h"
|
||||
#include "wren_value.h"
|
||||
|
||||
typedef struct sCompiler Compiler;
|
||||
|
||||
// This module defines the compiler for Wren. It takes a string of source code
|
||||
// and lexes, parses, and compiles it. Wren uses a single-pass compiler. It
|
||||
// does not build an actual AST during parsing and then consume that to
|
||||
// generate code. Instead, the parser directly emits bytecode.
|
||||
//
|
||||
// This forces a few restrictions on the grammar and semantics of the language.
|
||||
// Things like forward references and arbitrary lookahead are much harder. We
|
||||
// get a lot in return for that, though.
|
||||
//
|
||||
// The implementation is much simpler since we don't need to define a bunch of
|
||||
// AST data structures. More so, we don't have to deal with managing memory for
|
||||
// AST objects. The compiler does almost no dynamic allocation while running.
|
||||
//
|
||||
// Compilation is also faster since we don't create a bunch of temporary data
|
||||
// structures and destroy them after generating code.
|
||||
|
||||
// Compiles [source], a string of Wren source code located in [module], to an
|
||||
// [ObjFn] that will execute that code when invoked. Returns `NULL` if the
|
||||
// source contains any syntax errors.
|
||||
ObjFn* wrenCompile(WrenVM* vm, ObjModule* module,
|
||||
const char* sourcePath, const char* source);
|
||||
|
||||
// When a class is defined, its superclass is not known until runtime since
|
||||
// class definitions are just imperative statements. Most of the bytecode for a
|
||||
// a method doesn't care, but there are two places where it matters:
|
||||
//
|
||||
// - To load or store a field, we need to know its index of the field in the
|
||||
// instance's field array. We need to adjust this so that subclass fields
|
||||
// are positioned after superclass fields, and we don't know this until the
|
||||
// superclass is known.
|
||||
//
|
||||
// - Superclass calls need to know which superclass to dispatch to.
|
||||
//
|
||||
// We could handle this dynamically, but that adds overhead. Instead, when a
|
||||
// method is bound, we walk the bytecode for the function and patch it up.
|
||||
void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn);
|
||||
|
||||
// Reaches all of the heap-allocated objects in use by [compiler] (and all of
|
||||
// its parents) so that they are not collected by the GC.
|
||||
void wrenMarkCompiler(WrenVM* vm, Compiler* compiler);
|
||||
|
||||
#endif
|
||||
+1665
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
#ifndef wren_core_h
|
||||
#define wren_core_h
|
||||
|
||||
#include "wren_vm.h"
|
||||
|
||||
// This module defines the built-in classes and their primitives methods that
|
||||
// are implemented directly in C code. Some languages try to implement as much
|
||||
// of the core library itself in the primary language instead of in the host
|
||||
// language.
|
||||
//
|
||||
// With Wren, we try to do as much of it in C as possible. Primitive methods
|
||||
// are always faster than code written in Wren, and it minimizes startup time
|
||||
// since we don't have to parse, compile, and execute Wren code.
|
||||
//
|
||||
// There is one limitation, though. Methods written in C cannot call Wren ones.
|
||||
// They can only be the top of the callstack, and immediately return. This
|
||||
// makes it difficult to have primitive methods that rely on polymorphic
|
||||
// behavior. For example, `IO.write` should call `toString` on its argument,
|
||||
// including user-defined `toString` methods on user-defined classes.
|
||||
|
||||
void wrenInitializeCore(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,314 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include "wren_debug.h"
|
||||
|
||||
void wrenDebugPrintStackTrace(WrenVM* vm, ObjFiber* fiber)
|
||||
{
|
||||
fprintf(stderr, "%s\n", fiber->error->value);
|
||||
|
||||
for (int i = fiber->numFrames - 1; i >= 0; i--)
|
||||
{
|
||||
CallFrame* frame = &fiber->frames[i];
|
||||
ObjFn* fn;
|
||||
if (frame->fn->type == OBJ_FN)
|
||||
{
|
||||
fn = (ObjFn*)frame->fn;
|
||||
}
|
||||
else
|
||||
{
|
||||
fn = ((ObjClosure*)frame->fn)->fn;
|
||||
}
|
||||
|
||||
// Built-in libraries and method call stubs have no source path and are
|
||||
// explicitly omitted from stack traces since we don't want to highlight to
|
||||
// a user the implementation detail of what part of a core library is
|
||||
// implemented in C and what is in Wren.
|
||||
if (fn->debug->sourcePath == NULL ||
|
||||
fn->debug->sourcePath->length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// - 1 because IP has advanced past the instruction that it just executed.
|
||||
int line = fn->debug->sourceLines[frame->ip - fn->bytecode - 1];
|
||||
fprintf(stderr, "[%s line %d] in %s\n",
|
||||
fn->debug->sourcePath->value, line, fn->debug->name);
|
||||
}
|
||||
}
|
||||
|
||||
static int debugPrintInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
|
||||
{
|
||||
int start = i;
|
||||
uint8_t* bytecode = fn->bytecode;
|
||||
Code code = (Code)bytecode[i];
|
||||
|
||||
int line = fn->debug->sourceLines[i];
|
||||
if (lastLine == NULL || *lastLine != line)
|
||||
{
|
||||
printf("%4d:", line);
|
||||
if (lastLine != NULL) *lastLine = line;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" ");
|
||||
}
|
||||
|
||||
printf(" %04d ", i++);
|
||||
|
||||
#define READ_BYTE() (bytecode[i++])
|
||||
#define READ_SHORT() (i += 2, (bytecode[i - 2] << 8) | bytecode[i - 1])
|
||||
|
||||
#define BYTE_INSTRUCTION(name) \
|
||||
printf("%-16s %5d\n", name, READ_BYTE()); \
|
||||
break; \
|
||||
|
||||
switch (code)
|
||||
{
|
||||
case CODE_CONSTANT:
|
||||
{
|
||||
int constant = READ_SHORT();
|
||||
printf("%-16s %5d '", "CONSTANT", constant);
|
||||
wrenPrintValue(fn->constants[constant]);
|
||||
printf("'\n");
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_NULL: printf("NULL\n"); break;
|
||||
case CODE_FALSE: printf("FALSE\n"); break;
|
||||
case CODE_TRUE: printf("TRUE\n"); break;
|
||||
|
||||
case CODE_LOAD_LOCAL_0: printf("LOAD_LOCAL_0\n"); break;
|
||||
case CODE_LOAD_LOCAL_1: printf("LOAD_LOCAL_1\n"); break;
|
||||
case CODE_LOAD_LOCAL_2: printf("LOAD_LOCAL_2\n"); break;
|
||||
case CODE_LOAD_LOCAL_3: printf("LOAD_LOCAL_3\n"); break;
|
||||
case CODE_LOAD_LOCAL_4: printf("LOAD_LOCAL_4\n"); break;
|
||||
case CODE_LOAD_LOCAL_5: printf("LOAD_LOCAL_5\n"); break;
|
||||
case CODE_LOAD_LOCAL_6: printf("LOAD_LOCAL_6\n"); break;
|
||||
case CODE_LOAD_LOCAL_7: printf("LOAD_LOCAL_7\n"); break;
|
||||
case CODE_LOAD_LOCAL_8: printf("LOAD_LOCAL_8\n"); break;
|
||||
|
||||
case CODE_LOAD_LOCAL: BYTE_INSTRUCTION("LOAD_LOCAL");
|
||||
case CODE_STORE_LOCAL: BYTE_INSTRUCTION("STORE_LOCAL");
|
||||
case CODE_LOAD_UPVALUE: BYTE_INSTRUCTION("LOAD_UPVALUE");
|
||||
case CODE_STORE_UPVALUE: BYTE_INSTRUCTION("STORE_UPVALUE");
|
||||
|
||||
case CODE_LOAD_MODULE_VAR:
|
||||
{
|
||||
int slot = READ_SHORT();
|
||||
printf("%-16s %5d '%s'\n", "LOAD_MODULE_VAR", slot,
|
||||
fn->module->variableNames.data[slot].buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_STORE_MODULE_VAR:
|
||||
{
|
||||
int slot = READ_SHORT();
|
||||
printf("%-16s %5d '%s'\n", "STORE_MODULE_VAR", slot,
|
||||
fn->module->variableNames.data[slot].buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_LOAD_FIELD_THIS: BYTE_INSTRUCTION("LOAD_FIELD_THIS");
|
||||
case CODE_STORE_FIELD_THIS: BYTE_INSTRUCTION("STORE_FIELD_THIS");
|
||||
case CODE_LOAD_FIELD: BYTE_INSTRUCTION("LOAD_FIELD");
|
||||
case CODE_STORE_FIELD: BYTE_INSTRUCTION("STORE_FIELD");
|
||||
|
||||
case CODE_POP: printf("POP\n"); break;
|
||||
case CODE_DUP: printf("DUP\n"); break;
|
||||
|
||||
case CODE_CALL_0:
|
||||
case CODE_CALL_1:
|
||||
case CODE_CALL_2:
|
||||
case CODE_CALL_3:
|
||||
case CODE_CALL_4:
|
||||
case CODE_CALL_5:
|
||||
case CODE_CALL_6:
|
||||
case CODE_CALL_7:
|
||||
case CODE_CALL_8:
|
||||
case CODE_CALL_9:
|
||||
case CODE_CALL_10:
|
||||
case CODE_CALL_11:
|
||||
case CODE_CALL_12:
|
||||
case CODE_CALL_13:
|
||||
case CODE_CALL_14:
|
||||
case CODE_CALL_15:
|
||||
case CODE_CALL_16:
|
||||
{
|
||||
int numArgs = bytecode[i - 1] - CODE_CALL_0;
|
||||
int symbol = READ_SHORT();
|
||||
printf("CALL_%-11d %5d '%s'\n", numArgs, symbol,
|
||||
vm->methodNames.data[symbol].buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_SUPER_0:
|
||||
case CODE_SUPER_1:
|
||||
case CODE_SUPER_2:
|
||||
case CODE_SUPER_3:
|
||||
case CODE_SUPER_4:
|
||||
case CODE_SUPER_5:
|
||||
case CODE_SUPER_6:
|
||||
case CODE_SUPER_7:
|
||||
case CODE_SUPER_8:
|
||||
case CODE_SUPER_9:
|
||||
case CODE_SUPER_10:
|
||||
case CODE_SUPER_11:
|
||||
case CODE_SUPER_12:
|
||||
case CODE_SUPER_13:
|
||||
case CODE_SUPER_14:
|
||||
case CODE_SUPER_15:
|
||||
case CODE_SUPER_16:
|
||||
{
|
||||
int numArgs = bytecode[i - 1] - CODE_SUPER_0;
|
||||
int symbol = READ_SHORT();
|
||||
printf("SUPER_%-10d %5d '%s'\n", numArgs, symbol,
|
||||
vm->methodNames.data[symbol].buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_JUMP:
|
||||
{
|
||||
int offset = READ_SHORT();
|
||||
printf("%-16s %5d to %d\n", "JUMP", offset, i + offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_LOOP:
|
||||
{
|
||||
int offset = READ_SHORT();
|
||||
printf("%-16s %5d to %d\n", "LOOP", offset, i - offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_JUMP_IF:
|
||||
{
|
||||
int offset = READ_SHORT();
|
||||
printf("%-16s %5d to %d\n", "JUMP_IF", offset, i + offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_AND:
|
||||
{
|
||||
int offset = READ_SHORT();
|
||||
printf("%-16s %5d to %d\n", "AND", offset, i + offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_OR:
|
||||
{
|
||||
int offset = READ_SHORT();
|
||||
printf("%-16s %5d to %d\n", "OR", offset, i + offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_IS: printf("CODE_IS\n"); break;
|
||||
case CODE_CLOSE_UPVALUE: printf("CLOSE_UPVALUE\n"); break;
|
||||
case CODE_RETURN: printf("CODE_RETURN\n"); break;
|
||||
|
||||
case CODE_CLOSURE:
|
||||
{
|
||||
int constant = READ_SHORT();
|
||||
printf("%-16s %5d ", "CLOSURE", constant);
|
||||
wrenPrintValue(fn->constants[constant]);
|
||||
printf(" ");
|
||||
ObjFn* loadedFn = AS_FN(fn->constants[constant]);
|
||||
for (int j = 0; j < loadedFn->numUpvalues; j++)
|
||||
{
|
||||
int isLocal = READ_BYTE();
|
||||
int index = READ_BYTE();
|
||||
if (j > 0) printf(", ");
|
||||
printf("%s %d", isLocal ? "local" : "upvalue", index);
|
||||
}
|
||||
printf("\n");
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_CLASS:
|
||||
{
|
||||
int numFields = READ_BYTE();
|
||||
printf("%-16s %5d fields\n", "CLASS", numFields);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_METHOD_INSTANCE:
|
||||
{
|
||||
int symbol = READ_SHORT();
|
||||
printf("%-16s %5d '%s'\n", "METHOD_INSTANCE", symbol,
|
||||
vm->methodNames.data[symbol].buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_METHOD_STATIC:
|
||||
{
|
||||
int symbol = READ_SHORT();
|
||||
printf("%-16s %5d '%s'\n", "METHOD_STATIC", symbol,
|
||||
vm->methodNames.data[symbol].buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_LOAD_MODULE:
|
||||
{
|
||||
int constant = READ_SHORT();
|
||||
printf("%-16s %5d '", "LOAD_MODULE", constant);
|
||||
wrenPrintValue(fn->constants[constant]);
|
||||
printf("'\n");
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_IMPORT_VARIABLE:
|
||||
{
|
||||
int module = READ_SHORT();
|
||||
int variable = READ_SHORT();
|
||||
printf("%-16s %5d '", "IMPORT_VARIABLE", module);
|
||||
wrenPrintValue(fn->constants[module]);
|
||||
printf("' '");
|
||||
wrenPrintValue(fn->constants[variable]);
|
||||
printf("'\n");
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_END:
|
||||
printf("CODE_END\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("UKNOWN! [%d]\n", bytecode[i - 1]);
|
||||
break;
|
||||
}
|
||||
|
||||
// Return how many bytes this instruction takes, or -1 if it's an END.
|
||||
if (code == CODE_END) return -1;
|
||||
return i - start;
|
||||
}
|
||||
|
||||
int wrenDebugPrintInstruction(WrenVM* vm, ObjFn* fn, int i)
|
||||
{
|
||||
return debugPrintInstruction(vm, fn, i, NULL);
|
||||
}
|
||||
|
||||
void wrenDebugPrintCode(WrenVM* vm, ObjFn* fn)
|
||||
{
|
||||
printf("%s: %s\n", fn->debug->sourcePath->value, fn->debug->name);
|
||||
|
||||
int i = 0;
|
||||
int lastLine = -1;
|
||||
for (;;)
|
||||
{
|
||||
int offset = debugPrintInstruction(vm, fn, i, &lastLine);
|
||||
if (offset == -1) break;
|
||||
i += offset;
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void wrenDebugPrintStack(ObjFiber* fiber)
|
||||
{
|
||||
printf("(fiber %p) ", fiber);
|
||||
for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++)
|
||||
{
|
||||
wrenPrintValue(*slot);
|
||||
printf(" | ");
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef wren_debug_h
|
||||
#define wren_debug_h
|
||||
|
||||
#include "wren_value.h"
|
||||
#include "wren_vm.h"
|
||||
|
||||
void wrenDebugPrintStackTrace(WrenVM* vm, ObjFiber* fiber);
|
||||
int wrenDebugPrintInstruction(WrenVM* vm, ObjFn* fn, int i);
|
||||
void wrenDebugPrintCode(WrenVM* vm, ObjFn* fn);
|
||||
void wrenDebugPrintStack(ObjFiber* fiber);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,147 @@
|
||||
#include "wren_io.h"
|
||||
|
||||
#if WREN_USE_LIB_IO
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
// TODO: This is an arbitrary limit Do something smarter.
|
||||
#define MAX_READ_LEN 1024
|
||||
|
||||
// This string literal is generated automatically from io.wren. Do not edit.
|
||||
static const char* libSource =
|
||||
"class IO {\n"
|
||||
" static print {\n"
|
||||
" IO.writeString_(\"\n\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(obj) {\n"
|
||||
" IO.writeObject_(obj)\n"
|
||||
" IO.writeString_(\"\n\")\n"
|
||||
" return obj\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2) {\n"
|
||||
" printList_([a1, a2])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2, a3) {\n"
|
||||
" printList_([a1, a2, a3])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2, a3, a4) {\n"
|
||||
" printList_([a1, a2, a3, a4])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2, a3, a4, a5) {\n"
|
||||
" printList_([a1, a2, a3, a4, a5])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2, a3, a4, a5, a6) {\n"
|
||||
" printList_([a1, a2, a3, a4, a5, a6])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2, a3, a4, a5, a6, a7) {\n"
|
||||
" printList_([a1, a2, a3, a4, a5, a6, a7])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2, a3, a4, a5, a6, a7, a8) {\n"
|
||||
" printList_([a1, a2, a3, a4, a5, a6, a7, a8])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2, a3, a4, a5, a6, a7, a8, a9) {\n"
|
||||
" printList_([a1, a2, a3, a4, a5, a6, a7, a8, a9])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) {\n"
|
||||
" printList_([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) {\n"
|
||||
" printList_([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) {\n"
|
||||
" printList_([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) {\n"
|
||||
" printList_([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) {\n"
|
||||
" printList_([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) {\n"
|
||||
" printList_([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) {\n"
|
||||
" printList_([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static printList_(objects) {\n"
|
||||
" for (object in objects) IO.writeObject_(object)\n"
|
||||
" IO.writeString_(\"\n\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static write(obj) {\n"
|
||||
" IO.writeObject_(obj)\n"
|
||||
" return obj\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static read(prompt) {\n"
|
||||
" if (!(prompt is String)) Fiber.abort(\"Prompt must be a string.\")\n"
|
||||
" IO.write(prompt)\n"
|
||||
" return IO.read\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static writeObject_(obj) {\n"
|
||||
" var string = obj.toString\n"
|
||||
" if (string is String) {\n"
|
||||
" IO.writeString_(string)\n"
|
||||
" } else {\n"
|
||||
" IO.writeString_(\"[invalid toString]\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
static void ioWriteString(WrenVM* vm)
|
||||
{
|
||||
const char* s = wrenGetArgumentString(vm, 1);
|
||||
// TODO: Check for null.
|
||||
printf("%s", s);
|
||||
}
|
||||
|
||||
static void ioRead(WrenVM* vm)
|
||||
{
|
||||
char buffer[MAX_READ_LEN];
|
||||
char* result = fgets(buffer, MAX_READ_LEN, stdin);
|
||||
|
||||
if (result != NULL) {
|
||||
wrenReturnString(vm, buffer, (int)strlen(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
static void ioClock(WrenVM* vm)
|
||||
{
|
||||
wrenReturnDouble(vm, (double)clock() / CLOCKS_PER_SEC);
|
||||
}
|
||||
|
||||
static void ioTime(WrenVM* vm)
|
||||
{
|
||||
wrenReturnDouble(vm, (double)time(NULL));
|
||||
}
|
||||
|
||||
void wrenLoadIOLibrary(WrenVM* vm)
|
||||
{
|
||||
wrenInterpret(vm, "", libSource);
|
||||
wrenDefineStaticMethod(vm, "IO", "writeString_(_)", ioWriteString);
|
||||
wrenDefineStaticMethod(vm, "IO", "clock", ioClock);
|
||||
wrenDefineStaticMethod(vm, "IO", "time", ioTime);
|
||||
wrenDefineStaticMethod(vm, "IO", "read", ioRead);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef wren_io_h
|
||||
#define wren_io_h
|
||||
|
||||
#include "wren.h"
|
||||
#include "wren_common.h"
|
||||
|
||||
// This module defines the IO class and its associated methods. They are
|
||||
// implemented using the C standard library.
|
||||
#if WREN_USE_LIB_IO
|
||||
|
||||
void wrenLoadIOLibrary(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,60 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "wren_utils.h"
|
||||
#include "wren_vm.h"
|
||||
|
||||
DEFINE_BUFFER(Byte, uint8_t);
|
||||
DEFINE_BUFFER(Int, int);
|
||||
DEFINE_BUFFER(String, String);
|
||||
|
||||
void wrenSymbolTableInit(WrenVM* vm, SymbolTable* symbols)
|
||||
{
|
||||
wrenStringBufferInit(vm, symbols);
|
||||
}
|
||||
|
||||
void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols)
|
||||
{
|
||||
for (int i = 0; i < symbols->count; i++)
|
||||
{
|
||||
DEALLOCATE(vm, symbols->data[i].buffer);
|
||||
}
|
||||
|
||||
wrenStringBufferClear(vm, symbols);
|
||||
}
|
||||
|
||||
int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols,
|
||||
const char* name, size_t length)
|
||||
{
|
||||
String symbol;
|
||||
symbol.buffer = ALLOCATE_ARRAY(vm, char, length + 1);
|
||||
memcpy(symbol.buffer, name, length);
|
||||
symbol.buffer[length] = '\0';
|
||||
symbol.length = (int)length;
|
||||
|
||||
wrenStringBufferWrite(vm, symbols, symbol);
|
||||
return symbols->count - 1;
|
||||
}
|
||||
|
||||
int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols,
|
||||
const char* name, size_t length)
|
||||
{
|
||||
// See if the symbol is already defined.
|
||||
int existing = wrenSymbolTableFind(symbols, name, length);
|
||||
if (existing != -1) return existing;
|
||||
|
||||
// New symbol, so add it.
|
||||
return wrenSymbolTableAdd(vm, symbols, name, length);
|
||||
}
|
||||
|
||||
int wrenSymbolTableFind(SymbolTable* symbols, const char* name, size_t length)
|
||||
{
|
||||
// See if the symbol is already defined.
|
||||
// TODO: O(n). Do something better.
|
||||
for (int i = 0; i < symbols->count; i++)
|
||||
{
|
||||
if (symbols->data[i].length == length &&
|
||||
memcmp(symbols->data[i].buffer, name, length) == 0) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#ifndef wren_utils_h
|
||||
#define wren_utils_h
|
||||
|
||||
#include "wren.h"
|
||||
#include "wren_common.h"
|
||||
|
||||
// Reusable data structures and other utility functions.
|
||||
|
||||
// A simple structure to keep trace of the string length as long as its data
|
||||
// (including the null-terminator)
|
||||
typedef struct {
|
||||
char* buffer;
|
||||
uint32_t length;
|
||||
} String;
|
||||
|
||||
// We need buffers of a few different types. To avoid lots of casting between
|
||||
// void* and back, we'll use the preprocessor as a poor man's generics and let
|
||||
// it generate a few type-specific ones.
|
||||
#define DECLARE_BUFFER(name, type) \
|
||||
typedef struct \
|
||||
{ \
|
||||
type* data; \
|
||||
int count; \
|
||||
int capacity; \
|
||||
} name##Buffer; \
|
||||
void wren##name##BufferInit(WrenVM* vm, name##Buffer* buffer); \
|
||||
void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer); \
|
||||
void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data)
|
||||
|
||||
// This should be used once for each type instantiation, somewhere in a .c file.
|
||||
#define DEFINE_BUFFER(name, type) \
|
||||
void wren##name##BufferInit(WrenVM* vm, name##Buffer* buffer) \
|
||||
{ \
|
||||
buffer->data = NULL; \
|
||||
buffer->capacity = 0; \
|
||||
buffer->count = 0; \
|
||||
} \
|
||||
\
|
||||
void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer) \
|
||||
{ \
|
||||
wrenReallocate(vm, buffer->data, 0, 0); \
|
||||
wren##name##BufferInit(vm, buffer); \
|
||||
} \
|
||||
\
|
||||
void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) \
|
||||
{ \
|
||||
if (buffer->capacity < buffer->count + 1) \
|
||||
{ \
|
||||
int capacity = buffer->capacity == 0 ? 8 : buffer->capacity * 2; \
|
||||
buffer->data = (type*)wrenReallocate(vm, buffer->data, \
|
||||
buffer->capacity * sizeof(type), capacity * sizeof(type)); \
|
||||
buffer->capacity = capacity; \
|
||||
} \
|
||||
buffer->data[buffer->count] = data; \
|
||||
buffer->count++; \
|
||||
}
|
||||
|
||||
DECLARE_BUFFER(Byte, uint8_t);
|
||||
DECLARE_BUFFER(Int, int);
|
||||
DECLARE_BUFFER(String, String);
|
||||
|
||||
// TODO: Change this to use a map.
|
||||
typedef StringBuffer SymbolTable;
|
||||
|
||||
// Initializes the symbol table.
|
||||
void wrenSymbolTableInit(WrenVM* vm, SymbolTable* symbols);
|
||||
|
||||
// Frees all dynamically allocated memory used by the symbol table, but not the
|
||||
// SymbolTable itself.
|
||||
void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols);
|
||||
|
||||
// Adds name to the symbol table. Returns the index of it in the table.
|
||||
int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols,
|
||||
const char* name, size_t length);
|
||||
|
||||
// Adds name to the symbol table. Returns the index of it in the table. Will
|
||||
// use an existing symbol if already present.
|
||||
int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols,
|
||||
const char* name, size_t length);
|
||||
|
||||
// Looks up name in the symbol table. Returns its index if found or -1 if not.
|
||||
int wrenSymbolTableFind(SymbolTable* symbols, const char* name, size_t length);
|
||||
|
||||
#endif
|
||||
+1113
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,836 @@
|
||||
#ifndef wren_value_h
|
||||
#define wren_value_h
|
||||
|
||||
#include <stdbool.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 a single double-precision floating
|
||||
// point value. 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.
|
||||
|
||||
// TODO: Make these externally controllable.
|
||||
#define STACK_SIZE 1024
|
||||
#define MAX_CALL_FRAMES 256
|
||||
|
||||
// Identifies which specific type a heap-allocated object is.
|
||||
typedef enum {
|
||||
OBJ_CLASS,
|
||||
OBJ_CLOSURE,
|
||||
OBJ_FIBER,
|
||||
OBJ_FN,
|
||||
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
|
||||
{
|
||||
ObjType type;
|
||||
bool marked;
|
||||
|
||||
// The object's class.
|
||||
ObjClass* classObj;
|
||||
|
||||
// The next object in the linked list of all currently allocated objects.
|
||||
struct sObj* next;
|
||||
} Obj;
|
||||
|
||||
#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);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
// Does not include the null terminator.
|
||||
uint32_t length;
|
||||
char value[FLEXIBLE_ARRAY];
|
||||
} ObjString;
|
||||
|
||||
// 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 sUpvalue
|
||||
{
|
||||
// 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 sUpvalue* next;
|
||||
} Upvalue;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// Pointer to the current (really next-to-be-executed) instruction in the
|
||||
// function's bytecode.
|
||||
uint8_t* ip;
|
||||
|
||||
// The function or closure being executed.
|
||||
Obj* fn;
|
||||
|
||||
// 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;
|
||||
|
||||
typedef struct sObjFiber
|
||||
{
|
||||
Obj obj;
|
||||
Value stack[STACK_SIZE];
|
||||
Value* stackTop;
|
||||
|
||||
CallFrame frames[MAX_CALL_FRAMES];
|
||||
int numFrames;
|
||||
|
||||
// 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.
|
||||
Upvalue* 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 message. Otherwise, it will be NULL.
|
||||
ObjString* error;
|
||||
|
||||
// This will be true if the caller that called this fiber did so using "try".
|
||||
// In that case, if this fiber fails with an error, the error will be given
|
||||
// to the caller.
|
||||
bool callerIsTrying;
|
||||
} ObjFiber;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
// A normal value has been returned.
|
||||
PRIM_VALUE,
|
||||
|
||||
// A runtime error occurred.
|
||||
PRIM_ERROR,
|
||||
|
||||
// A new callframe has been pushed.
|
||||
PRIM_CALL,
|
||||
|
||||
// A fiber is being switched to.
|
||||
PRIM_RUN_FIBER
|
||||
|
||||
} PrimitiveResult;
|
||||
|
||||
typedef PrimitiveResult (*Primitive)(WrenVM* vm, ObjFiber* fiber, 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 ObjFn.
|
||||
char* name;
|
||||
|
||||
// The name of the source file where this function was defined. An [ObjString]
|
||||
// because this will be shared among all functions defined in the same file.
|
||||
ObjString* sourcePath;
|
||||
|
||||
// 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.
|
||||
int* 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;
|
||||
} ObjModule;
|
||||
|
||||
// A first-class function object. A raw ObjFn can be used and invoked directly
|
||||
// if it has no upvalues (i.e. [numUpvalues] is zero). If it does use upvalues,
|
||||
// it must be wrapped in an [ObjClosure] first. The compiler is responsible for
|
||||
// emitting code to ensure that that happens.
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
// TODO: Make one of these a flexible array? I tried each and it didn't seem
|
||||
// to help perf, but it bears more investigation.
|
||||
Value* constants;
|
||||
uint8_t* bytecode;
|
||||
|
||||
// The module where this function was defined.
|
||||
ObjModule* module;
|
||||
|
||||
int numUpvalues;
|
||||
int numConstants;
|
||||
|
||||
// TODO: Move to FnDebug?
|
||||
int bytecodeLength;
|
||||
|
||||
// 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.
|
||||
Upvalue* upvalues[FLEXIBLE_ARRAY];
|
||||
} ObjClosure;
|
||||
|
||||
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;
|
||||
|
||||
// May be a [ObjFn] or [ObjClosure].
|
||||
Obj* obj;
|
||||
} fn;
|
||||
} 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;
|
||||
Value fields[FLEXIBLE_ARRAY];
|
||||
} ObjInstance;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
|
||||
// TODO: Make these uint32_t to match ObjMap, or vice versa.
|
||||
|
||||
// The number of elements allocated.
|
||||
int capacity;
|
||||
|
||||
// The number of items in the list.
|
||||
int count;
|
||||
|
||||
// Pointer to a contiguous array of [capacity] elements.
|
||||
Value* 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;
|
||||
|
||||
|
||||
// Value -> ObjClass*.
|
||||
#define AS_CLASS(value) ((ObjClass*)AS_OBJ(value))
|
||||
|
||||
// Value -> ObjClosure*.
|
||||
#define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value))
|
||||
|
||||
// Value -> ObjFiber*.
|
||||
#define AS_FIBER(v) ((ObjFiber*)AS_OBJ(v))
|
||||
|
||||
// Value -> ObjFn*.
|
||||
#define AS_FN(value) ((ObjFn*)AS_OBJ(value))
|
||||
|
||||
// Value -> ObjInstance*.
|
||||
#define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value))
|
||||
|
||||
// Value -> ObjList*.
|
||||
#define AS_LIST(value) ((ObjList*)AS_OBJ(value))
|
||||
|
||||
// Value -> ObjMap*.
|
||||
#define AS_MAP(value) ((ObjMap*)AS_OBJ(value))
|
||||
|
||||
// Value -> ObjModule*.
|
||||
#define AS_MODULE(value) ((ObjModule*)AS_OBJ(value))
|
||||
|
||||
// Value -> double.
|
||||
#define AS_NUM(value) (wrenValueToNum(value))
|
||||
|
||||
// Value -> ObjRange*.
|
||||
#define AS_RANGE(v) ((ObjRange*)AS_OBJ(v))
|
||||
|
||||
// Value -> ObjString*.
|
||||
#define AS_STRING(v) ((ObjString*)AS_OBJ(v))
|
||||
|
||||
// Value -> const char*.
|
||||
#define AS_CSTRING(v) (AS_STRING(v)->value)
|
||||
|
||||
// Convert [boolean] to a boolean [Value].
|
||||
#define BOOL_VAL(boolean) (boolean ? TRUE_VAL : FALSE_VAL)
|
||||
|
||||
// double -> Value.
|
||||
#define NUM_VAL(num) (wrenNumToValue(num))
|
||||
|
||||
// Convert [obj], an `Obj*`, to a [Value].
|
||||
#define OBJ_VAL(obj) (wrenObjectToValue((Obj*)(obj)))
|
||||
|
||||
// Returns true if [value] is a bool.
|
||||
#define IS_BOOL(value) (wrenIsBool(value))
|
||||
|
||||
// Returns true if [value] is a class.
|
||||
#define IS_CLASS(value) (wrenIsObjType(value, OBJ_CLASS))
|
||||
|
||||
// Returns true if [value] is a closure.
|
||||
#define IS_CLOSURE(value) (wrenIsObjType(value, OBJ_CLOSURE))
|
||||
|
||||
// Returns true if [value] is a fiber.
|
||||
#define IS_FIBER(value) (wrenIsObjType(value, OBJ_FIBER))
|
||||
|
||||
// Returns true if [value] is a function object.
|
||||
#define IS_FN(value) (wrenIsObjType(value, OBJ_FN))
|
||||
|
||||
// Returns true if [value] is an instance.
|
||||
#define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE))
|
||||
|
||||
// Returns true if [value] is a range object.
|
||||
#define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE))
|
||||
|
||||
// Returns true if [value] is a string object.
|
||||
#define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING))
|
||||
|
||||
|
||||
// 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
|
||||
// -111111111111---------------------------------------------------
|
||||
//
|
||||
// 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--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 three bits of the mantissa for that, and only need a couple:
|
||||
//
|
||||
// 3 Type bits--v
|
||||
// 0[NaN ]1-----------------------------------------------[T]
|
||||
//
|
||||
// For pointers, we are left with 48 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 })
|
||||
#define NULL_VAL ((Value){ VAL_NULL })
|
||||
#define TRUE_VAL ((Value){ VAL_TRUE })
|
||||
#define UNDEFINED_VAL ((Value){ VAL_UNDEFINED })
|
||||
|
||||
#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 [fn], which can be a function or
|
||||
// closure.
|
||||
ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn);
|
||||
|
||||
// Resets [fiber] back to an initial state where it is ready to invoke [fn].
|
||||
void wrenResetFiber(ObjFiber* fiber, Obj* fn);
|
||||
|
||||
// TODO: The argument list here is getting a bit gratuitous.
|
||||
// Creates a new function object with the given code and constants. The new
|
||||
// function will take over ownership of [bytecode] and [sourceLines]. It will
|
||||
// copy [constants] into its own array.
|
||||
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module,
|
||||
Value* constants, int numConstants,
|
||||
int numUpvalues, int arity,
|
||||
uint8_t* bytecode, int bytecodeLength,
|
||||
ObjString* debugSourcePath,
|
||||
const char* debugName, int debugNameLength,
|
||||
int* sourceLines);
|
||||
|
||||
// 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, int numElements);
|
||||
|
||||
// Adds [value] to [list], reallocating and growing its storage if needed.
|
||||
void wrenListAdd(WrenVM* vm, ObjList* list, Value value);
|
||||
|
||||
// Inserts [value] in [list] at [index], shifting down the other elements.
|
||||
void wrenListInsert(WrenVM* vm, ObjList* list, Value value, int index);
|
||||
|
||||
// Removes and returns the item at [index] from [list].
|
||||
Value wrenListRemoveAt(WrenVM* vm, ObjList* list, int 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);
|
||||
|
||||
// Creates a new range from [from] to [to].
|
||||
Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive);
|
||||
|
||||
// Creates a new string object of [length] and copies [text] into it.
|
||||
//
|
||||
// [text] may be NULL if [length] is zero.
|
||||
Value wrenNewString(WrenVM* vm, const char* text, size_t length);
|
||||
|
||||
// Creates a new string object with a buffer large enough to hold a string of
|
||||
// [length] but does no initialization of the buffer.
|
||||
//
|
||||
// The caller is expected to fully initialize the buffer after calling.
|
||||
Value wrenNewUninitializedString(WrenVM* vm, size_t length);
|
||||
|
||||
// Creates a new string that is the concatenation of [left] and [right] (with
|
||||
// length [leftLength] and [rightLength], respectively). If -1 is passed
|
||||
// the string length is automatically calculated.
|
||||
ObjString* wrenStringConcat(WrenVM* vm, const char* left, int leftLength,
|
||||
const char* right, int rightLength);
|
||||
|
||||
// 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(WrenVM* vm, ObjString* haystack, ObjString* needle);
|
||||
|
||||
// Creates a new open upvalue pointing to [value] on the stack.
|
||||
Upvalue* wrenNewUpvalue(WrenVM* vm, Value* value);
|
||||
|
||||
// Mark [value] as reachable and still in use. This should only be called
|
||||
// during the sweep phase of a garbage collection.
|
||||
void wrenMarkValue(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 wrenMarkObj(WrenVM* vm, Obj* obj);
|
||||
|
||||
// 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);
|
||||
|
||||
// TODO: Need to decide if this is for user output of values, or for debug
|
||||
// tracing.
|
||||
void wrenPrintValue(Value value);
|
||||
|
||||
// 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
|
||||
+1660
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,384 @@
|
||||
#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
|
||||
{
|
||||
// Load the constant at index [arg].
|
||||
CODE_CONSTANT,
|
||||
|
||||
// Push null onto the stack.
|
||||
CODE_NULL,
|
||||
|
||||
// Push false onto the stack.
|
||||
CODE_FALSE,
|
||||
|
||||
// Push true onto the stack.
|
||||
CODE_TRUE,
|
||||
|
||||
// Pushes the value in the given local slot.
|
||||
CODE_LOAD_LOCAL_0,
|
||||
CODE_LOAD_LOCAL_1,
|
||||
CODE_LOAD_LOCAL_2,
|
||||
CODE_LOAD_LOCAL_3,
|
||||
CODE_LOAD_LOCAL_4,
|
||||
CODE_LOAD_LOCAL_5,
|
||||
CODE_LOAD_LOCAL_6,
|
||||
CODE_LOAD_LOCAL_7,
|
||||
CODE_LOAD_LOCAL_8,
|
||||
|
||||
// Note: The compiler assumes the following _STORE instructions always
|
||||
// immediately follow their corresponding _LOAD ones.
|
||||
|
||||
// Pushes the value in local slot [arg].
|
||||
CODE_LOAD_LOCAL,
|
||||
|
||||
// Stores the top of stack in local slot [arg]. Does not pop it.
|
||||
CODE_STORE_LOCAL,
|
||||
|
||||
// Pushes the value in upvalue [arg].
|
||||
CODE_LOAD_UPVALUE,
|
||||
|
||||
// Stores the top of stack in upvalue [arg]. Does not pop it.
|
||||
CODE_STORE_UPVALUE,
|
||||
|
||||
// Pushes the value of the top-level variable in slot [arg].
|
||||
CODE_LOAD_MODULE_VAR,
|
||||
|
||||
// Stores the top of stack in top-level variable slot [arg]. Does not pop it.
|
||||
CODE_STORE_MODULE_VAR,
|
||||
|
||||
// Pushes the value of the field in slot [arg] of the receiver of the current
|
||||
// function. This is used for regular field accesses on "this" directly in
|
||||
// methods. This instruction is faster than the more general CODE_LOAD_FIELD
|
||||
// instruction.
|
||||
CODE_LOAD_FIELD_THIS,
|
||||
|
||||
// Stores the top of the stack in field slot [arg] in the receiver of the
|
||||
// current value. Does not pop the value. This instruction is faster than the
|
||||
// more general CODE_LOAD_FIELD instruction.
|
||||
CODE_STORE_FIELD_THIS,
|
||||
|
||||
// Pops an instance and pushes the value of the field in slot [arg] of it.
|
||||
CODE_LOAD_FIELD,
|
||||
|
||||
// Pops an instance and stores the subsequent top of stack in field slot
|
||||
// [arg] in it. Does not pop the value.
|
||||
CODE_STORE_FIELD,
|
||||
|
||||
// Pop and discard the top of stack.
|
||||
CODE_POP,
|
||||
|
||||
// Push a copy of the value currently on the top of the stack.
|
||||
CODE_DUP,
|
||||
|
||||
// Invoke the method with symbol [arg]. The number indicates the number of
|
||||
// arguments (not including the receiver).
|
||||
CODE_CALL_0,
|
||||
CODE_CALL_1,
|
||||
CODE_CALL_2,
|
||||
CODE_CALL_3,
|
||||
CODE_CALL_4,
|
||||
CODE_CALL_5,
|
||||
CODE_CALL_6,
|
||||
CODE_CALL_7,
|
||||
CODE_CALL_8,
|
||||
CODE_CALL_9,
|
||||
CODE_CALL_10,
|
||||
CODE_CALL_11,
|
||||
CODE_CALL_12,
|
||||
CODE_CALL_13,
|
||||
CODE_CALL_14,
|
||||
CODE_CALL_15,
|
||||
CODE_CALL_16,
|
||||
|
||||
// Invoke a superclass method with symbol [arg]. The number indicates the
|
||||
// number of arguments (not including the receiver).
|
||||
CODE_SUPER_0,
|
||||
CODE_SUPER_1,
|
||||
CODE_SUPER_2,
|
||||
CODE_SUPER_3,
|
||||
CODE_SUPER_4,
|
||||
CODE_SUPER_5,
|
||||
CODE_SUPER_6,
|
||||
CODE_SUPER_7,
|
||||
CODE_SUPER_8,
|
||||
CODE_SUPER_9,
|
||||
CODE_SUPER_10,
|
||||
CODE_SUPER_11,
|
||||
CODE_SUPER_12,
|
||||
CODE_SUPER_13,
|
||||
CODE_SUPER_14,
|
||||
CODE_SUPER_15,
|
||||
CODE_SUPER_16,
|
||||
|
||||
// Jump the instruction pointer [arg] forward.
|
||||
CODE_JUMP,
|
||||
|
||||
// Jump the instruction pointer [arg] backward. Pop and discard the top of
|
||||
// the stack.
|
||||
CODE_LOOP,
|
||||
|
||||
// Pop and if not truthy then jump the instruction pointer [arg] forward.
|
||||
CODE_JUMP_IF,
|
||||
|
||||
// If the top of the stack is false, jump [arg] forward. Otherwise, pop and
|
||||
// continue.
|
||||
CODE_AND,
|
||||
|
||||
// If the top of the stack is non-false, jump [arg] forward. Otherwise, pop
|
||||
// and continue.
|
||||
CODE_OR,
|
||||
|
||||
// Pop [a] then [b] and push true if [b] is an instance of [a].
|
||||
CODE_IS,
|
||||
|
||||
// Close the upvalue for the local on the top of the stack, then pop it.
|
||||
CODE_CLOSE_UPVALUE,
|
||||
|
||||
// Exit from the current function and return the value on the top of the
|
||||
// stack.
|
||||
CODE_RETURN,
|
||||
|
||||
// Creates a closure for the function stored at [arg] in the constant table.
|
||||
//
|
||||
// Following the function argument is a number of arguments, two for each
|
||||
// upvalue. The first is true if the variable being captured is a local (as
|
||||
// opposed to an upvalue), and the second is the index of the local or
|
||||
// upvalue being captured.
|
||||
//
|
||||
// Pushes the created closure.
|
||||
CODE_CLOSURE,
|
||||
|
||||
// Creates a class. Top of stack is the superclass, or `null` if the class
|
||||
// inherits Object. Below that is a string for the name of the class. Byte
|
||||
// [arg] is the number of fields in the class.
|
||||
CODE_CLASS,
|
||||
|
||||
// Define a method for symbol [arg]. The class receiving the method is popped
|
||||
// off the stack, then the function defining the body is popped.
|
||||
CODE_METHOD_INSTANCE,
|
||||
|
||||
// Define a method for symbol [arg]. The class whose metaclass will receive
|
||||
// the method is popped off the stack, then the function defining the body is
|
||||
// popped.
|
||||
CODE_METHOD_STATIC,
|
||||
|
||||
// Load the module whose name is stored in string constant [arg]. Pushes
|
||||
// NULL onto the stack. If the module has already been loaded, does nothing
|
||||
// else. Otherwise, it creates a fiber to run the desired module and switches
|
||||
// to that. When that fiber is done, the current one is resumed.
|
||||
CODE_LOAD_MODULE,
|
||||
|
||||
// Reads a top-level variable from another module. [arg1] is a string
|
||||
// constant for the name of the module, and [arg2] is a string constant for
|
||||
// the variable name. Pushes the variable if found, or generates a runtime
|
||||
// error otherwise.
|
||||
CODE_IMPORT_VARIABLE,
|
||||
|
||||
// This pseudo-instruction indicates the end of the bytecode. It should
|
||||
// always be preceded by a `CODE_RETURN`, so is never actually executed.
|
||||
CODE_END
|
||||
} Code;
|
||||
|
||||
struct WrenMethod
|
||||
{
|
||||
// The fiber that invokes the method. Its stack is pre-populated with the
|
||||
// receiver for the method, and it contains a single callframe whose function
|
||||
// is the bytecode stub to invoke the method.
|
||||
ObjFiber* fiber;
|
||||
|
||||
WrenMethod* prev;
|
||||
WrenMethod* 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;
|
||||
|
||||
// Memory management data:
|
||||
|
||||
// The externally-provided function used to allocate memory.
|
||||
WrenReallocateFn reallocate;
|
||||
|
||||
// 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 minimum value for [nextGC] when recalculated after a collection.
|
||||
size_t minNextGC;
|
||||
|
||||
// The scale factor used to calculate [nextGC] from the current number of in
|
||||
// use bytes, as a percent. For example, 150 here means that nextGC will be
|
||||
// 50% larger than the current number of in-use bytes.
|
||||
int heapScalePercent;
|
||||
|
||||
// The first object in the linked list of all currently allocated objects.
|
||||
Obj* first;
|
||||
|
||||
// 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;
|
||||
|
||||
// Foreign function data:
|
||||
|
||||
// During a foreign function call, this will point to the first argument (the
|
||||
// receiver) of the call on the fiber's stack.
|
||||
Value* foreignCallSlot;
|
||||
|
||||
// Pointer to the first node in the linked list of active method handles or
|
||||
// NULL if there are no handles.
|
||||
WrenMethod* methodHandles;
|
||||
|
||||
// During a foreign function call, this will contain the number of arguments
|
||||
// to the function.
|
||||
int foreignCallNumArgs;
|
||||
|
||||
// The function used to load modules.
|
||||
WrenLoadModuleFn loadModule;
|
||||
|
||||
// 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);
|
||||
|
||||
// Imports the module with [name].
|
||||
//
|
||||
// If the module has already been imported (or is already in the middle of
|
||||
// being imported, in the case of a circular import), returns true. Otherwise,
|
||||
// returns a new fiber that will execute the module's code. That fiber should
|
||||
// be called before any variables are loaded from the module.
|
||||
//
|
||||
// If the module could not be found, returns false.
|
||||
Value wrenImportModule(WrenVM* vm, const char* name);
|
||||
|
||||
// Returns the value of the module-level variable named [name] in the main
|
||||
// module.
|
||||
Value wrenFindVariable(WrenVM* vm, const char* name);
|
||||
|
||||
// Adds a new implicitly declared top-level variable named [name] to [module].
|
||||
//
|
||||
// If [module] is `NULL`, uses the main module.
|
||||
//
|
||||
// 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);
|
||||
|
||||
// Adds a new top-level variable named [name] to [module].
|
||||
//
|
||||
// If [module] is `NULL`, uses the main 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);
|
||||
|
||||
// Sets the current Compiler being run to [compiler].
|
||||
void wrenSetCompiler(WrenVM* vm, Compiler* compiler);
|
||||
|
||||
// Mark [obj] as a GC root so that it doesn't get collected.
|
||||
void wrenPushRoot(WrenVM* vm, Obj* obj);
|
||||
|
||||
// Remove 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
|
||||
Reference in New Issue
Block a user