Grow the stack as needed.
This is untuned for performance, but seems to be doing the right thing.
This commit is contained in:
parent
c7cd1fb1ac
commit
e7d10218db
@ -324,6 +324,15 @@ struct sCompiler
|
|||||||
int scopeDepth;
|
int scopeDepth;
|
||||||
|
|
||||||
// The current number of slots (locals and temporaries) in use.
|
// The current number of slots (locals and temporaries) in use.
|
||||||
|
//
|
||||||
|
// We use this and maxSlots to track the maximum number of additional slots
|
||||||
|
// a function may need while executing. When the function is called, the
|
||||||
|
// fiber will check to ensure its stack has enough room to cover that worst
|
||||||
|
// case and grow the stack if needed.
|
||||||
|
//
|
||||||
|
// This value here doesn't include parameters to the function. Since those
|
||||||
|
// are already pushed onto the stack by the caller and tracked there, we
|
||||||
|
// don't need to double count them here.
|
||||||
int numSlots;
|
int numSlots;
|
||||||
|
|
||||||
// The maximum number of slots (locals and temporaries) in use at one time in
|
// The maximum number of slots (locals and temporaries) in use at one time in
|
||||||
@ -1611,11 +1620,6 @@ static bool finishBlock(Compiler* compiler)
|
|||||||
// In that case, this adds the code to ensure it returns `this`.
|
// In that case, this adds the code to ensure it returns `this`.
|
||||||
static void finishBody(Compiler* compiler, bool isInitializer)
|
static void finishBody(Compiler* compiler, bool isInitializer)
|
||||||
{
|
{
|
||||||
// Now that the parameter list has been compiled, we know how many slots they
|
|
||||||
// use.
|
|
||||||
compiler->numSlots = compiler->numLocals;
|
|
||||||
compiler->maxSlots = compiler->numLocals;
|
|
||||||
|
|
||||||
bool isExpressionBody = finishBlock(compiler);
|
bool isExpressionBody = finishBlock(compiler);
|
||||||
|
|
||||||
if (isInitializer)
|
if (isInitializer)
|
||||||
@ -3075,8 +3079,6 @@ static void createConstructor(Compiler* compiler, Signature* signature,
|
|||||||
{
|
{
|
||||||
Compiler methodCompiler;
|
Compiler methodCompiler;
|
||||||
initCompiler(&methodCompiler, compiler->parser, compiler, false);
|
initCompiler(&methodCompiler, compiler->parser, compiler, false);
|
||||||
methodCompiler.numSlots = signature->arity + 1;
|
|
||||||
methodCompiler.maxSlots = methodCompiler.numSlots;
|
|
||||||
|
|
||||||
// Allocate the instance.
|
// Allocate the instance.
|
||||||
emitOp(&methodCompiler, compiler->enclosingClass->isForeign
|
emitOp(&methodCompiler, compiler->enclosingClass->isForeign
|
||||||
|
|||||||
@ -18,7 +18,7 @@ void wrenDebugPrintStackTrace(ObjFiber* fiber)
|
|||||||
for (int i = fiber->numFrames - 1; i >= 0; i--)
|
for (int i = fiber->numFrames - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
CallFrame* frame = &fiber->frames[i];
|
CallFrame* frame = &fiber->frames[i];
|
||||||
ObjFn* fn = wrenGetFrameFunction(frame);
|
ObjFn* fn = wrenUpwrapClosure(frame->fn);
|
||||||
|
|
||||||
// The built-in core module has no name. We explicitly omit it from stack
|
// The built-in core module has no name. We explicitly omit it from stack
|
||||||
// traces since we don't want to highlight to a user the implementation
|
// traces since we don't want to highlight to a user the implementation
|
||||||
|
|||||||
@ -175,3 +175,17 @@ int wrenUtf8DecodeNumBytes(uint8_t byte)
|
|||||||
if ((byte & 0xe0) == 0xc0) return 2;
|
if ((byte & 0xe0) == 0xc0) return 2;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// From: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2Float
|
||||||
|
int wrenPowerOf2Ceil(int n)
|
||||||
|
{
|
||||||
|
n--;
|
||||||
|
n |= n >> 1;
|
||||||
|
n |= n >> 2;
|
||||||
|
n |= n >> 4;
|
||||||
|
n |= n >> 8;
|
||||||
|
n |= n >> 16;
|
||||||
|
n++;
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
@ -49,12 +49,7 @@ typedef struct {
|
|||||||
{ \
|
{ \
|
||||||
if (buffer->capacity < buffer->count + count) \
|
if (buffer->capacity < buffer->count + count) \
|
||||||
{ \
|
{ \
|
||||||
int capacity = buffer->capacity; \
|
int capacity = wrenPowerOf2Ceil(buffer->count + count); \
|
||||||
while (capacity < buffer->count + count) \
|
|
||||||
{ \
|
|
||||||
capacity = capacity == 0 ? 8 : capacity * 2; \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
buffer->data = (type*)wrenReallocate(vm, buffer->data, \
|
buffer->data = (type*)wrenReallocate(vm, buffer->data, \
|
||||||
buffer->capacity * sizeof(type), capacity * sizeof(type)); \
|
buffer->capacity * sizeof(type), capacity * sizeof(type)); \
|
||||||
buffer->capacity = capacity; \
|
buffer->capacity = capacity; \
|
||||||
@ -120,4 +115,7 @@ int wrenUtf8Decode(const uint8_t* bytes, uint32_t length);
|
|||||||
// returns 0.
|
// returns 0.
|
||||||
int wrenUtf8DecodeNumBytes(uint8_t byte);
|
int wrenUtf8DecodeNumBytes(uint8_t byte);
|
||||||
|
|
||||||
|
// Returns the smallest power of two that is equal to or greater than [n].
|
||||||
|
int wrenPowerOf2Ceil(int n);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -147,14 +147,19 @@ ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn)
|
|||||||
|
|
||||||
ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn)
|
ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn)
|
||||||
{
|
{
|
||||||
// Allocate the call frames before the fiber in case it triggers a GC.
|
// Allocate the arrays before the fiber in case it triggers a GC.
|
||||||
CallFrame* frames = ALLOCATE_ARRAY(vm, CallFrame, INITIAL_CALL_FRAMES);
|
CallFrame* frames = ALLOCATE_ARRAY(vm, CallFrame, INITIAL_CALL_FRAMES);
|
||||||
|
|
||||||
|
int stackCapacity = wrenPowerOf2Ceil(wrenUpwrapClosure(fn)->maxSlots);
|
||||||
|
Value* stack = ALLOCATE_ARRAY(vm, Value, stackCapacity);
|
||||||
|
|
||||||
ObjFiber* fiber = ALLOCATE(vm, ObjFiber);
|
ObjFiber* fiber = ALLOCATE(vm, ObjFiber);
|
||||||
initObj(vm, &fiber->obj, OBJ_FIBER, vm->fiberClass);
|
initObj(vm, &fiber->obj, OBJ_FIBER, vm->fiberClass);
|
||||||
fiber->id = vm->nextFiberId++;
|
fiber->id = vm->nextFiberId++;
|
||||||
fiber->frames = frames;
|
fiber->frames = frames;
|
||||||
fiber->frameCapacity = INITIAL_CALL_FRAMES;
|
fiber->frameCapacity = INITIAL_CALL_FRAMES;
|
||||||
|
fiber->stack = stack;
|
||||||
|
fiber->stackCapacity = stackCapacity;
|
||||||
wrenResetFiber(vm, fiber, fn);
|
wrenResetFiber(vm, fiber, fn);
|
||||||
|
|
||||||
return fiber;
|
return fiber;
|
||||||
@ -960,6 +965,8 @@ static void blackenFiber(WrenVM* vm, ObjFiber* fiber)
|
|||||||
|
|
||||||
// Keep track of how much memory is still in use.
|
// Keep track of how much memory is still in use.
|
||||||
vm->bytesAllocated += sizeof(ObjFiber);
|
vm->bytesAllocated += sizeof(ObjFiber);
|
||||||
|
vm->bytesAllocated += fiber->frameCapacity * sizeof(CallFrame);
|
||||||
|
vm->bytesAllocated += fiber->stackCapacity * sizeof(Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void blackenFn(WrenVM* vm, ObjFn* fn)
|
static void blackenFn(WrenVM* vm, ObjFn* fn)
|
||||||
@ -1117,6 +1124,14 @@ void wrenFreeObj(WrenVM* vm, Obj* obj)
|
|||||||
wrenMethodBufferClear(vm, &((ObjClass*)obj)->methods);
|
wrenMethodBufferClear(vm, &((ObjClass*)obj)->methods);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case OBJ_FIBER:
|
||||||
|
{
|
||||||
|
ObjFiber* fiber = (ObjFiber*)obj;
|
||||||
|
DEALLOCATE(vm, fiber->frames);
|
||||||
|
DEALLOCATE(vm, fiber->stack);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case OBJ_FN:
|
case OBJ_FN:
|
||||||
{
|
{
|
||||||
ObjFn* fn = (ObjFn*)obj;
|
ObjFn* fn = (ObjFn*)obj;
|
||||||
@ -1147,7 +1162,6 @@ void wrenFreeObj(WrenVM* vm, Obj* obj)
|
|||||||
|
|
||||||
case OBJ_STRING:
|
case OBJ_STRING:
|
||||||
case OBJ_CLOSURE:
|
case OBJ_CLOSURE:
|
||||||
case OBJ_FIBER:
|
|
||||||
case OBJ_INSTANCE:
|
case OBJ_INSTANCE:
|
||||||
case OBJ_RANGE:
|
case OBJ_RANGE:
|
||||||
case OBJ_UPVALUE:
|
case OBJ_UPVALUE:
|
||||||
|
|||||||
@ -41,9 +41,6 @@
|
|||||||
// The representation is controlled by the `WREN_NAN_TAGGING` define. If that's
|
// The representation is controlled by the `WREN_NAN_TAGGING` define. If that's
|
||||||
// defined, Nan tagging is used.
|
// defined, Nan tagging is used.
|
||||||
|
|
||||||
// TODO: Make this externally controllable.
|
|
||||||
#define STACK_SIZE 1024
|
|
||||||
|
|
||||||
// These macros cast a Value to one of the specific value types. These do *not*
|
// These macros cast a Value to one of the specific value types. These do *not*
|
||||||
// perform any validation, so must only be used after the Value has been
|
// perform any validation, so must only be used after the Value has been
|
||||||
// ensured to be the right type.
|
// ensured to be the right type.
|
||||||
@ -210,9 +207,18 @@ typedef struct
|
|||||||
typedef struct sObjFiber
|
typedef struct sObjFiber
|
||||||
{
|
{
|
||||||
Obj obj;
|
Obj obj;
|
||||||
Value stack[STACK_SIZE];
|
|
||||||
|
// The stack of value slots. This is used for holding local variables and
|
||||||
|
// temporaries while the fiber is executing. It heap-allocated and grown as
|
||||||
|
// needed.
|
||||||
|
Value* stack;
|
||||||
|
|
||||||
|
// A pointer to one past the top-most value on the stack.
|
||||||
Value* stackTop;
|
Value* stackTop;
|
||||||
|
|
||||||
|
// The number of allocated slots in the stack array.
|
||||||
|
int stackCapacity;
|
||||||
|
|
||||||
// The stack of call frames. This is a dynamic array that grows as needed but
|
// The stack of call frames. This is a dynamic array that grows as needed but
|
||||||
// never shrinks.
|
// never shrinks.
|
||||||
CallFrame* frames;
|
CallFrame* frames;
|
||||||
@ -629,14 +635,16 @@ ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn);
|
|||||||
// Resets [fiber] back to an initial state where it is ready to invoke [fn].
|
// Resets [fiber] back to an initial state where it is ready to invoke [fn].
|
||||||
void wrenResetFiber(WrenVM* vm, ObjFiber* fiber, Obj* fn);
|
void wrenResetFiber(WrenVM* vm, ObjFiber* fiber, Obj* fn);
|
||||||
|
|
||||||
static inline ObjFn* wrenGetFrameFunction(CallFrame* frame)
|
// Returns the base ObjFn backing an ObjClosure, or [function] if it already is
|
||||||
|
// a raw ObjFn.
|
||||||
|
static inline ObjFn* wrenUpwrapClosure(Obj* function)
|
||||||
{
|
{
|
||||||
switch (frame->fn->type)
|
switch (function->type)
|
||||||
{
|
{
|
||||||
case OBJ_FN: return (ObjFn*)frame->fn;
|
case OBJ_FN: return (ObjFn*)function;
|
||||||
case OBJ_CLOSURE: return ((ObjClosure*)frame->fn)->fn;
|
case OBJ_CLOSURE: return ((ObjClosure*)function)->fn;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
ASSERT(false, "Function should be an ObjFn or ObjClosure.");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -652,7 +660,7 @@ static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber,
|
|||||||
CallFrame* frame = &fiber->frames[fiber->numFrames++];
|
CallFrame* frame = &fiber->frames[fiber->numFrames++];
|
||||||
frame->stackStart = stackStart;
|
frame->stackStart = stackStart;
|
||||||
frame->fn = function;
|
frame->fn = function;
|
||||||
frame->ip = wrenGetFrameFunction(frame)->bytecode;
|
frame->ip = wrenUpwrapClosure(frame->fn)->bytecode;
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size);
|
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size);
|
||||||
|
|||||||
@ -432,6 +432,7 @@ static bool checkArity(WrenVM* vm, Value value, int numArgs)
|
|||||||
static inline void callFunction(
|
static inline void callFunction(
|
||||||
WrenVM* vm, ObjFiber* fiber, Obj* function, int numArgs)
|
WrenVM* vm, ObjFiber* fiber, Obj* function, int numArgs)
|
||||||
{
|
{
|
||||||
|
// Grow the call frame array if needed.
|
||||||
if (fiber->numFrames + 1 > fiber->frameCapacity)
|
if (fiber->numFrames + 1 > fiber->frameCapacity)
|
||||||
{
|
{
|
||||||
int max = fiber->frameCapacity * 2;
|
int max = fiber->frameCapacity * 2;
|
||||||
@ -440,9 +441,44 @@ static inline void callFunction(
|
|||||||
sizeof(CallFrame) * max);
|
sizeof(CallFrame) * max);
|
||||||
fiber->frameCapacity = max;
|
fiber->frameCapacity = max;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Check for stack overflow. We handle the call frame array growing,
|
// Grow the stack if needed.
|
||||||
// but not the stack itself.
|
int stackSize = (int)(fiber->stackTop - fiber->stack);
|
||||||
|
int needed = stackSize + wrenUpwrapClosure(function)->maxSlots;
|
||||||
|
|
||||||
|
if (fiber->stackCapacity < needed)
|
||||||
|
{
|
||||||
|
int capacity = wrenPowerOf2Ceil(needed);
|
||||||
|
|
||||||
|
Value* oldStack = fiber->stack;
|
||||||
|
fiber->stack = (Value*)wrenReallocate(vm, fiber->stack,
|
||||||
|
sizeof(Value) * fiber->stackCapacity,
|
||||||
|
sizeof(Value) * capacity);
|
||||||
|
fiber->stackCapacity = capacity;
|
||||||
|
|
||||||
|
// If the reallocation moves the stack, then we need to shift every pointer
|
||||||
|
// into the stack to point to its new location.
|
||||||
|
if (fiber->stack != oldStack)
|
||||||
|
{
|
||||||
|
// Top of the stack.
|
||||||
|
long offset = fiber->stack - oldStack;
|
||||||
|
fiber->stackTop += offset;
|
||||||
|
|
||||||
|
// Stack pointer for each call frame.
|
||||||
|
for (int i = 0; i < fiber->numFrames; i++)
|
||||||
|
{
|
||||||
|
fiber->frames[i].stackStart += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open upvalues.
|
||||||
|
for (ObjUpvalue* upvalue = fiber->openUpvalues;
|
||||||
|
upvalue != NULL;
|
||||||
|
upvalue = upvalue->next)
|
||||||
|
{
|
||||||
|
upvalue->value += offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
wrenAppendCallFrame(vm, fiber, function, fiber->stackTop - numArgs);
|
wrenAppendCallFrame(vm, fiber, function, fiber->stackTop - numArgs);
|
||||||
}
|
}
|
||||||
@ -744,7 +780,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
|
|||||||
frame = &fiber->frames[fiber->numFrames - 1]; \
|
frame = &fiber->frames[fiber->numFrames - 1]; \
|
||||||
stackStart = frame->stackStart; \
|
stackStart = frame->stackStart; \
|
||||||
ip = frame->ip; \
|
ip = frame->ip; \
|
||||||
fn = wrenGetFrameFunction(frame);
|
fn = wrenUpwrapClosure(frame->fn);
|
||||||
|
|
||||||
// Terminates the current fiber with error string [error]. If another calling
|
// Terminates the current fiber with error string [error]. If another calling
|
||||||
// fiber is willing to catch the error, transfers control to it, otherwise
|
// fiber is willing to catch the error, transfers control to it, otherwise
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user