Skip to content

Commit

Permalink
clocks: Optimize method invocations
Browse files Browse the repository at this point in the history
- Lox’s semantics define a method invocation as two operations—accessing the method and then calling the result. The VM must support those as separate operations because the user can separate them.
- You can access a method without calling it and then invoke the bound method later.
- But always executing those as separate operations has a significant cost. Every single time a Lox program accesses and invokes a method, the runtime heap allocates a new ObjBoundMethod, initializes its fields, then pulls them right back out. Later, the GC has to spend time freeing all of those ephemeral bound methods.
- Implement a new instruction OpInvoke which is used when a Lox program accesses a method and then immediately calls it.
- Since the compiler can see this invocation (dotted property access followed by an opening parenthesis), this is a good opportunity for a superinstruction optimization.
- In invoke() first we grab the receiver off the stack. The arguments passed to the method are above it on the stack, so we peek that many slots down.
- We don’t need to heap allocate and initialize an ObjBoundMethod. In fact, we don’t even need to juggle anything on the stack. The receiver and method arguments are already right where they need to be.
- In a microbenchmark whic does a batch of 10,000 method calls and tests how many of these batches the VM can execute in 10 seconds, there was a 7.6x improvement.
  • Loading branch information
buzzcut-s committed Sep 17, 2021
1 parent 2999014 commit 9e881db
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 0 deletions.
1 change: 1 addition & 0 deletions include/clocks/chunk.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ typedef enum
OpJumpIfFalse,
OpLoop,
OpCall,
OpInvoke,
OpClosure,
OpCloseUpvalue,
OpReturn,
Expand Down
6 changes: 6 additions & 0 deletions src/compiler.c
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,12 @@ static void dot(const bool can_assign)
expression();
emit_bytes(OpSetField, name);
}
else if (match(TokenLeftParen))
{
const uint8_t arg_count = argument_list();
emit_bytes(OpInvoke, name);
emit_byte(arg_count);
}
else
emit_bytes(OpGetProperty, name);
}
Expand Down
12 changes: 12 additions & 0 deletions src/debug.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ static int closure_instruction(const Chunk* chunk, int offset)
return offset;
}

static int invoke_instruction(const Chunk* chunk, int offset)
{
const uint8_t constant = chunk->code[offset + 1];
const uint8_t arg_count = chunk->code[offset + 2];
printf("%-16s (%d args) %4d '", "OpInvoke", arg_count, constant);
print_value(chunk->constants.values[constant]);
printf("'\n");
return offset + 3;
}

int disassemble_instruction(const Chunk* chunk, const int offset)
{
printf("%04d ", offset);
Expand Down Expand Up @@ -151,6 +161,8 @@ int disassemble_instruction(const Chunk* chunk, const int offset)

case OpCall:
return byte_instruction("OpCall", chunk, offset);
case OpInvoke:
return invoke_instruction(chunk, offset);

case OpClosure:
return closure_instruction(chunk, offset);
Expand Down
45 changes: 45 additions & 0 deletions src/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,40 @@ static bool call_value(const Value callee, const int arg_count)
return false;
}

static bool invoke_from_class(const ObjClass* klass,
const ObjString* name, const int arg_count)
{
Value method;
if (!table_find(&klass->methods, name, &method))
{
runtime_error("Undefined property '%s'.", name->chars);
return false;
}

return call(AS_CLOSURE(method), arg_count);
}

static bool invoke(const ObjString* name, const int arg_count)
{
const Value recv = peek(arg_count);
if (!IS_INSTANCE(recv))
{
runtime_error("Only instances have methods.");
return false;
}

const ObjInstance* instance = AS_INSTANCE(recv);

Value value;
if (table_find(&instance->fields, name, &value))
{
vm.stack_top[-arg_count - 1] = value;
return call_value(value, arg_count);
}

return invoke_from_class(instance->klass, name, arg_count);
}

static ObjUpvalue* capture_upvalue(Value* local)
{
ObjUpvalue* prev_upvalue = NULL;
Expand Down Expand Up @@ -502,6 +536,17 @@ static InterpretResult run()
frame = &vm.frames[vm.frame_count - 1];
break;
}
case OpInvoke:
{
const ObjString* method = READ_STRING();
const int arg_count = READ_BYTE();

if (!invoke(method, arg_count))
return InterpretRuntimeError;

frame = &vm.frames[vm.frame_count - 1];
break;
}

case OpClosure:
{
Expand Down

0 comments on commit 9e881db

Please sign in to comment.