-
Yorick Peterse authored
Prior to this commit, "self" was just syntax sugar for obtaining local variable 0. This variable in turn was populated by the (implicit) argument 0. In other words, this: def foo(bar) { self } foo Was more or less translated into the following: def foo(self_arg, bar) { self_arg } foo(self, bar) While fairly simple to implement, this poses two problems: 1. "self" is an implicit argument, which can be confusing for users. For example, when using mirrors to obtain the list of method arguments, "self" would be included in the list. 2. The VM could not schedule a Block for execution on its own, because it doesn't know what object to pass as the first argument (= self). Problem 2 made it impossible to implement panic hooks in a nice way, and any future features that require the VM to schedule blocks (e.g. when trapping signals). To work around this, "self" is now explicitly bound to blocks, when they are defined. To execute methods, we use a new instruction: RunBlockWithReceiver. This instruction takes a receiver (= the object to use for "self") to use when executing the method. The receiver in this case will be the object the method was invoked on. When a block is created, we no longer create a new binding for it. Instead, we store the binding that the block captures, which we later set as the parent for the new binding when executing the block. This removes the need for allocating a Binding for every block that is defined, even when never executed. The block also stores the receiver to use when executing said block. This setup also means we can remove quite a bit of nasty bits from the compiler, as we no longer need to generate implicit arguments and local variables. It also allows us to (in the future) obtain the receiver of a block (for meta programming), without having to rely on the exact local variable index used to store this object.