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:
Bob Nystrom
2015-03-14 15:00:50 -07:00
parent 64eccdd9be
commit 92c17e81f6
19 changed files with 82 additions and 70 deletions
+183
View File
@@ -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
+50
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+23
View File
@@ -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
+314
View File
@@ -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");
}
+12
View File
@@ -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
+147
View File
@@ -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
+15
View File
@@ -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
+60
View File
@@ -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;
}
+84
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+836
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+384
View File
@@ -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