[squeak-dev] ProtoObject (was: #isNilOr:, #notNilAnd:)

Thiede, Christoph Christoph.Thiede at student.hpi.uni-potsdam.de
Mon Mar 23 09:03:35 UTC 2020


Hi Eliot,


> nil is a singleton.


Uh, yes, of course. This was a strange aspect of my example ... But you can reproduce the same problem without reinstantiating UndefinedObject:


nilTracer := ObjectTracer on: nil.
nilTracer isNil. "false"
nilTracer perform: #isNil. "true"

This is still not nice. Well, this is not an issue of inlining but it happens because #isNil defined on ProtoObject. If I understand your vision of ProtoObject correctly, we would want to change this anyway. What problems do you expect when we move #isNil down to Object? :-)

> Perhaps we should add that guard to #basicNew. :)

Rather not. #basicNew is meant as a very private message which you should never send to strangers. For experimental purposes of whatever kind, it is great to have the possibility to do these illegal things, just to explore the effects. I would rather vote for criticizing the use of #basicNew in linters such as RefactoringBrowser.


> #isNil is not inlined in Squeak. #ifNil:, #ifNotNil:, #ifNil:ifNotNil:, #ifNotNil:ifNil: are inlined when their arguments are literal blocks.

>
> Having them inlined is perfectly fine as long as nil is a singleton.

Sorry, I am still not convinced. :-) Consider the following:

nilTracer ifNil: [true] ifNotNil: [false]. "false"

There are two issues with this output:

1. I expect this to return true so that nilTracer becomes absolutely transparent. However, inlining is not the problem here, but rather the fact that #== is defined on ProtoObject. Again here: Would you agree to move this down to Object, and what specific problems would you expect?

2. Even if we fix #==, this #ifNil:ifNotNil: call will *not* be traced by ObjectTracer as long as it is inlined. In my opinion, it is not desirable to have any special selectors that don't follow the usual rules of message dispatching in Squeak ...

... thus, my proposal below. The proposal is not related to singletons in any way, it just describes an approach of how we could get rid of compiler-hard-coded behavior.


> 2. we have a prototype adaptive optimizer/speculative inliner, Scorch, written by Clément Béra, with my having designed them lower levels of the architecture. I am currebtly making space in my work life to port this to Squeak and productise it.  This optimizer will automatically create inlined code that is much more efficient than these helped methods.  So these are a premature optimization.

I'm really looking forward to this! So I understand you correctly that in Squeak Scorch, we will not need any precompiled inlining any longer? :D In this case, anything like #acceptsInlining would not be worth implementation, of course :-)

> Yes, #class is a special selector known by the VM. IIRC this one is easier to get rid of by removing #class from #specialSelectors (or disabling the use of the special bytecode for #class some other way) and recompiling all code.

It is definitively crazy that the bug does not occur if you run {specialObj class} simulated. However we decide, shouldn't we implement the simulation of special selectors properly in Context so that they are not actually sent as regular messages? For Sista's bytecode 16r77, for example, we could use the mirror primitive #objectClass:. I'd be glad to try my luck if you think it is useful!

(Apologize a VM newbie's question: Where can I read the official implementation of bytecode interpretation for Cog? For educational purposes, I found this in SqueakJS<https://github.com/bertfreudenberg/SqueakJS/blob/13581caeb489875c4a39a78aa9d95866cc8892a5/vm.primitives.js#L57-L79>, but for Cog, can I find it in the OpenSmalltalkVM repo or is this in VMMaker package?)

Best,
Christoph


<http://www.hpi.de/>
________________________________
Von: Squeak-dev <squeak-dev-bounces at lists.squeakfoundation.org> im Auftrag von Levente Uzonyi <leves at caesar.elte.hu>
Gesendet: Sonntag, 22. März 2020 16:36:07
An: The general-purpose Squeak developers list
Betreff: Re: [squeak-dev] ProtoObject (was: #isNilOr:, #notNilAnd:)

Hi Christoph,

On Sat, 21 Mar 2020, Thiede, Christoph wrote:

>
> (Forking this thread due to difference of concerns ... :-))
>
>
> > I'm also not sure whether these methods belong to ProtoObject or not.
>
> This is an interesting problem, see also the recent thread about cleaning up ProtoObject. I agree with Eliot that a good ProtoObject is as small and stupid as possible in order to be really useful for writing powerful proxies
> or decorators. It should be possible to wrap nil into an ObjectTracer, for example! To put the issue in a nutshell, try out the following:
>
>       newNil := UndefinedObject basicNew.
>
> nilTracer := ObjectTracer on: newNil.
>
> nilTracer isNil. "false"
>
> nilTracer perform: #isNil. "true"

nil is a singleton. You seem to be aware of this because in your code you
had to work around the guard implemented in UndefinedObject >> #new to
create another instance by using #basicNew.
The VM relies on this property, so nil is a special object known by the
VM.
nil's role as a singleton is pronounced by the language itself: it has
its own keyword.
Perhaps we should add that guard to #basicNew. :)

>
>
> Here's a similar issue:
>
>       specialClass := Object newSubclass.
> specialClass compile: 'class ^42'.
> specialObj := specialClass new.
> specialObj class. "Object1"
> specialObj perform: #class. "42"

Yes, #class is a special selector known by the VM. IIRC this one is easier
to get rid of by removing #class from #specialSelectors (or disabling
the use of the special bytecode for #class some other way) and recompiling
all code.

>
>
> (By the way, we are not even consistent with ourself in this concern:
>
>       ([specialObj class] newProcess runUntil: #willReturn) suspendedContext top. "42"
>
>
> ) Pooh, that's both really not nice ... Basically, inlining of #isNil & Co. is an optimization and IMHO it should not affect the basic concept of how messages are dispatched. Too less inlining affects performance, but too
> much inlining introduces conceptional errors.

#isNil is not inlined in Squeak. #ifNil:, #ifNotNil:, #ifNil:ifNotNil:,
#ifNotNil:ifNil: are inlined when their arguments are literal blocks.

Having them inlined is perfectly fine as long as nil is a singleton.

> Would it be a big performance impact if we made inlining optional? Here is a short proposal:

Here's a quick benchmark measuring the slowdown of #ifNotNil: with a non
full block 10 times:

((1 to: 10) collect: [ :run |
         [ :empty :inlined :notInlined | (notInlined - empty) / (inlined - empty) asFloat ]
                 valueWithArguments: {
                         [ 1 to: 100000000 do: [ :i | ] ] timeToRun.
                         [ 1 to: 100000000 do: [ :i | i ifNotNil: [ :j | j ] ] ] timeToRun.
                         [ | b | b := [ :j | j ]. 1 to: 100000000 do: [ :i | i ifNotNil: b ] ] timeToRun } ])
         sort
         collect: [ :result | result asScaledDecimal: 2 ]

On my machine it yields values between 12 and 32.

>
> We could tell every bytecode interpreter to check for any receiver if it agrees on being deprived of messages that can be inlined. We could decide this per receiver class and cache these answers in the VM. Hypothetical
> example:
>
>       Object >> #acceptsInlining
>
>     "This message is never sent, but it's existance signalizes the VM that it is okay to inline popular messages such as #isNil or #ifTrue:."
>
>
> If this concept is too expensive or too confusing, we could also add an instance variable to the class object. Anyway, something like this. That way we could make the concept of inlining optional. If an interpreter evaluated
> {nilTracer isNil}, it would have to do the following (I'm following the simulation code in #interpretNextSistaV1InstructionFor:. Disclaimer: Absolutely free of optimizations!)
>
>       ...
> "short sends"
> (div16 = 6 and: [rcvr respondsTo: #acceptsInlining]) ifTrue:
> [^client
> send: (Smalltalk specialSelectorAt: offset + 1)
> super: false
> numArgs: (Smalltalk specialNargsAt: offset + 1)].
> ...
> "otherwise, message will be dispatched regularly"
>
>
> Some similar checks would be required in case of primitives that are generated for inlined methods, such as 257f.:
>
>       ...
> ((primIndex := meth primitive) > 0 and: [
>         (self isInliningPrimitive: primitive) not or: [rcvr respondsTo: #acceptsInlining]])
> ifTrue:
>     [val := self doPrimitive: primIndex method: meth receiver: rcvr args: arguments.
>     (self isPrimFailToken: val) ifFalse:
>         [^val]]
> ...
> "otherwise, message will be dispatched regularly"
>
>
> How would you think of this concept in general? I'm excited to hear your feedback! :-)

My impression is that you want to break the basic concept of nil being a
singleton. It's doable, but then you'll need a separate compiler and
object space where the nil keyword is not allowed.


Levente

>
> Best,
> Christoph
>
>
> _________________________________________________________________________________________________________________________________________________________________________________________________________________________________
> Von: Squeak-dev <squeak-dev-bounces at lists.squeakfoundation.org> im Auftrag von Levente Uzonyi <leves at caesar.elte.hu>
> Gesendet: Samstag, 21. März 2020 20:25 Uhr
> An: The general-purpose Squeak developers list
> Betreff: Re: [squeak-dev] #isNilOr:, #notNilAnd:
> Hi Christoph,
>
> They are okay for scripts, but we know that methods like that always end
> up being used out of scripts, and are even misused every now and then.
> They are especially susceptible to misuse when their return value is
> ignored; in which case they are both equivalent to #ifNotNil:.
>
> I'm also not sure whether these methods belong to ProtoObject or not.
> I know the other nil handling methods are there as well, but that doesn't
> mean all such methods belong there.
> E.g. #ifNil:/#ifNotNil: should be there because the current compiler and
> VM will make those available for all objects provided the methods are
> inlined. But #isNil and #notNil have no such properties, so they belong
> more to Object than ProtoObject in my opinion.
>
>
> Levente
>
> On Mon, 16 Mar 2020, Thiede, Christoph wrote:
>
> >
> > Hi all,
> >
> >
> > after the release has been managed successfully, I would like to kindly push this proposal. In the latest months, I found myself to really often desiring #isNilOr: or #notNilAnd:. How would you think about these convenience
> > selectors? :-)
> >
> >
> > Best,
> >
> > Christoph
> >
> >________________________________________________________________________________________________________________________________________________________________________________________________________________________________
> _
> > Von: Thiede, Christoph
> > Gesendet: Mittwoch, 11. Dezember 2019 11:01:44
> > An: Squeak Dev
> > Betreff: #isNilOr:, #notNilAnd:
> >
> > Hi all, just another idea for a hypothetic extension to ProtoObject:
> >
> >
> > ProtoObject >> #isNilOr: aBlock
> >
> > ^ aBlock cull: self
> >
> > UndefinedObject >> #isNilOr: aBlock
> >
> > ^ true
> >
> > ProtoObject >> #notNilAnd: aBlock
> >
> > ^ aBlock cull: self
> >
> > UndefinedObject >> #notNilAnd: aBlock
> >
> > ^ false
> >
> >
> > Examples:
> >
> > parentObject notNilAnd: #canBrowseSubtopic.
> >
> > helpClass notNilAnd: [:hC| hC usesCodeStyling].
> >
> > self selectionIndex isNilOr: [:index | index > 2].
> >
> > anObject isNilOr: #isZero.
> >
> > foo isNilOr: #isNotEmpty.
> >
> >
> > Just a bit syntactic sugar for idiomatic problems such as below. In particular, I found several possible applications for #notNilAnd: in my image.
> >
> >
> > Go ahead and tell me why it would be a bad idea :)
> >
> >
> > Best,
> >
> > Christoph
> >
> >
> >________________________________________________________________________________________________________________________________________________________________________________________________________________________________
> _
> > Von: Squeak-dev <squeak-dev-bounces at lists.squeakfoundation.org> im Auftrag von Thiede, Christoph
> > Gesendet: Freitag, 25. Oktober 2019 19:21 Uhr
> > An: gettimothy via Squeak-dev
> > Betreff: Re: [squeak-dev] The Inbox: HelpSystem-Core-ct.123.mcz
> >
> > If you're needing a decision, I'd vote for option 3.
> >
> >________________________________________________________________________________________________________________________________________________________________________________________________________________________________
> _
> > Von: Squeak-dev <squeak-dev-bounces at lists.squeakfoundation.org> im Auftrag von Taeumel, Marcel
> > Gesendet: Mittwoch, 16. Oktober 2019 11:14:15
> > An: gettimothy via Squeak-dev
> > Betreff: Re: [squeak-dev] The Inbox: HelpSystem-Core-ct.123.mcz
> > Quick suggestion on the formatting. This one:
> > ^ self canBrowseTopic or: [
> >    parentTopic ifNotNil: #canBrowseSubtopic ifNil: [false]]
> >
> > could become:
> >
> > ^ self canBrowseTopic or: [
> >    parentTopic
> >    ifNil: [false]
> >    ifNotNil: [:topic | topic canBrowseSubtopic]]
> >
> > or:
> >
> > ^ self canBrowseTopic
> >       or: [parentTopic notNil and: [parentTopic canBrowseSubtopic]]
> >
> > Hmmm... what are other opinions on this? There is no need for #ifNil/ifNotNil in such a boolean expression?
> >
> > Best,
> > Marcel
> >
> >
> >       Am 13.10.2019 21:04:19 schrieb commits at source.squeak.org <commits at source.squeak.org>:
> >
> >       A new version of HelpSystem-Core was added to project The Inbox:
> >       http://source.squeak.org/inbox/HelpSystem-Core-ct.123.mcz
> >
> >       ==================== Summary ====================
> >
> >       Name: HelpSystem-Core-ct.123
> >       Author: ct
> >       Time: 13 October 2019, 9:04:08.373932 pm
> >       UUID: dec7ceca-320f-d945-8d2a-c2f6a5e49a52
> >       Ancestors: HelpSystem-Core-ct.120
> >
> >       Refactors HelpBrowser menu: Move menu stuff from HelpBrowser into HelpTopic hierarchy in favor of a better object design
> >
> >       Thanks again, Marcel :-)
> >
> >       =============== Diff against HelpSystem-Core-ct.120 ===============
> >
> >       Item was added:
> >       + ----- Method: AbstractHelpTopic>>browseTopicFromParent: (in category 'tools') -----
> >       + browseTopicFromParent: parentTopic
> >       +
> >       + self canBrowseTopic
> >       + ifTrue: [^ self browseTopic].
> >       + parentTopic canBrowseSubtopic
> >       + ifTrue: [^ parentTopic browseSubtopic: self].
> >       + !
> >
> >       Item was added:
> >       + ----- Method: AbstractHelpTopic>>canBrowseSubtopic (in category 'testing') -----
> >       + canBrowseSubtopic
> >       +
> >       + ^ false!
> >
> >       Item was added:
> >       + ----- Method: AbstractHelpTopic>>canBrowseTopic (in category 'testing') -----
> >       + canBrowseTopic
> >       +
> >       + ^ false!
> >
> >       Item was added:
> >       + ----- Method: AbstractHelpTopic>>canBrowseTopicFromParent: (in category 'testing') -----
> >       + canBrowseTopicFromParent: parentTopic
> >       +
> >       + ^ self canBrowseTopic or: [
> >       + parentTopic ifNotNil: #canBrowseSubtopic ifNil: [false]]!
> >
> >       Item was added:
> >       + ----- Method: AbstractHelpTopic>>topicMenu:parentTopic: (in category 'menus') -----
> >       + topicMenu: aMenu parentTopic: parentTopic
> >       +
> >       + aMenu
> >       + add: 'Inspect (i)' translated target: self action: #inspect;
> >       + add: 'Explore (I)' translated target: self action: #explore.
> >       + (self canBrowseTopicFromParent: parentTopic)
> >       + ifTrue: [
> >       + aMenu add: 'Browse (b)' translated
> >       + target: self
> >       + selector: #browseTopicFromParent:
> >       + argumentList: {parentTopic} ].
> >       +
> >       + ^ aMenu!
> >
> >       Item was added:
> >       + ----- Method: AbstractHelpTopic>>topicMenuKey:fromParent: (in category 'menus') -----
> >       + topicMenuKey: aChar fromParent: parentTopic
> >       +
> >       + aChar
> >       + caseOf: {
> >       + [$b] -> [(self canBrowseTopicFromParent: parentTopic)
> >       + ifTrue: [ self browseTopicFromParent: parentTopic ]].
> >       + [$i] -> [self inspect].
> >       + [$I] -> [self explore] }
> >       + otherwise: [^ false].
> >       + ^ true!
> >
> >       Item was added:
> >       + ----- Method: ClassAPIHelpTopic>>canBrowseTopic (in category 'testing') -----
> >       + canBrowseTopic
> >       +
> >       + ^ true!
> >
> >       Item was added:
> >       + ----- Method: ClassBasedHelpTopic>>canBrowseSubtopic (in category 'testing') -----
> >       + canBrowseSubtopic
> >       +
> >       + ^ true!
> >
> >       Item was added:
> >       + ----- Method: ClassBasedHelpTopic>>canBrowseTopic (in category 'testing') -----
> >       + canBrowseTopic
> >       +
> >       + ^ true!
> >
> >       Item was added:
> >       + ----- Method: DirectoryBasedHelpTopic>>canBrowseTopic (in category 'testing') -----
> >       + canBrowseTopic
> >       +
> >       + ^ true!
> >
> >       Item was added:
> >       + ----- Method: FileBasedHelpTopic>>canBrowseTopic (in category 'testing') -----
> >       + canBrowseTopic
> >       +
> >       + ^ true!
> >
> >       Item was removed:
> >       - ----- Method: HelpBrowser>>browseTopic (in category 'actions') -----
> >       - browseTopic
> >       -
> >       - ^ (self currentTopic respondsTo: #browseTopic)
> >       - ifTrue: [self currentTopic browseTopic]
> >       - ifFalse: [self currentParentTopic browseSubtopic: self currentTopic]!
> >
> >       Item was removed:
> >       - ----- Method: HelpBrowser>>canBrowseTopic (in category 'testing') -----
> >       - canBrowseTopic
> >       -
> >       - ^ (self currentTopic respondsTo: #browseTopic)
> >       - or: [self currentParentTopic respondsTo: #browseSubtopic:]!
> >
> >       Item was removed:
> >       - ----- Method: HelpBrowser>>exploreTopic (in category 'actions') -----
> >       - exploreTopic
> >       -
> >       - ^ self currentTopic explore!
> >
> >       Item was removed:
> >       - ----- Method: HelpBrowser>>inspectTopic (in category 'actions') -----
> >       - inspectTopic
> >       -
> >       - ^ self currentTopic inspect!
> >
> >       Item was changed:
> >       ----- Method: HelpBrowser>>treeKey:from:event: (in category 'menus') -----
> >       treeKey: aChar from: aView event: anEvent
> >
> >       anEvent anyModifierKeyPressed ifFalse: [^ false].
> >       + ^ (self currentTopic topicMenuKey: aChar fromParent: self currentParentTopic)!
> >       - aChar
> >       - caseOf: {
> >       - [$b] -> [self browseTopic].
> >       - [$i] -> [self inspectTopic].
> >       - [$I] -> [self exploreTopic]. }
> >       - otherwise: [^ false].
> >       - ^ true!
> >
> >       Item was changed:
> >       ----- Method: HelpBrowser>>treeListMenu: (in category 'menus') -----
> >       treeListMenu: aMenu
> >
> >
> >       + ^ self currentTopic
> >       + ifNil: [aMenu]
> >       + ifNotNil: [:topic | topic
> >       + topicMenu: aMenu
> >       + parentTopic: self currentParentTopic]!
> >       - self currentTopic ifNil: [^ aMenu].
> >       -
> >       - aMenu
> >       - add: 'Inspect (i)' action: #inspectTopic;
> >       - add: 'Explore (I)' action: #exploreTopic.
> >       -
> >       - self canBrowseTopic ifTrue: [
> >       - aMenu
> >       - addLine;
> >       - add: 'Browse (b)' action: #browseTopic].
> >       -
> >       - ^ aMenu!
> >
> >       Item was added:
> >       + ----- Method: MethodListHelpTopic>>canBrowseTopic (in category 'testing') -----
> >       + canBrowseTopic
> >       +
> >       + ^ true!
> >
> >       Item was added:
> >       + ----- Method: PackageAPIHelpTopic>>canBrowseTopic (in category 'testing') -----
> >       + canBrowseTopic
> >       +
> >       + ^ true!
> >
> >
> >
> >
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20200323/1a725584/attachment-0001.html>


More information about the Squeak-dev mailing list