mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-18 13:49:59 +01:00
Compile functions in place instead of copying when done.
The compiler used to maintain all of the data that defines a function, which it incrementally built up. Then, when it was done, it copied all of that to a new ObjFn. In theory, that was to trim down some memory and avoid unneeded fields in ObjFn. In practice, the performance benefit is negligible and the memory saving is nullified by the fact that it copies anyway. This moves all of that mutable state directly into ObjFn. The compiler creates a new empty ObjFn at the beginning and then just fills it in in place. This shaves off about 40 lines of code, is simpler, and doesn't seem to impact perf.
This commit is contained in:
@ -304,9 +304,6 @@ struct sCompiler
|
||||
// top level.
|
||||
struct sCompiler* parent;
|
||||
|
||||
// The constants that have been defined in this function so far.
|
||||
ValueBuffer constants;
|
||||
|
||||
// The currently in scope local variables.
|
||||
Local locals[MAX_LOCALS];
|
||||
|
||||
@ -317,11 +314,6 @@ struct sCompiler
|
||||
// of them is stored in [numUpvalues].
|
||||
CompilerUpvalue upvalues[MAX_UPVALUES];
|
||||
|
||||
int numUpvalues;
|
||||
|
||||
// The number of parameters this method or function expects.
|
||||
int numParams;
|
||||
|
||||
// The current level of block scope nesting, where zero is no nesting. A -1
|
||||
// here means top-level code is being compiled and there is no block scope
|
||||
// in effect at all. Any variables declared will be module-level.
|
||||
@ -338,10 +330,6 @@ struct sCompiler
|
||||
// are already pushed onto the stack by the caller and tracked there, we
|
||||
// don't need to double count them here.
|
||||
int numSlots;
|
||||
|
||||
// The maximum number of slots (locals and temporaries) in use at one time in
|
||||
// the function.
|
||||
int maxSlots;
|
||||
|
||||
// The current innermost loop being compiled, or NULL if not in a loop.
|
||||
Loop* loop;
|
||||
@ -349,11 +337,8 @@ struct sCompiler
|
||||
// If this is a compiler for a method, keeps track of the class enclosing it.
|
||||
ClassCompiler* enclosingClass;
|
||||
|
||||
// The growable buffer of code that's been compiled so far.
|
||||
ByteBuffer bytecode;
|
||||
|
||||
// The growable buffer of source line mappings.
|
||||
IntBuffer debugSourceLines;
|
||||
// The function being compiled.
|
||||
ObjFn* fn;
|
||||
};
|
||||
|
||||
// Describes where a variable is declared.
|
||||
@ -456,10 +441,11 @@ static int addConstant(Compiler* compiler, Value constant)
|
||||
{
|
||||
if (compiler->parser->hasError) return -1;
|
||||
|
||||
if (compiler->constants.count < MAX_CONSTANTS)
|
||||
if (compiler->fn->constants.count < MAX_CONSTANTS)
|
||||
{
|
||||
if (IS_OBJ(constant)) wrenPushRoot(compiler->parser->vm, AS_OBJ(constant));
|
||||
wrenValueBufferWrite(compiler->parser->vm, &compiler->constants, constant);
|
||||
wrenValueBufferWrite(compiler->parser->vm, &compiler->fn->constants,
|
||||
constant);
|
||||
if (IS_OBJ(constant)) wrenPopRoot(compiler->parser->vm);
|
||||
}
|
||||
else
|
||||
@ -468,7 +454,7 @@ static int addConstant(Compiler* compiler, Value constant)
|
||||
MAX_CONSTANTS);
|
||||
}
|
||||
|
||||
return compiler->constants.count - 1;
|
||||
return compiler->fn->constants.count - 1;
|
||||
}
|
||||
|
||||
// Initializes [compiler].
|
||||
@ -477,15 +463,12 @@ static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent,
|
||||
{
|
||||
compiler->parser = parser;
|
||||
compiler->parent = parent;
|
||||
|
||||
// Initialize this to NULL before allocating in case a GC gets triggered in
|
||||
// the middle of initializing the compiler.
|
||||
wrenValueBufferInit(&compiler->constants);
|
||||
|
||||
compiler->numUpvalues = 0;
|
||||
compiler->numParams = 0;
|
||||
compiler->loop = NULL;
|
||||
compiler->enclosingClass = NULL;
|
||||
|
||||
// Initialize this to NULL before allocating in case a GC gets triggered in
|
||||
// the middle of initializing the compiler.
|
||||
compiler->fn = NULL;
|
||||
|
||||
parser->vm->compiler = compiler;
|
||||
|
||||
@ -523,10 +506,9 @@ static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent,
|
||||
}
|
||||
|
||||
compiler->numSlots = compiler->numLocals;
|
||||
compiler->maxSlots = compiler->numLocals;
|
||||
|
||||
wrenByteBufferInit(&compiler->bytecode);
|
||||
wrenIntBufferInit(&compiler->debugSourceLines);
|
||||
compiler->fn = wrenNewFunction(parser->vm, parser->module,
|
||||
compiler->numLocals);
|
||||
}
|
||||
|
||||
// Lexing ----------------------------------------------------------------------
|
||||
@ -1120,13 +1102,13 @@ static void consumeLine(Compiler* compiler, const char* errorMessage)
|
||||
// Emits one single-byte argument. Returns its index.
|
||||
static int emitByte(Compiler* compiler, int byte)
|
||||
{
|
||||
wrenByteBufferWrite(compiler->parser->vm, &compiler->bytecode, (uint8_t)byte);
|
||||
wrenByteBufferWrite(compiler->parser->vm, &compiler->fn->code, (uint8_t)byte);
|
||||
|
||||
// Assume the instruction is associated with the most recently consumed token.
|
||||
wrenIntBufferWrite(compiler->parser->vm, &compiler->debugSourceLines,
|
||||
wrenIntBufferWrite(compiler->parser->vm, &compiler->fn->debug->sourceLines,
|
||||
compiler->parser->previous.line);
|
||||
|
||||
return compiler->bytecode.count - 1;
|
||||
return compiler->fn->code.count - 1;
|
||||
}
|
||||
|
||||
// Emits one bytecode instruction.
|
||||
@ -1136,9 +1118,9 @@ static void emitOp(Compiler* compiler, Code instruction)
|
||||
|
||||
// Keep track of the stack's high water mark.
|
||||
compiler->numSlots += stackEffects[instruction];
|
||||
if (compiler->numSlots > compiler->maxSlots)
|
||||
if (compiler->numSlots > compiler->fn->maxSlots)
|
||||
{
|
||||
compiler->maxSlots = compiler->numSlots;
|
||||
compiler->fn->maxSlots = compiler->numSlots;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1352,16 +1334,16 @@ static int resolveLocal(Compiler* compiler, const char* name, int length)
|
||||
static int addUpvalue(Compiler* compiler, bool isLocal, int index)
|
||||
{
|
||||
// Look for an existing one.
|
||||
for (int i = 0; i < compiler->numUpvalues; i++)
|
||||
for (int i = 0; i < compiler->fn->numUpvalues; i++)
|
||||
{
|
||||
CompilerUpvalue* upvalue = &compiler->upvalues[i];
|
||||
if (upvalue->index == index && upvalue->isLocal == isLocal) return i;
|
||||
}
|
||||
|
||||
// If we got here, it's a new upvalue.
|
||||
compiler->upvalues[compiler->numUpvalues].isLocal = isLocal;
|
||||
compiler->upvalues[compiler->numUpvalues].index = index;
|
||||
return compiler->numUpvalues++;
|
||||
compiler->upvalues[compiler->fn->numUpvalues].isLocal = isLocal;
|
||||
compiler->upvalues[compiler->fn->numUpvalues].index = index;
|
||||
return compiler->fn->numUpvalues++;
|
||||
}
|
||||
|
||||
// Attempts to look up [name] in the functions enclosing the one being compiled
|
||||
@ -1456,28 +1438,16 @@ static void loadLocal(Compiler* compiler, int slot)
|
||||
emitByteArg(compiler, CODE_LOAD_LOCAL, slot);
|
||||
}
|
||||
|
||||
// Discards memory owned by [compiler] and removes it from the stack of
|
||||
// ongoing compilers.
|
||||
static void abortCompiler(Compiler* compiler)
|
||||
{
|
||||
wrenByteBufferClear(compiler->parser->vm, &compiler->bytecode);
|
||||
wrenIntBufferClear(compiler->parser->vm, &compiler->debugSourceLines);
|
||||
wrenValueBufferClear(compiler->parser->vm, &compiler->constants);
|
||||
|
||||
compiler->parser->vm->compiler = compiler->parent;
|
||||
}
|
||||
|
||||
// Finishes [compiler], which is compiling a function, method, or chunk of top
|
||||
// level code. If there is a parent compiler, then this emits code in the
|
||||
// parent compiler to load the resulting function.
|
||||
static ObjFn* endCompiler(Compiler* compiler,
|
||||
const char* debugName, int debugNameLength)
|
||||
{
|
||||
// If we hit an error, don't bother creating the function since it's borked
|
||||
// anyway.
|
||||
// If we hit an error, don't finish the function since it's borked anyway.
|
||||
if (compiler->parser->hasError)
|
||||
{
|
||||
abortCompiler(compiler);
|
||||
compiler->parser->vm->compiler = compiler->parent;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -1485,31 +1455,17 @@ static ObjFn* endCompiler(Compiler* compiler,
|
||||
// we can't rely on CODE_RETURN to tell us we're at the end.
|
||||
emitOp(compiler, CODE_END);
|
||||
|
||||
// Create a function object for the code we just compiled.
|
||||
ObjFn* fn = wrenNewFunction(compiler->parser->vm,
|
||||
compiler->parser->module,
|
||||
compiler->constants.data,
|
||||
compiler->constants.count,
|
||||
compiler->numUpvalues,
|
||||
compiler->maxSlots,
|
||||
compiler->numParams,
|
||||
compiler->bytecode.data,
|
||||
compiler->bytecode.count,
|
||||
debugName, debugNameLength,
|
||||
compiler->debugSourceLines.data);
|
||||
wrenPushRoot(compiler->parser->vm, (Obj*)fn);
|
||||
|
||||
// Clear constants. The constants are copied by wrenNewFunction
|
||||
wrenValueBufferClear(compiler->parser->vm, &compiler->constants);
|
||||
|
||||
wrenFunctionBindName(compiler->parser->vm, compiler->fn,
|
||||
debugName, debugNameLength);
|
||||
|
||||
// In the function that contains this one, load the resulting function object.
|
||||
if (compiler->parent != NULL)
|
||||
{
|
||||
int constant = addConstant(compiler->parent, OBJ_VAL(fn));
|
||||
int constant = addConstant(compiler->parent, OBJ_VAL(compiler->fn));
|
||||
|
||||
// If the function has no upvalues, we don't need to create a closure.
|
||||
// We can just load and run the function directly.
|
||||
if (compiler->numUpvalues == 0)
|
||||
if (compiler->fn->numUpvalues == 0)
|
||||
{
|
||||
emitShortArg(compiler->parent, CODE_CONSTANT, constant);
|
||||
}
|
||||
@ -1520,7 +1476,7 @@ static ObjFn* endCompiler(Compiler* compiler,
|
||||
|
||||
// Emit arguments for each upvalue to know whether to capture a local or
|
||||
// an upvalue.
|
||||
for (int i = 0; i < compiler->numUpvalues; i++)
|
||||
for (int i = 0; i < compiler->fn->numUpvalues; i++)
|
||||
{
|
||||
emitByte(compiler->parent, compiler->upvalues[i].isLocal ? 1 : 0);
|
||||
emitByte(compiler->parent, compiler->upvalues[i].index);
|
||||
@ -1530,14 +1486,12 @@ static ObjFn* endCompiler(Compiler* compiler,
|
||||
|
||||
// Pop this compiler off the stack.
|
||||
compiler->parser->vm->compiler = compiler->parent;
|
||||
|
||||
wrenPopRoot(compiler->parser->vm);
|
||||
|
||||
|
||||
#if WREN_DEBUG_DUMP_COMPILED_CODE
|
||||
wrenDumpCode(compiler->parser->vm, fn);
|
||||
wrenDumpCode(compiler->parser->vm, compiler->fn);
|
||||
#endif
|
||||
|
||||
return fn;
|
||||
return compiler->fn;
|
||||
}
|
||||
|
||||
// Grammar ---------------------------------------------------------------------
|
||||
@ -1590,11 +1544,11 @@ static void parsePrecedence(Compiler* compiler, Precedence precedence);
|
||||
static void patchJump(Compiler* compiler, int offset)
|
||||
{
|
||||
// -2 to adjust for the bytecode for the jump offset itself.
|
||||
int jump = compiler->bytecode.count - offset - 2;
|
||||
int jump = compiler->fn->code.count - offset - 2;
|
||||
if (jump > MAX_JUMP) error(compiler, "Too much code to jump over.");
|
||||
|
||||
compiler->bytecode.data[offset] = (jump >> 8) & 0xff;
|
||||
compiler->bytecode.data[offset + 1] = jump & 0xff;
|
||||
compiler->fn->code.data[offset] = (jump >> 8) & 0xff;
|
||||
compiler->fn->code.data[offset + 1] = jump & 0xff;
|
||||
}
|
||||
|
||||
// Parses a block body, after the initial "{" has been consumed.
|
||||
@ -1877,7 +1831,7 @@ static void methodCall(Compiler* compiler, Code instruction,
|
||||
consume(compiler, TOKEN_PIPE, "Expect '|' after function parameters.");
|
||||
}
|
||||
|
||||
fnCompiler.numParams = fnSignature.arity;
|
||||
fnCompiler.fn->arity = fnSignature.arity;
|
||||
|
||||
finishBody(&fnCompiler, false);
|
||||
|
||||
@ -2799,7 +2753,7 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants,
|
||||
static void startLoop(Compiler* compiler, Loop* loop)
|
||||
{
|
||||
loop->enclosing = compiler->loop;
|
||||
loop->start = compiler->bytecode.count - 1;
|
||||
loop->start = compiler->fn->code.count - 1;
|
||||
loop->scopeDepth = compiler->scopeDepth;
|
||||
compiler->loop = loop;
|
||||
}
|
||||
@ -2816,7 +2770,7 @@ static void testExitLoop(Compiler* compiler)
|
||||
// statements can be handled correctly.
|
||||
static void loopBody(Compiler* compiler)
|
||||
{
|
||||
compiler->loop->body = compiler->bytecode.count;
|
||||
compiler->loop->body = compiler->fn->code.count;
|
||||
statement(compiler);
|
||||
}
|
||||
|
||||
@ -2826,7 +2780,7 @@ static void endLoop(Compiler* compiler)
|
||||
{
|
||||
// We don't check for overflow here since the forward jump over the loop body
|
||||
// will report an error for the same problem.
|
||||
int loopOffset = compiler->bytecode.count - compiler->loop->start + 2;
|
||||
int loopOffset = compiler->fn->code.count - compiler->loop->start + 2;
|
||||
emitShortArg(compiler, CODE_LOOP, loopOffset);
|
||||
|
||||
patchJump(compiler, compiler->loop->exitJump);
|
||||
@ -2834,19 +2788,19 @@ static void endLoop(Compiler* compiler)
|
||||
// Find any break placeholder instructions (which will be CODE_END in the
|
||||
// bytecode) and replace them with real jumps.
|
||||
int i = compiler->loop->body;
|
||||
while (i < compiler->bytecode.count)
|
||||
while (i < compiler->fn->code.count)
|
||||
{
|
||||
if (compiler->bytecode.data[i] == CODE_END)
|
||||
if (compiler->fn->code.data[i] == CODE_END)
|
||||
{
|
||||
compiler->bytecode.data[i] = CODE_JUMP;
|
||||
compiler->fn->code.data[i] = CODE_JUMP;
|
||||
patchJump(compiler, i + 1);
|
||||
i += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skip this instruction and its arguments.
|
||||
i += 1 + getNumArguments(compiler->bytecode.data,
|
||||
compiler->constants.data, i);
|
||||
i += 1 + getNumArguments(compiler->fn->code.data,
|
||||
compiler->fn->constants.data, i);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3161,7 +3115,7 @@ static bool method(Compiler* compiler, Variable classVariable)
|
||||
|
||||
// We don't need the function we started compiling in the parameter list
|
||||
// any more.
|
||||
abortCompiler(&methodCompiler);
|
||||
methodCompiler.parser->vm->compiler = methodCompiler.parent;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -3261,7 +3215,7 @@ static void classDefinition(Compiler* compiler, bool isForeign)
|
||||
// Update the class with the number of fields.
|
||||
if (!isForeign)
|
||||
{
|
||||
compiler->bytecode.data[numFieldsInstruction] =
|
||||
compiler->fn->code.data[numFieldsInstruction] =
|
||||
(uint8_t)classCompiler.fields.count;
|
||||
}
|
||||
|
||||
@ -3441,7 +3395,7 @@ void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn)
|
||||
int ip = 0;
|
||||
for (;;)
|
||||
{
|
||||
Code instruction = (Code)fn->bytecode[ip++];
|
||||
Code instruction = (Code)fn->code.data[ip++];
|
||||
switch (instruction)
|
||||
{
|
||||
case CODE_LOAD_FIELD:
|
||||
@ -3451,7 +3405,7 @@ void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn)
|
||||
// Shift this class's fields down past the inherited ones. We don't
|
||||
// check for overflow here because we'll see if the number of fields
|
||||
// overflows when the subclass is created.
|
||||
fn->bytecode[ip++] += classObj->superclass->numFields;
|
||||
fn->code.data[ip++] += classObj->superclass->numFields;
|
||||
break;
|
||||
|
||||
case CODE_SUPER_0:
|
||||
@ -3476,18 +3430,18 @@ void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn)
|
||||
ip += 2;
|
||||
|
||||
// Fill in the constant slot with a reference to the superclass.
|
||||
int constant = (fn->bytecode[ip] << 8) | fn->bytecode[ip + 1];
|
||||
fn->constants[constant] = OBJ_VAL(classObj->superclass);
|
||||
int constant = (fn->code.data[ip] << 8) | fn->code.data[ip + 1];
|
||||
fn->constants.data[constant] = OBJ_VAL(classObj->superclass);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_CLOSURE:
|
||||
{
|
||||
// Bind the nested closure too.
|
||||
int constant = (fn->bytecode[ip] << 8) | fn->bytecode[ip + 1];
|
||||
wrenBindMethodCode(classObj, AS_FN(fn->constants[constant]));
|
||||
int constant = (fn->code.data[ip] << 8) | fn->code.data[ip + 1];
|
||||
wrenBindMethodCode(classObj, AS_FN(fn->constants.data[constant]));
|
||||
|
||||
ip += getNumArguments(fn->bytecode, fn->constants, ip - 1);
|
||||
ip += getNumArguments(fn->code.data, fn->constants.data, ip - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -3496,7 +3450,7 @@ void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn)
|
||||
|
||||
default:
|
||||
// Other instructions are unaffected, so just skip over them.
|
||||
ip += getNumArguments(fn->bytecode, fn->constants, ip - 1);
|
||||
ip += getNumArguments(fn->code.data, fn->constants.data, ip - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -3511,7 +3465,7 @@ void wrenMarkCompiler(WrenVM* vm, Compiler* compiler)
|
||||
// tracks the innermost one.
|
||||
do
|
||||
{
|
||||
wrenGrayBuffer(vm, &compiler->constants);
|
||||
wrenGrayObj(vm, (Obj*)compiler->fn);
|
||||
compiler = compiler->parent;
|
||||
}
|
||||
while (compiler != NULL);
|
||||
|
||||
@ -29,7 +29,7 @@ void wrenDebugPrintStackTrace(ObjFiber* fiber)
|
||||
if (fn->module->name == NULL) continue;
|
||||
|
||||
// -1 because IP has advanced past the instruction that it just executed.
|
||||
int line = fn->debug->sourceLines[frame->ip - fn->bytecode - 1];
|
||||
int line = fn->debug->sourceLines.data[frame->ip - fn->code.data - 1];
|
||||
fprintf(stderr, "[%s line %d] in %s\n",
|
||||
fn->module->name->value, line, fn->debug->name);
|
||||
}
|
||||
@ -95,10 +95,10 @@ void wrenDumpValue(Value value)
|
||||
static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
|
||||
{
|
||||
int start = i;
|
||||
uint8_t* bytecode = fn->bytecode;
|
||||
uint8_t* bytecode = fn->code.data;
|
||||
Code code = (Code)bytecode[i];
|
||||
|
||||
int line = fn->debug->sourceLines[i];
|
||||
int line = fn->debug->sourceLines.data[i];
|
||||
if (lastLine == NULL || *lastLine != line)
|
||||
{
|
||||
printf("%4d:", line);
|
||||
@ -124,7 +124,7 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
|
||||
{
|
||||
int constant = READ_SHORT();
|
||||
printf("%-16s %5d '", "CONSTANT", constant);
|
||||
wrenDumpValue(fn->constants[constant]);
|
||||
wrenDumpValue(fn->constants.data[constant]);
|
||||
printf("'\n");
|
||||
break;
|
||||
}
|
||||
@ -265,9 +265,9 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
|
||||
{
|
||||
int constant = READ_SHORT();
|
||||
printf("%-16s %5d ", "CLOSURE", constant);
|
||||
wrenDumpValue(fn->constants[constant]);
|
||||
wrenDumpValue(fn->constants.data[constant]);
|
||||
printf(" ");
|
||||
ObjFn* loadedFn = AS_FN(fn->constants[constant]);
|
||||
ObjFn* loadedFn = AS_FN(fn->constants.data[constant]);
|
||||
for (int j = 0; j < loadedFn->numUpvalues; j++)
|
||||
{
|
||||
int isLocal = READ_BYTE();
|
||||
|
||||
@ -234,54 +234,33 @@ ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size)
|
||||
return object;
|
||||
}
|
||||
|
||||
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module,
|
||||
const Value* constants, int numConstants,
|
||||
int numUpvalues, int maxSlots, int arity,
|
||||
uint8_t* bytecode, int bytecodeLength,
|
||||
const char* debugName, int debugNameLength,
|
||||
int* sourceLines)
|
||||
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots)
|
||||
{
|
||||
// Allocate these before the function in case they trigger a GC which would
|
||||
// free the function.
|
||||
Value* copiedConstants = NULL;
|
||||
if (numConstants > 0)
|
||||
{
|
||||
copiedConstants = ALLOCATE_ARRAY(vm, Value, numConstants);
|
||||
for (int i = 0; i < numConstants; i++)
|
||||
{
|
||||
copiedConstants[i] = constants[i];
|
||||
}
|
||||
}
|
||||
|
||||
FnDebug* debug = ALLOCATE(vm, FnDebug);
|
||||
|
||||
// Copy the function's name.
|
||||
debug->name = ALLOCATE_ARRAY(vm, char, debugNameLength + 1);
|
||||
memcpy(debug->name, debugName, debugNameLength);
|
||||
debug->name[debugNameLength] = '\0';
|
||||
|
||||
debug->sourceLines = sourceLines;
|
||||
debug->name = NULL;
|
||||
wrenIntBufferInit(&debug->sourceLines);
|
||||
|
||||
ObjFn* fn = ALLOCATE(vm, ObjFn);
|
||||
initObj(vm, &fn->obj, OBJ_FN, vm->fnClass);
|
||||
|
||||
// TODO: Should eventually copy this instead of taking ownership. When the
|
||||
// compiler grows this, its capacity will often exceed the actual used size.
|
||||
// Copying to an exact-sized buffer will save a bit of memory. I tried doing
|
||||
// this, but it made the "for" benchmark ~15% slower for some unknown reason.
|
||||
fn->bytecode = bytecode;
|
||||
fn->constants = copiedConstants;
|
||||
|
||||
wrenValueBufferInit(&fn->constants);
|
||||
wrenByteBufferInit(&fn->code);
|
||||
fn->module = module;
|
||||
fn->maxSlots = maxSlots;
|
||||
fn->numUpvalues = numUpvalues;
|
||||
fn->numConstants = numConstants;
|
||||
fn->arity = arity;
|
||||
fn->bytecodeLength = bytecodeLength;
|
||||
fn->numUpvalues = 0;
|
||||
fn->arity = 0;
|
||||
fn->debug = debug;
|
||||
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length)
|
||||
{
|
||||
fn->debug->name = ALLOCATE_ARRAY(vm, char, length + 1);
|
||||
memcpy(fn->debug->name, name, length);
|
||||
fn->debug->name[length] = '\0';
|
||||
}
|
||||
|
||||
Value wrenNewInstance(WrenVM* vm, ObjClass* classObj)
|
||||
{
|
||||
ObjInstance* instance = ALLOCATE_FLEX(vm, ObjInstance,
|
||||
@ -1016,18 +995,15 @@ static void blackenFiber(WrenVM* vm, ObjFiber* fiber)
|
||||
static void blackenFn(WrenVM* vm, ObjFn* fn)
|
||||
{
|
||||
// Mark the constants.
|
||||
for (int i = 0; i < fn->numConstants; i++)
|
||||
{
|
||||
wrenGrayValue(vm, fn->constants[i]);
|
||||
}
|
||||
wrenGrayBuffer(vm, &fn->constants);
|
||||
|
||||
// Keep track of how much memory is still in use.
|
||||
vm->bytesAllocated += sizeof(ObjFn);
|
||||
vm->bytesAllocated += sizeof(uint8_t) * fn->bytecodeLength;
|
||||
vm->bytesAllocated += sizeof(Value) * fn->numConstants;
|
||||
|
||||
vm->bytesAllocated += sizeof(uint8_t) * fn->code.capacity;
|
||||
vm->bytesAllocated += sizeof(Value) * fn->constants.capacity;
|
||||
|
||||
// The debug line number buffer.
|
||||
vm->bytesAllocated += sizeof(int) * fn->bytecodeLength;
|
||||
vm->bytesAllocated += sizeof(int) * fn->code.capacity;
|
||||
// TODO: What about the function name?
|
||||
}
|
||||
|
||||
@ -1179,10 +1155,10 @@ void wrenFreeObj(WrenVM* vm, Obj* obj)
|
||||
case OBJ_FN:
|
||||
{
|
||||
ObjFn* fn = (ObjFn*)obj;
|
||||
DEALLOCATE(vm, fn->constants);
|
||||
DEALLOCATE(vm, fn->bytecode);
|
||||
wrenValueBufferClear(vm, &fn->constants);
|
||||
wrenByteBufferClear(vm, &fn->code);
|
||||
wrenIntBufferClear(vm, &fn->debug->sourceLines);
|
||||
DEALLOCATE(vm, fn->debug->name);
|
||||
DEALLOCATE(vm, fn->debug->sourceLines);
|
||||
DEALLOCATE(vm, fn->debug);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -269,7 +269,7 @@ typedef struct
|
||||
// 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;
|
||||
IntBuffer sourceLines;
|
||||
} FnDebug;
|
||||
|
||||
// A loaded module and the top-level variables it defines.
|
||||
@ -298,23 +298,19 @@ typedef struct
|
||||
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;
|
||||
|
||||
|
||||
ByteBuffer code;
|
||||
ValueBuffer constants;
|
||||
|
||||
// The module where this function was defined.
|
||||
ObjModule* module;
|
||||
|
||||
// The maximum number of stack slots this function may use.
|
||||
int maxSlots;
|
||||
|
||||
// The number of upvalues this function closes over.
|
||||
int numUpvalues;
|
||||
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.
|
||||
@ -657,7 +653,7 @@ static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber,
|
||||
CallFrame* frame = &fiber->frames[fiber->numFrames++];
|
||||
frame->stackStart = stackStart;
|
||||
frame->fn = function;
|
||||
frame->ip = wrenUnwrapClosure(frame->fn)->bytecode;
|
||||
frame->ip = wrenUnwrapClosure(frame->fn)->code.data;
|
||||
}
|
||||
|
||||
// Ensures [fiber]'s stack has at least [needed] slots.
|
||||
@ -665,16 +661,11 @@ void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed);
|
||||
|
||||
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size);
|
||||
|
||||
// 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,
|
||||
const Value* constants, int numConstants,
|
||||
int numUpvalues, int maxSlots, int arity,
|
||||
uint8_t* bytecode, int bytecodeLength,
|
||||
const char* debugName, int debugNameLength,
|
||||
int* sourceLines);
|
||||
// Creates a new empty function. Before being used, it must have code,
|
||||
// constants, etc. added to it.
|
||||
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots);
|
||||
|
||||
void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length);
|
||||
|
||||
// Creates a new instance of the given [classObj].
|
||||
Value wrenNewInstance(WrenVM* vm, ObjClass* classObj);
|
||||
|
||||
@ -805,7 +805,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
|
||||
DISPATCH();
|
||||
|
||||
CASE_CODE(CONSTANT):
|
||||
PUSH(fn->constants[READ_SHORT()]);
|
||||
PUSH(fn->constants.data[READ_SHORT()]);
|
||||
DISPATCH();
|
||||
|
||||
{
|
||||
@ -876,7 +876,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
|
||||
args = fiber->stackTop - numArgs;
|
||||
|
||||
// The superclass is stored in a constant.
|
||||
classObj = AS_CLASS(fn->constants[READ_SHORT()]);
|
||||
classObj = AS_CLASS(fn->constants.data[READ_SHORT()]);
|
||||
goto completeCall;
|
||||
|
||||
completeCall:
|
||||
@ -1110,7 +1110,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
|
||||
|
||||
CASE_CODE(CLOSURE):
|
||||
{
|
||||
ObjFn* prototype = AS_FN(fn->constants[READ_SHORT()]);
|
||||
ObjFn* prototype = AS_FN(fn->constants.data[READ_SHORT()]);
|
||||
|
||||
ASSERT(prototype->numUpvalues > 0,
|
||||
"Should not create closure for functions that don't need it.");
|
||||
@ -1204,21 +1204,21 @@ WrenValue* wrenMakeCallHandle(WrenVM* vm, const char* signature)
|
||||
|
||||
// Create a little stub function that assumes the arguments are on the stack
|
||||
// and calls the method.
|
||||
uint8_t* bytecode = ALLOCATE_ARRAY(vm, uint8_t, 5);
|
||||
bytecode[0] = (uint8_t)(CODE_CALL_0 + numParams);
|
||||
bytecode[1] = (method >> 8) & 0xff;
|
||||
bytecode[2] = method & 0xff;
|
||||
bytecode[3] = CODE_RETURN;
|
||||
bytecode[4] = CODE_END;
|
||||
ObjFn* fn = wrenNewFunction(vm, NULL, numParams + 1);
|
||||
|
||||
int* debugLines = ALLOCATE_ARRAY(vm, int, 5);
|
||||
memset(debugLines, 1, 5);
|
||||
// Wrap the function in a handle. Do this here so it doesn't get collected as
|
||||
// we fill it in.
|
||||
WrenValue* value = wrenCaptureValue(vm, OBJ_VAL(fn));
|
||||
|
||||
ObjFn* fn = wrenNewFunction(vm, NULL, NULL, 0, 0, numParams + 1, 0, bytecode,
|
||||
5, signature, signatureLength, debugLines);
|
||||
wrenByteBufferWrite(vm, &fn->code, (uint8_t)(CODE_CALL_0 + numParams));
|
||||
wrenByteBufferWrite(vm, &fn->code, (method >> 8) & 0xff);
|
||||
wrenByteBufferWrite(vm, &fn->code, method & 0xff);
|
||||
wrenByteBufferWrite(vm, &fn->code, CODE_RETURN);
|
||||
wrenByteBufferWrite(vm, &fn->code, CODE_END);
|
||||
wrenIntBufferFill(vm, &fn->debug->sourceLines, 0, 5);
|
||||
wrenFunctionBindName(vm, fn, signature, signatureLength);
|
||||
|
||||
// Wrap the function in a handle.
|
||||
return wrenCaptureValue(vm, OBJ_VAL(fn));
|
||||
return value;
|
||||
}
|
||||
|
||||
WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method)
|
||||
|
||||
Reference in New Issue
Block a user