wrap up closure implementation from chapter 25

This commit is contained in:
K. Lange 2020-12-27 13:02:26 +09:00
parent 076da0bc1e
commit 78022fb701
9 changed files with 263 additions and 35 deletions

View File

@ -43,6 +43,13 @@ typedef enum {
OP_JUMP,
OP_LOOP,
OP_CALL,
OP_CLOSURE,
OP_CLOSURE_LONG,
OP_GET_UPVALUE,
OP_GET_UPVALUE_LONG,
OP_SET_UPVALUE,
OP_SET_UPVALUE_LONG,
OP_CLOSE_UPVALUE,
} KrkOpCode;
/**

View File

@ -40,8 +40,14 @@ typedef struct {
typedef struct {
KrkToken name;
ssize_t depth;
int isCaptured;
} Local;
typedef struct {
size_t index;
int isLocal;
} Upvalue;
typedef enum {
TYPE_FUNCTION,
TYPE_MODULE,
@ -55,6 +61,7 @@ typedef struct Compiler {
Local locals[MAX_LOCALS];
size_t localCount;
size_t scopeDepth;
Upvalue upvalues[MAX_LOCALS];
} Compiler;
Parser parser;
@ -64,6 +71,9 @@ static KrkChunk * currentChunk() {
return &current->function->chunk;
}
#define EMIT_CONSTANT_OP(opc, arg) do { if (arg < 256) { emitBytes(opc, arg); } \
else { emitBytes(opc ## _LONG, arg >> 16); emitBytes(arg >> 8, arg); } } while (0)
static void initCompiler(Compiler * compiler, FunctionType type) {
compiler->enclosing = current;
compiler->function = NULL;
@ -81,6 +91,7 @@ static void initCompiler(Compiler * compiler, FunctionType type) {
local->depth = 0;
local->name.start = "";
local->name.length = 0;
local->isCaptured = 0;
}
static void parsePrecedence(Precedence precedence);
@ -327,7 +338,11 @@ static void endScope() {
current->scopeDepth--;
while (current->localCount > 0 &&
current->locals[current->localCount - 1].depth > current->scopeDepth) {
emitByte(OP_POP);
if (current->locals[current->localCount - 1].isCaptured) {
emitByte(OP_CLOSE_UPVALUE);
} else {
emitByte(OP_POP);
}
current->localCount--;
}
}
@ -377,7 +392,14 @@ static void function(FunctionType type, int blockWidth) {
block(blockWidth);
KrkFunction * function = endCompiler();
emitConstant(OBJECT_VAL(function));
size_t ind = krk_addConstant(currentChunk(), OBJECT_VAL(function));
EMIT_CONSTANT_OP(OP_CLOSURE, ind);
for (size_t i = 0; i < function->upvalueCount; ++i) {
/* TODO: if the maximum count changes, fix the sizes for this */
emitByte(compiler.upvalues[i].isLocal ? 1 : 0);
emitByte(compiler.upvalues[i].index);
}
}
static void markInitialized() {
@ -594,28 +616,57 @@ static void codepoint(int canAssign) {
}
*/
#define EMIT_CONSTANT_OP(opc, arg) do { if (arg < 256) { emitBytes(opc, arg); } \
else { emitBytes(opc ## _LONG, arg >> 16); emitBytes(arg >> 8, arg); } } while (0)
static size_t addUpvalue(Compiler * compiler, ssize_t index, int isLocal) {
size_t upvalueCount = compiler->function->upvalueCount;
for (size_t i = 0; i < upvalueCount; ++i) {
Upvalue * upvalue = &compiler->upvalues[i];
if (upvalue->index == index && upvalue->isLocal == isLocal) {
return i;
}
}
if (upvalueCount == MAX_LOCALS) {
error("Too many closure variables in function.");
return 0;
}
compiler->upvalues[upvalueCount].isLocal = isLocal;
compiler->upvalues[upvalueCount].index = index;
return compiler->function->upvalueCount++;
}
static ssize_t resolveUpvalue(Compiler * compiler, KrkToken * name) {
if (compiler->enclosing == NULL) return -1;
ssize_t local = resolveLocal(compiler->enclosing, name);
if (local != -1) {
compiler->enclosing->locals[local].isCaptured = 1;
return addUpvalue(compiler, local, 1);
}
ssize_t upvalue = resolveUpvalue(compiler->enclosing, name);
if (upvalue != -1) {
return addUpvalue(compiler, upvalue, 0);
}
return -1;
}
#define DO_VARIABLE(opset,opget) do { \
if (canAssign && match(TOKEN_EQUAL)) { \
expression(); \
EMIT_CONSTANT_OP(opset, arg); \
} else { \
EMIT_CONSTANT_OP(opget, arg); \
} } while (0)
static void namedVariable(KrkToken name, int canAssign) {
ssize_t arg = resolveLocal(current, &name);
if (arg != -1) {
if (canAssign && match(TOKEN_EQUAL)) {
expression();
EMIT_CONSTANT_OP(OP_SET_LOCAL, arg);
} else {
EMIT_CONSTANT_OP(OP_GET_LOCAL, arg);
}
DO_VARIABLE(OP_SET_LOCAL, OP_GET_LOCAL);
} else if ((arg = resolveUpvalue(current, &name)) != -1) {
DO_VARIABLE(OP_SET_UPVALUE, OP_GET_UPVALUE);
} else {
arg = identifierConstant(&name);
if (canAssign && match(TOKEN_EQUAL)) {
expression();
EMIT_CONSTANT_OP(OP_SET_GLOBAL, arg);
} else {
EMIT_CONSTANT_OP(OP_GET_GLOBAL, arg);
}
DO_VARIABLE(OP_SET_GLOBAL, OP_GET_GLOBAL);
}
}
#undef DO_VARIABLE
static void variable(int canAssign) {
namedVariable(parser.previous, canAssign);
@ -722,6 +773,7 @@ static void addLocal(KrkToken name) {
Local * local = &current->locals[current->localCount++];
local->name = name;
local->depth = -1;
local->isCaptured = 0;
}
static void declareVariable() {

25
debug.c
View File

@ -11,16 +11,18 @@ void krk_disassembleChunk(KrkChunk * chunk, const char * name) {
}
#define SIMPLE(opc) case opc: fprintf(stderr, "%s\n", #opc); return offset + 1;
#define CONSTANT(opc) case opc: { size_t constant = chunk->code[offset + 1]; \
#define CONSTANT(opc,more) case opc: { size_t constant = chunk->code[offset + 1]; \
fprintf(stderr, "%-16s %4d '", #opc, (int)constant); \
krk_printValue(stderr, chunk->constants.values[constant]); \
fprintf(stderr,"' (type=%s)\n", typeName(chunk->constants.values[constant])); \
more; \
return offset + 2; } \
case opc ## _LONG: { size_t constant = (chunk->code[offset + 1] << 16) | \
(chunk->code[offset + 2] << 8) | (chunk->code[offset + 3]); \
fprintf(stderr, "%-16s %4d '", #opc "_LONG", (int)constant); \
krk_printValue(stderr, chunk->constants.values[constant]); \
fprintf(stderr,"' (type=%s)\n", typeName(chunk->constants.values[constant])); \
more; \
return offset + 4; }
#define OPERANDB(opc) case opc: { uint32_t operand = chunk->code[offset + 1]; \
fprintf(stderr, "%-16s %4d\n", #opc, (int)operand); \
@ -35,6 +37,15 @@ void krk_disassembleChunk(KrkChunk * chunk, const char * name) {
fprintf(stderr, "%-16s %4d -> %d\n", #opc, (int)offset, (int)(offset + 3 sign jump)); \
return offset + 3; }
#define CLOSURE_MORE \
KrkFunction * function = AS_FUNCTION(chunk->constants.values[constant]); \
for (size_t j = 0; j < function->upvalueCount; ++j) { \
int isLocal = chunk->code[offset++]; \
int index = chunk->code[offset++]; \
fprintf(stderr, "%04d | %s %d\n", \
(int)offset - 2, isLocal ? "local" : "upvalue", index); \
}
size_t krk_disassembleInstruction(KrkChunk * chunk, size_t offset) {
fprintf(stderr, "%04u ", (unsigned int)offset);
if (offset > 0 && chunk->lines[offset] == chunk->lines[offset - 1]) {
@ -60,17 +71,21 @@ size_t krk_disassembleInstruction(KrkChunk * chunk, size_t offset) {
SIMPLE(OP_LESS)
SIMPLE(OP_PRINT)
SIMPLE(OP_POP)
CONSTANT(OP_DEFINE_GLOBAL)
CONSTANT(OP_CONSTANT)
CONSTANT(OP_GET_GLOBAL)
CONSTANT(OP_SET_GLOBAL)
CONSTANT(OP_DEFINE_GLOBAL,(void)0)
CONSTANT(OP_CONSTANT,(void)0)
CONSTANT(OP_GET_GLOBAL,(void)0)
CONSTANT(OP_SET_GLOBAL,(void)0)
CONSTANT(OP_CLOSURE, CLOSURE_MORE)
OPERANDL(OP_SET_LOCAL)
OPERANDL(OP_GET_LOCAL)
OPERANDL(OP_SET_UPVALUE)
OPERANDL(OP_GET_UPVALUE)
JUMP(OP_JUMP,+)
JUMP(OP_JUMP_IF_FALSE,+)
JUMP(OP_JUMP_IF_TRUE,+)
JUMP(OP_LOOP,-)
OPERANDB(OP_CALL)
SIMPLE(OP_CLOSE_UPVALUE)
default:
fprintf(stderr, "Unknown opcode: %02x\n", opcode);
return offset + 1;

View File

@ -29,6 +29,16 @@ static void freeObject(KrkObj * object) {
FREE(KrkNative, object);
break;
}
case OBJ_CLOSURE: {
KrkClosure * closure = (KrkClosure*)object;
FREE_ARRAY(KrkUpvalue*,closure->upvalues,closure->upvalueCount);
FREE(KrkClosure, object);
break;
}
case OBJ_UPVALUE: {
FREE(KrkUpvalue, object);
break;
}
}
}

View File

@ -70,12 +70,19 @@ void krk_printObject(FILE * f, KrkValue value) {
case OBJ_NATIVE:
fprintf(f, "<native bind>");
break;
case OBJ_CLOSURE:
fprintf(f, "<closure <def %s>>", AS_CLOSURE(value)->function->name->chars);
break;
case OBJ_UPVALUE:
fprintf(f, "<upvalue>");
break;
}
}
KrkFunction * newFunction() {
KrkFunction * function = ALLOCATE_OBJECT(KrkFunction, OBJ_FUNCTION);
function->arity = 0;
function->upvalueCount = 0;
function->name = NULL;
krk_initChunk(&function->chunk);
return function;
@ -86,3 +93,23 @@ KrkNative * newNative(NativeFn function) {
native->function = function;
return native;
}
KrkClosure * newClosure(KrkFunction * function) {
KrkUpvalue ** upvalues = ALLOCATE(KrkUpvalue*, function->upvalueCount);
for (size_t i = 0; i < function->upvalueCount; ++i) {
upvalues[i] = NULL;
}
KrkClosure * closure = ALLOCATE_OBJECT(KrkClosure, OBJ_CLOSURE);
closure->function = function;
closure->upvalues = upvalues;
closure->upvalueCount = function->upvalueCount;
return closure;
}
KrkUpvalue * newUpvalue(KrkValue * slot) {
KrkUpvalue * upvalue = ALLOCATE_OBJECT(KrkUpvalue, OBJ_UPVALUE);
upvalue->location = slot;
upvalue->next = NULL;
upvalue->closed = NONE_VAL();
return upvalue;
}

View File

@ -14,11 +14,15 @@
#define AS_FUNCTION(value) ((KrkFunction *)AS_OBJECT(value))
#define IS_NATIVE(value) isObjType(value, OBJ_NATIVE)
#define AS_NATIVE(value) (((KrkNative *)AS_OBJECT(value))->function)
#define IS_CLOSURE(value) isObjType(value, OBJ_CLOSURE)
#define AS_CLOSURE(value) ((KrkClosure *)AS_OBJECT(value))
typedef enum {
OBJ_FUNCTION,
OBJ_NATIVE,
OBJ_CLOSURE,
OBJ_STRING,
OBJ_UPVALUE,
} ObjType;
struct Obj {
@ -33,13 +37,29 @@ struct ObjString {
uint32_t hash;
};
typedef struct KrkUpvalue {
KrkObj obj;
KrkValue * location;
KrkValue closed;
struct KrkUpvalue * next;
} KrkUpvalue;
typedef struct {
KrkObj obj;
int arity;
size_t upvalueCount;
KrkChunk chunk;
KrkString * name;
} KrkFunction;
typedef struct {
KrkObj obj;
KrkFunction * function;
KrkUpvalue ** upvalues;
size_t upvalueCount;
} KrkClosure;
typedef KrkValue (*NativeFn)(int argCount, KrkValue* args);
typedef struct {
KrkObj obj;
@ -56,3 +76,7 @@ extern void krk_printObject(FILE * f, KrkValue value);
extern KrkFunction * newFunction();
extern KrkNative * newNative(NativeFn function);
extern KrkClosure * newClosure(KrkFunction * function);
extern KrkUpvalue * newUpvalue(KrkValue * slot);

View File

@ -64,3 +64,25 @@ result = sleep(0.5)
print "Call to sleep returned: " + result
function("something else")
if True:
let a = 1
def f():
print a
let b = 2
def g():
print b
let c = 3
def h():
print c
def outer():
let x = "outside"
def inner():
print x
return inner
print "Function is defined, creating it..."
let closure = outer()
print "And executing the result..."
closure()

96
vm.c
View File

@ -14,6 +14,7 @@ KrkVM vm;
static void resetStack() {
vm.stackTop = vm.stack;
vm.frameCount = 0;
vm.openUpvalues = NULL;
}
static void runtimeError(const char * fmt, ...) {
@ -25,7 +26,7 @@ static void runtimeError(const char * fmt, ...) {
for (int i = vm.frameCount - 1; i >= 0; i--) {
CallFrame * frame = &vm.frames[i];
KrkFunction * function = frame->function;
KrkFunction * function = frame->closure->function;
size_t instruction = frame->ip - function->chunk.code - 1;
fprintf(stderr, "[line %d] in ", (int)function->chunk.lines[instruction]);
if (function->name == NULL) {
@ -86,9 +87,9 @@ static KrkValue krk_sleep(int argc, KrkValue argv[]) {
return BOOLEAN_VAL(1);
}
static int call(KrkFunction * function, int argCount) {
if (argCount != function->arity) {
runtimeError("Wrong number of arguments (%d expected, got %d)", function->arity, argCount);
static int call(KrkClosure * closure, int argCount) {
if (argCount != closure->function->arity) {
runtimeError("Wrong number of arguments (%d expected, got %d)", closure->function->arity, argCount);
return 0;
}
if (vm.frameCount == FRAMES_MAX) {
@ -96,8 +97,8 @@ static int call(KrkFunction * function, int argCount) {
return 0;
}
CallFrame * frame = &vm.frames[vm.frameCount++];
frame->function = function;
frame->ip = function->chunk.code;
frame->closure = closure;
frame->ip = closure->function->chunk.code;
frame->slots = vm.stackTop - argCount - 1;
return 1;
}
@ -105,8 +106,8 @@ static int call(KrkFunction * function, int argCount) {
static int callValue(KrkValue callee, int argCount) {
if (IS_OBJECT(callee)) {
switch (OBJECT_TYPE(callee)) {
case OBJ_FUNCTION:
return call(AS_FUNCTION(callee), argCount);
case OBJ_CLOSURE:
return call(AS_CLOSURE(callee), argCount);
case OBJ_NATIVE: {
NativeFn native = AS_NATIVE(callee);
KrkValue result = native(argCount, vm.stackTop - argCount);
@ -122,6 +123,35 @@ static int callValue(KrkValue callee, int argCount) {
return 0;
}
static KrkUpvalue * captureUpvalue(KrkValue * local) {
KrkUpvalue * prevUpvalue = NULL;
KrkUpvalue * upvalue = vm.openUpvalues;
while (upvalue != NULL && upvalue->location > local) {
prevUpvalue = upvalue;
upvalue = upvalue->next;
}
if (upvalue && upvalue->location == local) {
return upvalue;
}
KrkUpvalue * createdUpvalue = newUpvalue(local);
createdUpvalue->next = upvalue;
if (prevUpvalue == NULL) {
vm.openUpvalues = createdUpvalue;
} else {
prevUpvalue->next = createdUpvalue;
}
return createdUpvalue;
}
static void closeUpvalues(KrkValue * last) {
while (vm.openUpvalues != NULL && vm.openUpvalues->location >= last) {
KrkUpvalue * upvalue = vm.openUpvalues;
upvalue->closed = *upvalue->location;
upvalue->location = &upvalue->closed;
vm.openUpvalues = upvalue->next;
}
}
void krk_initVM() {
resetStack();
vm.objects = NULL;
@ -151,6 +181,9 @@ const char * typeName(KrkValue value) {
if (value.type == VAL_FLOATING) return "Floating";
if (value.type == VAL_OBJECT) {
if (IS_STRING(value)) return "String";
if (IS_FUNCTION(value)) return "Function";
if (IS_NATIVE(value)) return "Native";
if (IS_CLOSURE(value)) return "Closure";
return "(Unspecified Object)";
}
return "???";
@ -233,7 +266,7 @@ static void addObjects() {
#define READ_BYTE() (*frame->ip++)
#define BINARY_OP(op) { KrkValue b = krk_pop(); KrkValue a = krk_pop(); krk_push(op(a,b)); break; }
#define READ_CONSTANT(s) (frame->function->chunk.constants.values[readBytes(frame,s)])
#define READ_CONSTANT(s) (frame->closure->function->chunk.constants.values[readBytes(frame,s)])
#define READ_STRING(s) AS_STRING(READ_CONSTANT(s))
static size_t readBytes(CallFrame * frame, int num) {
@ -264,8 +297,8 @@ static KrkValue run() {
fprintf(stderr, " ]");
}
fprintf(stderr, "\n");
krk_disassembleInstruction(&frame->function->chunk,
(size_t)(frame->ip - frame->function->chunk.code));
krk_disassembleInstruction(&frame->closure->function->chunk,
(size_t)(frame->ip - frame->closure->function->chunk.code));
#endif
uint8_t opcode;
switch ((opcode = READ_BYTE())) {
@ -276,6 +309,7 @@ static KrkValue run() {
}
case OP_RETURN: {
KrkValue result = krk_pop();
closeUpvalues(frame->slots);
vm.frameCount--;
if (vm.frameCount == 0) {
krk_pop();
@ -312,7 +346,7 @@ static KrkValue run() {
case OP_CONSTANT_LONG:
case OP_CONSTANT: {
size_t index = readBytes(frame, opcode == OP_CONSTANT ? 1 : 3);
KrkValue constant = frame->function->chunk.constants.values[index];
KrkValue constant = frame->closure->function->chunk.constants.values[index];
krk_push(constant);
break;
}
@ -389,6 +423,38 @@ static KrkValue run() {
frame = &vm.frames[vm.frameCount - 1];
break;
}
case OP_CLOSURE_LONG:
case OP_CLOSURE: {
KrkFunction * function = AS_FUNCTION(READ_CONSTANT((opcode == OP_CLOSURE ? 1 : 3)));
KrkClosure * closure = newClosure(function);
krk_push(OBJECT_VAL(closure));
for (size_t i = 0; i < closure->upvalueCount; ++i) {
int isLocal = READ_BYTE();
int index = READ_BYTE();
if (isLocal) {
closure->upvalues[i] = captureUpvalue(frame->slots + index);
} else {
closure->upvalues[i] = frame->closure->upvalues[index];
}
}
break;
}
case OP_GET_UPVALUE_LONG:
case OP_GET_UPVALUE: {
int slot = readBytes(frame, (opcode == OP_GET_UPVALUE) ? 1 : 3);
krk_push(*frame->closure->upvalues[slot]->location);
break;
}
case OP_SET_UPVALUE_LONG:
case OP_SET_UPVALUE: {
int slot = readBytes(frame, (opcode == OP_SET_UPVALUE) ? 1 : 3);
*frame->closure->upvalues[slot]->location = krk_peek(0);
break;
}
case OP_CLOSE_UPVALUE:
closeUpvalues(vm.stackTop - 1);
krk_pop();
break;
}
}
@ -400,7 +466,11 @@ int krk_interpret(const char * src) {
KrkFunction * function = krk_compile(src);
krk_push(OBJECT_VAL(function));
callValue(OBJECT_VAL(function), 0);
KrkClosure * closure = newClosure(function);
krk_pop();
krk_push(OBJECT_VAL(closure));
callValue(OBJECT_VAL(closure), 0);
KrkValue result = run();
return IS_NONE(result);

3
vm.h
View File

@ -8,7 +8,7 @@
#define FRAMES_MAX 64
typedef struct {
KrkFunction * function;
KrkClosure * closure;
uint8_t * ip;
KrkValue * slots;
} CallFrame;
@ -21,6 +21,7 @@ typedef struct {
KrkValue * stackTop;
KrkTable globals;
KrkTable strings;
KrkUpvalue * openUpvalues;
KrkObj * objects;
} KrkVM;