@@ -11,15 +11,16 @@ namespace {
1111ptrdiff_t get_jump_offset (uint8_t high_byte, uint8_t low_byte) {
1212 // NOTE: we have to be really careful here about how we do the static_cast!
1313 // The high and low bytes are encoded including the sign information as the
14- // most significant bit of the high byte. So we need to combine them into a single
15- // int16_t FIRST, so that the sign information is in the right place (i.e. the most
16- // significant bit of the int16_t), and then only convert to ptrdiff_t.
14+ // most significant bit of the high byte. So we need to combine them into a
15+ // single int16_t FIRST, so that the sign information is in the right place
16+ // (i.e. the most significant bit of the int16_t), and then only convert to
17+ // ptrdiff_t.
1718 //
1819 // If we did
1920 // static_cast<ptrdiff_t>(high_byte) << 8 | low_byte
2021 //
21- // the sign bit would not be in the most significant bit of the ptrdiff_t, and so
22- // we would end up with a completely different number.
22+ // the sign bit would not be in the most significant bit of the ptrdiff_t, and
23+ // so we would end up with a completely different number.
2324 int16_t jump_offset = (static_cast <int16_t >(high_byte) << 8 ) | low_byte;
2425 return static_cast <ptrdiff_t >(jump_offset);
2526}
@@ -60,14 +61,19 @@ lox::InterpretResult interpret(std::string_view source) {
6061}
6162
6263VM::VM (std::unique_ptr<scanner::Scanner> scanner, GC gc)
63- : stack_size( 0 ), _gc(std::move(gc)), parser() {
64+ : _gc(std::move(gc)), parser() {
6465 call_frames.reserve (MAX_CALL_FRAMES);
66+ // NOTE: By reserving memory here we avoid having to reallocate when we call
67+ // push_back. That means that we don't need to do what the book does by having
68+ // stack_pop decrement a pointer: we can just make stack_pop call pop_back()
69+ // on the stack. That allows us to keep track of the stack size using
70+ // stack.size() rather than a separate variable, which can get out of sync.
6571 stack.reserve (MAX_STACK_SIZE);
6672 auto top_level_fn = _gc.alloc <ObjFunction>(" #toplevel#" , size_t (0 ));
6773 parser = std::make_unique<Parser>(std::move (scanner), top_level_fn, _gc);
6874
6975 // Aggressive GC: run it every time we allocate
70- _gc.set_alloc_callback ([this ]() { this ->gc (); });
76+ _gc.set_alloc_callback ([this ]() { this ->maybe_gc (); });
7177}
7278
7379lox::InterpretResult VM::invoke_toplevel () {
@@ -101,43 +107,39 @@ lox::Value VM::read_constant() {
101107}
102108
103109VM& VM::stack_reset () {
104- stack_size = 0 ;
110+ stack. clear () ;
105111 return *this ;
106112}
107113
108114VM& VM::stack_push (const lox::Value& value) {
109- if (stack_size >= MAX_STACK_SIZE) {
115+ if (stack. size () >= MAX_STACK_SIZE) {
110116 error (" stack overflow" );
111117 }
112- if (stack_size >= stack.size ()) {
113- stack.push_back (value);
114- } else {
115- stack[stack_size] = value;
116- }
117- stack_size++;
118+ stack.push_back (value);
118119 return *this ;
119120}
120121
121122lox::Value* VM::stack_top_address () {
122- if (stack_size == 0 ) {
123+ if (stack. empty () ) {
123124 error (" stack_top_address: stack underflow" );
124125 }
125- return &stack[stack_size - 1 ] ;
126+ return &stack. back () ;
126127}
127128
128129lox::Value VM::stack_pop () {
129- if (stack_size == 0 ) {
130+ if (stack. empty () ) {
130131 error (" stack_pop: stack underflow" );
131132 }
132- stack_size--;
133- return stack[stack_size];
133+ Value value = std::move (stack.back ());
134+ stack.pop_back ();
135+ return value;
134136}
135137
136138lox::Value VM::stack_peek () {
137- if (stack_size == 0 ) {
139+ if (stack. empty () ) {
138140 error (" stack_peek: stack underflow" );
139141 }
140- return stack[stack_size - 1 ] ;
142+ return stack. back () ;
141143}
142144
143145std::string VM::read_global_name () {
@@ -149,12 +151,14 @@ std::string VM::read_global_name() {
149151
150152void VM::close_upvalues_after (Value* addr) {
151153 for (auto it = open_upvalues.begin (); it != open_upvalues.end ();) {
152- // `it` is an iterator over a vector of ObjUpvalue*, so `it` itself is
153- // ObjUpvalue**
154+ // `it` iterates over a vector of ObjUpvalue*, so we have to dereference
155+ // it to get the ObjUpvalue* itself.
154156 auto upvalue_ptr = *it;
155157 if (upvalue_ptr->location >= addr) {
158+ #ifdef LOX_DEBUG
156159 std::cerr << " closing upvalue at location " << upvalue_ptr->location
157160 << " with value " << *(upvalue_ptr->location ) << " \n " ;
161+ #endif
158162 upvalue_ptr->closed = *(upvalue_ptr->location );
159163 upvalue_ptr->location = &(upvalue_ptr->closed );
160164 it = open_upvalues.erase (it);
@@ -166,21 +170,22 @@ void VM::close_upvalues_after(Value* addr) {
166170
167171lox::Value
168172VM::stack_modify_top (const std::function<lox::Value(const lox::Value&)>& op) {
169- if (stack_size == 0 ) {
173+ if (stack. empty () ) {
170174 error (" stack_modify_top: stack underflow" );
171175 }
172- stack[stack_size - 1 ] = op (stack[stack_size - 1 ]);
173- return stack[stack_size - 1 ];
176+ Value& value = stack.back ();
177+ value = op (value);
178+ return value;
174179}
175180
176181std::ostream& VM::stack_dump (std::ostream& out) const {
177- if (stack_size == 0 ) {
182+ if (stack. empty () ) {
178183 out << " <empty stack>\n " ;
179184 return out;
180185 }
181186 out << " " ;
182- for (size_t i = 0 ; i < stack_size; i++ ) {
183- out << " [" << stack[i] << " ]" ;
187+ for (const auto & v : stack ) {
188+ out << " [" << v << " ]" ;
184189 }
185190 out << " \n " ;
186191 return out;
@@ -205,7 +210,7 @@ void VM::call(ObjClosure* callee, size_t arg_count) {
205210 std::to_string (callee->function ->arity ) +
206211 " arguments but got " + std::to_string (arg_count));
207212 }
208- size_t stack_start = stack_size - arg_count - 1 ;
213+ size_t stack_start = stack. size () - arg_count - 1 ;
209214 CallFrame new_frame (callee, 0 , stack_start);
210215 call_frames.push_back (new_frame);
211216}
@@ -319,6 +324,7 @@ InterpretResult VM::run() {
319324 break ;
320325 }
321326 case OpCode::CLOSE_UPVALUE: {
327+ // This effectively only closes the upvalue that's at the top of the stack.
322328 close_upvalues_after (stack_top_address ());
323329 stack_pop ();
324330 break ;
@@ -428,8 +434,8 @@ InterpretResult VM::run() {
428434 }
429435 case OpCode::SET_LOCAL: {
430436 uint8_t local_index = read_byte ();
431- if (static_cast <size_t >(local_index) > stack_size ) {
432- std::cerr << " stack_size=" << stack_size
437+ if (static_cast <size_t >(local_index) > stack. size () ) {
438+ std::cerr << " stack_size=" << stack. size ()
433439 << " , local_index=" << +local_index << " \n " ;
434440 error (" SET_LOCAL: invalid local variable index" );
435441 }
@@ -438,8 +444,8 @@ InterpretResult VM::run() {
438444 }
439445 case OpCode::GET_LOCAL: {
440446 uint8_t local_index = read_byte ();
441- if (static_cast <size_t >(local_index) > stack_size ) {
442- std::cerr << " stack_size=" << stack_size
447+ if (static_cast <size_t >(local_index) > stack. size () ) {
448+ std::cerr << " stack_size=" << stack. size ()
443449 << " , local_index=" << +local_index << " \n " ;
444450 error (" GET_LOCAL: invalid local variable index" );
445451 }
@@ -473,7 +479,7 @@ InterpretResult VM::run() {
473479 uint8_t nargs = read_byte ();
474480 // The function pointer should have been pushed to the stack before
475481 // the arguments.
476- auto maybe_objptr = stack[stack_size - 1 - nargs];
482+ auto maybe_objptr = stack[stack. size () - 1 - nargs];
477483 if (!std::holds_alternative<Obj*>(maybe_objptr)) {
478484 throw std::runtime_error (" objptr was not a pointer to Obj" );
479485 }
@@ -485,9 +491,9 @@ InterpretResult VM::run() {
485491 auto native_fnptr = dynamic_cast <ObjNativeFunction*>(maybe_closptr);
486492 if (native_fnptr != nullptr ) {
487493 Value retval =
488- native_fnptr->call (nargs, &stack[stack_size - nargs]);
494+ native_fnptr->call (nargs, &stack[stack. size () - nargs]);
489495 // Pop the function and its arguments off the stack.
490- stack_size -= ( nargs + 1 );
496+ stack. resize (stack. size () - nargs - 1 );
491497 // Push the return value onto the stack.
492498 stack_push (retval);
493499 } else {
@@ -510,7 +516,7 @@ InterpretResult VM::run() {
510516 } else {
511517 // Reset the VM's state to where it was before it entered the current
512518 // call.
513- stack_size = current_frame ().stack_start ;
519+ stack. resize ( current_frame ().stack_start ) ;
514520 stack_push (retval);
515521 call_frames.pop_back ();
516522 }
@@ -538,12 +544,12 @@ InterpretResult VM::run() {
538544 }
539545}
540546
541- void VM::gc () {
547+ void VM::maybe_gc () {
542548 if (_gc.should_gc ()) {
543549
544550 // Mark roots as grey.
545- for (size_t i = 0 ; i < stack_size; ++i ) {
546- _gc.mark_as_grey (stack[i] );
551+ for (const auto & v : stack ) {
552+ _gc.mark_as_grey (v );
547553 }
548554 for (const auto & [_, global_value] : globals) {
549555 _gc.mark_as_grey (global_value);
0 commit comments