Exercise 1: Tracing origin/base Through a Constructor and a Method Call — Possible Solution ==================================================================== THE PROGRAM ------------------------------ class Counter { init(startAt) { this.count = startAt; } increment() { this.count = this.count + 1; return this.count; } } var a = Counter(5); print a.increment(); CALL 1: Counter(5) ------------------------------ Before OP_CALL: stack = [..., ClassCounter_value, 5.0] (arg_count = 1) origin = len(stack) - arg_count - 1 -> position of ClassCounter_value callee = stack[origin] -> the WispClass Since callee is a WispClass with an 'init' method (arity 1, matches arg_count): instance = WispInstance(Counter) stack[origin] = instance # replaces the class value in place new_base = origin # NOT origin + 1 -- slot 0 = instance push CallFrame(init_closure, ip=0, base=origin, origin=origin) Stack is now: [..., instance, 5.0] slot 0 (base + 0) = instance <- 'this' slot 1 (base + 1) = 5.0 <- 'startAt' init's own body: this.count = startAt compiles to OP_GET_LOCAL 0 (push 'this') OP_GET_LOCAL 1 (push 'startAt') OP_SET_PROPERTY 'count' then the implicit initializer return: OP_GET_LOCAL 0 (push 'this' again) OP_RETURN OP_RETURN: result = instance (popped); frames.pop(); del stack[finished_frame.origin:] -> removes instance AND 5.0 stack.append(result) -> stack: [..., instance] 'var a = ...' then binds 'a' to this instance via OP_DEFINE_GLOBAL. CALL 2: a.increment() ------------------------------ Compiling a.increment() first pushes 'a' (OP_GET_GLOBAL), then OP_GET_PROPERTY 'increment' -- this POPS the instance and pushes a BoundMethod(receiver=instance, method=increment_closure) in its place. Before OP_CALL: stack = [..., BoundMethod_value] (arg_count = 0) origin = len(stack) - 0 - 1 -> position of BoundMethod_value callee = stack[origin] -> the BoundMethod Since callee is a BoundMethod: stack[origin] = callee.receiver # the SAME instance object from Call 1 new_base = origin # slot 0 = the receiver push CallFrame(increment_closure, ip=0, base=origin, origin=origin) Stack is now: [..., instance] slot 0 (base + 0) = instance <- 'this' (increment takes no params, so there is no slot 1 at all) increment's own body reads/writes this.count via OP_GET_LOCAL 0 + OP_GET_PROPERTY/OP_SET_PROPERTY exactly as before, computes 6.0, and returns it. OP_RETURN discards everything from origin onward and leaves just 6.0 on the stack. RESULT: prints "6" WHY THIS WORKS AS AN ANSWER ------------------------------ Both calls land on base = origin, not origin + 1 -- the defining trait of every method/constructor call in this design, as opposed to an ordinary function call (Chapter 6), where base = origin + 1 because the callee itself is discarded rather than becoming a local. In both traces here, slot 0 is never the "first argument" -- it's always the receiver, which is exactly what makes `this` resolve correctly inside every method body without any special-casing in visit_variable at all.