>> Another thing to look at is the inspector of the debugger showing self as
>> nil (first line, the implicit self), but his is for another time.
Here is a PR:
Fix-CleanBlocks-StInspectorSelfNode #542
https://github.com/pharo-spec/NewTools/pull/542
<https://github.com/pharo-spec/NewTools/pull/542>
> On 30 May 2023, at 17:29, Marcus Denker <[email protected]> wrote:
>
> I did a PR:
>
>
> https://github.com/pharo-project/pharo/pull/13855
>
>
> differences:
>
> - guard for nil in #readInContext
> - two tests:
>
> SelfVariableTest >> testReadInContext [
>
> | var |
> var := self class lookupVar: #self.
> self assert: (var readInContext: thisContext) identicalTo: self.
> "read from a block context"
> self assert: [(var readInContext: thisContext)] value identicalTo: self
> ]
>
>
> SelfVariableTest >> testReadInContextClean [
> <compilerOptions: #( +optionCleanBlockClosure)>
> | var block |
> "if context is one stack we can read"
> block := [ (thisContext lookupVar: #self) readInContext: thisContext ].
> self assert: block value equals: self.
> "but no chance if not, then it is nil"
> var := self class lookupVar: #self.
> block := [ thisContext ].
> self assert: (var readInContext: block value ) equals: nil
> ]
>
>> On 29 May 2023, at 10:48, Marcus Denker <[email protected]> wrote:
>>
>> In the last post, we looked at reading the PseudoVariable thisContext in the
>> Debugger. A summary of that might be: inspecting a variable compiled a DoIt,
>> that DoIt it executed parametrised with the context that you look at in the
>> debugger. As it is, in the end, just another method executing, the byte-code
>> pushThisContext pushes the context of the executing DoIT method, not the
>> context that you look at in the debugger.
>>
>> For "self", this is of course the same. Take Pharo11, add and insect
>> "thisContext method inspect. self"
>>
>> 41 <52> pushThisContext
>> 42 <80> send: method
>> 43 <81> send: inspect
>> 44 <D8> pop
>> 45 <58> returnSelf
>>
>>
>> Thus even "self" is not really concerned with the context you are looking
>> at, it is the "self" of the DoIt. The nice thing is that this is always (in
>> the debugger) the correct object: In the case of blocks, the only time you
>> look at a block in a debugger to read self is if you are in the home method
>> of the block (the method that contains the definition of the block). And as
>> the "self" of a block closure is the "self" of the home context, you get the
>> value you expect.
>>
>>
>> # Self and Clean Blocks
>>
>> It gets interesting if you put clean blocks into the picture. Clean Blocks
>> are blocks that just use information that is available at compile time. The
>> compiler thus can pre-allocate them. For this discussion, we need to know
>> that clean blocks do not have an outer Context, and self of the block is
>> always nil.
>>
>> Clean block support is there, you can enable it and even re-compile the
>> whole image, but there are still some details to be improved before we can
>> enable it. One of that is to fix all the debugger infrastructure to deal
>> with blocks that have no receiver and no home context, this post thus is one
>> tiny step of that work.
>>
>> For this post, we just need to compile one method with clean blocks enabled,
>> we create a class and method to play with (e.g. class TT with method tt):
>>
>>
>> ```
>> tt
>> <compilerOptions: #(+ optionCleanBlockClosure)>
>> [ 1halt ] value
>> ```
>>
>> (or instead of the pragma, enable the setting globally with the Settings
>> browser)
>>
>> With just sending a message to 1, the block can be compiled as a clean
>> block. The halt will mean that the debugger opens on the context of the
>> clean block being executed.
>>
>> Let's do that, execute "TT new tt" and we get a debugger:
>>
>> <1CleanBlockDebugger.jpeg>
>>
>>
>> It is interesting to look at the context, if you inspect the context, you
>> see that the receiver is nil. Looking at the block closure, we see that it
>> is indeed a CleanBlockClosure, outerContext is nil (and thus it can not
>> follow the outerContext till it finds the home context).
>>
>> So if we now just write "self" in the block and expect it (we do not save,
>> we just want to explore), it evaluates to "nil":
>>
>>
>> <2SelfInClean.jpeg>
>> This is of course correct: The debugger looks at the home context of the
>> block, we ask context for "self" and self in a context evaluating a clean
>> block is nil, as the block does not know self.
>>
>> But I am sure you are not happy with that. It feels wrong. The reason is
>> that if you would accept the method (recompile), restart, then self would be
>> "correct" (the instance of TT). This is because the compiler now sees the
>> self, realizes that this block requires a full block and compiles a full
>> block.
>>
>> This is similar to accessing temps in blocks that do not access them. Change
>> the method to have a temp, but one that is not accessed in the block:
>>
>> ```
>> tt
>>
>> | temp |
>> temp := 1.
>> [ 1halt ] value.
>> ^temp.
>> ```
>>
>> If you write "temp" into the block and inspect or print it, you get the
>> value of temp:
>>
>>
>> <3ReadTemp.jpeg>
>> The context of the executing block does not know the variable "temp" at all,
>> it's value is as unknown to it as the self is to the clean block. (This is
>> true both if compiled as a clean block or a full block).
>>
>> So why can we nevertheless read it? The reason:
>>
>> - Looking up the variable by name of course works (after all, the compiler
>> can compile a block that does access it, the compiler uses the same
>> data for variable lookup as the reflective subsystem does)
>> - The reflective read is carefully build to allow reading this variable even
>> from the block context that does not know it (by leveraging the stack of
>> contexts)
>> - We force the compiler to compile all temp variable accesses as reflective
>> accesses in a DoIt
>>
>> So if we can "fake" it for temps (both clean and non-clean blocks), can we
>> do it for "self in a clean block", too?
>>
>> If you think back at the discussion of reading thisContext, we fixed that by
>> forcing reflective read for thisContext, too. And of course the PR that
>> implements it, was done
>> already for all PseudoVariables.
>>
>> This means, reading self should, in Pharo12, already end up executing
>> SelfVariable>>#readInContext:, so let's just put a halt there, trigger a
>> read in
>> the debugger and, indeed:
>>
>> <4HaltInSelfRead.jpeg>
>>
>>
>> One thing we now see is that reflective read of self is implemented to do
>> exactly the same as "pushSelf", it returns the receiver of the context we
>> look at in the debugger:
>>
>> ```
>> readInContext: aContext
>> self halt.
>> ^aContext receiver
>> ```
>>
>>
>> But that is not really correct: in case of a block, it should be the
>> receiver of the *home* context. For all practical purposes (when in the
>> debugger) they are the same, and for Clean Blocks we even know that the home
>> Context is unknown... So how does this help if we would implement it like
>> that:
>>
>> ```
>> readInContext: aContext
>> ^aContext home receiver
>> ```
>>
>> The trick is that we can find a home context for clean blocks in some
>> specific cases. Even though we can not get the home of a clean block via
>> it's outer context (as is is nil), we can find the home method if it happens
>> to be on the stack. And in the debugger, we are in exactly that case!
>>
>> So let's try to fix #home for clean blocks.
>>
>> ## The concept of active Home
>>
>> The Debugger already needs to know if the home is currently on the stack.
>> For that, it used a method #activeHome, in Pharo10 this looked like that:
>>
>>
>> activeHome
>> | methodReturnContext |
>> self isBlockContext ifFalse: [^self].
>> self sender ifNil: [^nil].
>> methodReturnContext := self methodReturnContext.
>> ^self sender findContextSuchThat: [:ctxt | ctxt = methodReturnContext]
>>
>>
>> #activeHome returns the #home if it is currently on the stack, the debugger
>> uses that to check if a “save and proceed”
>> is possible when editing code in a Block. Until Pharo11, it was implemented
>> to search up the sender chain until it finds the #home.
>>
>> But it can be rewritten to do the same, but checking for the #homeMethod and
>> using #findMethodContextSuchThat:
>>
>> activeHome
>> | homeMethod |
>> self isBlockContext ifFalse: [^self].
>> homeMethod := self homeMethod.
>> ^self findMethodContextSuchThat: [:ctxt | ctxt method == homeMethod]
>>
>>
>> With #homeMethod being implemented to delegate to the bock:
>>
>> Context>>homeMethod
>> "Answer the method in which the receiver was defined, i.e. the context
>> from which an ^-return ] should return from. Note: implemented to not need
>> #home"
>>
>> ^ closureOrNil ifNil: [ self method ] ifNotNil: [ :closure | closure
>> homeMethod ]
>>
>>
>> Where, if no #home via the outerContext is available, it asks the
>> CompiledBlock:
>>
>> BlockClosure>>homeMethod
>> "return the home method. If no #home is available due to no
>> outerContext, use the compiledBlock"
>> ^ (self home
>> ifNotNil: [ :homeContext | homeContext ]
>> ifNil: [ self compiledBlock ]) method
>>
>>
>> Which uses the static #outerCode chain (CompiledBlocks encode a back-pointer
>> to the enclosing block or method),
>> with #method following #outerCode until it reaches a CompiledMethod:
>>
>> CompiledBlock>>method
>> "answer the compiled method that I am installed in, or nil if none.”
>> ^self outerCode method
>>
>>
>> This was already done in Pharo11,
>>
>> 11965-Implement-ContextactiveHome-without-using-home #12063
>> https://github.com/pharo-project/pharo/pull/12063
>>
>>
>> But we can now continue and use it to improve #home of Context, you see that
>> it already has a (bad) workaround when the outerContext is nil:
>>
>>
>> ```
>> home
>> "Answer the context in which the receiver was defined, i.e. the context
>> from which an ^-return ] should return from."
>>
>> closureOrNil ifNil: [ ^ self ].
>> "this happens for clean blocks. We should later check if it is not
>> better to return nil"
>> closureOrNil outerContext ifNil: [ ^ self ].
>> ^ closureOrNil outerContext home
>> ```
>>
>>
>> returning self when the outerContext is nil (when we are in a clean block)
>> is only correct for the home context of a clean block.
>> We can do better, and use #activeHome and search for it on the stack (with
>> the added bonus to correctly return nil if we do not find it).
>>
>>
>> ```
>> home
>> "Answer the context in which the receiver was defined, i.e. the context
>> from which an ^-return ] should return from."
>>
>> closureOrNil ifNil: [ ^ self ].
>> "no outerContext for clean blocks, we try to find it on the stack"
>> ^ closureOrNil outerContext
>> ifNil: [ self activeHome ]
>> ifNotNil: [:outer | outer home ]
>> ```
>>
>> And it works!
>>
>> <5FixedReadSelf.jpeg>
>>
>> I am not sure if falling back to #activeHome in #home is really the best
>> (maybe explicitly handling the nil and do the fallback to activeHome
>> at the side of the caller is less magic), but we can change that easily
>> later if it turns out the be better.
>>
>> Another thing to look at is the inspector of the debugger showing self as
>> nil (first line, the implicit self), but his is for another time.
>>
>> Marcus
>>
>>
>>
>