On Mon, Oct 27, 2014 at 9:19 AM, Levente Uzonyi leves@elte.hu wrote:
On Mon, 27 Oct 2014, Clément Bera wrote:
Hey guys,
I was discussing about methods like that with Marcus:
MyClass>>foo #(false) first ifFalse: [ 'first call since compilation' logCr. #(false) at: 1 put: true ] ifTrue: [ 'other calls' logCr ]
DoIt: 4 timesRepeat: [MyClass new foo ]
Transcript: 'first call since compilation' 'other calls' 'other calls' 'other calls'
In the sista image/VM, we are most probably going with Eliot to implement a slow path when you edit the literal of a method (deoptimizing the method) so we can consider that the literals are immutable in the generic case (and if it's not true, execution falls back to a slow path).
But I was thinking, does it worth it to implement the slow path ? Couldn't we just put literals as immutable all the time ? The methods as the one shown before would not work any more. But does someone use it ?
This method was used in Seaside (or some Seaside extension) to cache the contents of files in methods.
By the way, how would you ensure immutability?
See my message earlier in the thread. Via a per-object isImmutable bit.
I mean, how would you prevent something like this?
MyClass >> foo
^#('placeHolder')
And then
(((MyClass >> #foo) literalAt: 1) at: 1) becomeForward: Object new
Levente
P.S.: why isn't this on the vm-dev list?
...
On Mon, 27 Oct 2014, Eliot Miranda wrote:
See my message earlier in the thread. Via a per-object isImmutable bit.
So you would set the bit for the CompiledMethod and all literals (including sub-arrays, floats, integers, booleans etc)?
Does the set immutability bit also mean that the object can't be swapped with #become:?
Levente
On Mon, Oct 27, 2014 at 9:55 AM, Levente Uzonyi leves@elte.hu wrote:
On Mon, 27 Oct 2014, Eliot Miranda wrote:
See my message earlier in the thread. Via a per-object isImmutable bit.
So you would set the bit for the CompiledMethod and all literals (including sub-arrays, floats, integers, booleans etc)?
The literals, definitely. Whether the bit is set for compiled methods or not depends on things like how easy you want to be able to update the source pointer or add/delete properties.
Does the set immutability bit also mean that the object can't be swapped with #become:?
That's debatable, but in VW we disallowed two-way become: and allowed one-way become: for immutables. I think that's right. one-way become is about references to objects, two-way become is about changing objects themselves.
On Mon, 27 Oct 2014, Eliot Miranda wrote:
So you would set the bit for the CompiledMethod and all literals (including sub-arrays, floats, integers, booleans etc)?
The literals, definitely. Whether the bit is set for compiled methods or not depends on things like how easy you want to be able to update the source pointer or add/delete properties.
If the CompiledMethod itself is not immutable, then one can replace its top level literals easily. If it's immutable, then the system will face all kind of problems, like adding the correct trailer during method installation. You could say "no problem, I'll use #becomeForward: to do it, it's fast in Spur", but that's what my other question is about.
Does the set immutability bit also mean that the object can't be swapped with #become:?
That's debatable, but in VW we disallowed two-way become: and allowed one-way become: for immutables. I think that's right. one-way become is about references to objects, two-way become is about changing objects themselves.
It's not about #become: vs #becomeForward:. If objects with the immutable bit set can be replaced with #becomeForward:, then the literals are not really immutable, because they can be changed. If you also want to check if there are immutable objects pointing to the object which is about to be replaced, then the performance of #becomeForward: would become as slow as in the non-Spur VMs.
Levente
On Mon, Oct 27, 2014 at 12:12 PM, Levente Uzonyi leves@elte.hu wrote:
On Mon, 27 Oct 2014, Eliot Miranda wrote:
So you would set the bit for the CompiledMethod and all literals
(including sub-arrays, floats, integers, booleans etc)?
The literals, definitely. Whether the bit is set for compiled methods or not depends on things like how easy you want to be able to update the source pointer or add/delete properties.
If the CompiledMethod itself is not immutable, then one can replace its top level literals easily. If it's immutable, then the system will face all kind of problems, like adding the correct trailer during method installation. You could say "no problem, I'll use #becomeForward: to do it, it's fast in Spur", but that's what my other question is about.
Does the set immutability bit also mean that the object can't be
swapped with #become:?
That's debatable, but in VW we disallowed two-way become: and allowed one-way become: for immutables. I think that's right. one-way become is about references to objects, two-way become is about changing objects themselves.
It's not about #become: vs #becomeForward:. If objects with the immutable bit set can be replaced with #becomeForward:, then the literals are not really immutable, because they can be changed. If you also want to check if there are immutable objects pointing to the object which is about to be replaced, then the performance of #becomeForward: would become as slow as in the non-Spur VMs.
I disagree. It's about an incremental improvement. A per-object immutability bit used to protect against direct alteration of literals works. Right now there's nothing to stop one recompiling a method, and per-object immutability doesn't change that. So mutable compiled methods are tolerable (at least in VW we kept methods mutable). But having the bit stop direct modification of literals is goodness, not to be avoided because it doesn't prevent more exotic modification via become.
On Mon, 27 Oct 2014, Eliot Miranda wrote:
I disagree. It's about an incremental improvement. A per-object
immutability bit used to protect against direct alteration of literals works. Right now there's nothing to stop one recompiling a method, and per-object immutability doesn't change that. So mutable compiled methods are tolerable (at least in VW we kept methods mutable). But having the bit stop direct modification of literals is goodness, not to be avoided because it doesn't prevent more exotic modification via become.
But if you allow mutable CompiledMethods, then Clement's example can be rewritten as:
MyClass >> #foo
^#(false) first ifFalse: [ 'first call since compilation' logCr. thisContext method in: [ :method | (method literalAt: (method literals indexOf: #(false)) put: { true }) ] ] ifTrue: [ 'other calls' logCr ]
(#becomeForward: could also be used on the literal).
How will you avoid implementing the deoptimization of the method (the slow path) when this is possible?
Levente
Levente
I think that the first step is not to make sure that you cannot hack inside literals like mad. But to make sure that in 99.99 % of the case, this assumption can hold.
Stef
On 27/10/14 15:46, Levente Uzonyi wrote:
On Mon, 27 Oct 2014, Eliot Miranda wrote:
I disagree. It's about an incremental improvement. A per-object
immutability bit used to protect against direct alteration of literals works. Right now there's nothing to stop one recompiling a method, and per-object immutability doesn't change that. So mutable compiled methods are tolerable (at least in VW we kept methods mutable). But having the bit stop direct modification of literals is goodness, not to be avoided because it doesn't prevent more exotic modification via become.
But if you allow mutable CompiledMethods, then Clement's example can be rewritten as:
MyClass >> #foo
^#(false) first ifFalse: [ 'first call since compilation' logCr. thisContext method in: [ :method | (method literalAt: (method literals indexOf: #(false))
put: { true }) ] ] ifTrue: [ 'other calls' logCr ]
(#becomeForward: could also be used on the literal).
How will you avoid implementing the deoptimization of the method (the slow path) when this is possible?
Levente
On Mon, 27 Oct 2014, stepharo wrote:
Levente
I think that the first step is not to make sure that you cannot hack inside literals like mad. But to make sure that in 99.99 % of the case, this assumption can hold.
Stef
Let me sum it up: - The question is (by Clement) if it's okay to not implement the slow path. - He assumes that he doesn't have to implement it if literals are immutable. - I showed that literal immutability is not enough to avoid impelemting the slow path.
So immutability doesn't affect anything about the need for implementing of the slow path. If Clement decides to not implement it, then one will be able to get the system into an inconsistent state, no matter if literals are immutable or not.
If you think that the inconsistent state can only be achieved by hacking, then here's another example without "hacks":
SomeClass >> #someMethod
^someBoolean ifTrue: [ Array with: 1 with: 2 with: 3 ] ifFalse: [ Array empty "we don't have to create a new array for this, right? :)" ]
SomeOtherClass >> #someOtherMethod: anArray
| copy | anArray doSomething. "let's grow that array" copy := Array new: anArray size. copy replaceFrom: 1 to: anArray size with: anArray startingAt: 1. anArray becomeForward: copy. "this is really cheap in Spur (and in VW; even collections and streams use it)"
ThirdClass >> #yetAnotherMethod: aSomeClass
| x | x := aSomeClass someMethod. y someOtherMethod: x. ^x
And now, when #yetAnotherMethod: is sent, we swap the literal in Array class >> #empty with another non-empty array. Based on Eliot's comment about VW, I'm pretty sure the VW guys had problems like this, despite of having immutable literals.
Levente
2014-10-28 0:19 GMT+01:00 Levente Uzonyi leves@elte.hu:
On Mon, 27 Oct 2014, stepharo wrote:
Levente
I think that the first step is not to make sure that you cannot hack inside literals like mad. But to make sure that in 99.99 % of the case, this assumption can hold.
Stef
Let me sum it up:
- The question is (by Clement) if it's okay to not implement the slow path.
- He assumes that he doesn't have to implement it if literals are
immutable.
- I showed that literal immutability is not enough to avoid impelemting
the slow path.
So immutability doesn't affect anything about the need for implementing of the slow path. If Clement decides to not implement it, then one will be able to get the system into an inconsistent state, no matter if literals are immutable or not.
If you think that the inconsistent state can only be achieved by hacking, then here's another example without "hacks":
SomeClass >> #someMethod
^someBoolean ifTrue: [ Array with: 1 with: 2 with: 3 ] ifFalse: [ Array empty "we don't have to create a new
array for this, right? :)" ]
SomeOtherClass >> #someOtherMethod: anArray
| copy | anArray doSomething. "let's grow that array" copy := Array new: anArray size. copy replaceFrom: 1 to: anArray size with: anArray startingAt: 1. anArray becomeForward: copy. "this is really cheap in Spur (and in
VW; even collections and streams use it)"
ThirdClass >> #yetAnotherMethod: aSomeClass
| x | x := aSomeClass someMethod. y someOtherMethod: x. ^x
And now, when #yetAnotherMethod: is sent, we swap the literal in Array class >> #empty with another non-empty array. Based on Eliot's comment about VW, I'm pretty sure the VW guys had problems like this, despite of having immutable literals.
Levente
But is there a becomeForward: in VW?
2014-10-28 15:04 GMT+01:00 Levente Uzonyi leves@elte.hu:
On Tue, 28 Oct 2014, Nicolas Cellier wrote:
But is there a becomeForward: in VW?
It's called #oneWayBecome: according to google.
Levente
Oh fine, I didn't even notice when it was introduced... After checking in vwnc7.8, I see not so many senders anyway...
vm-dev@lists.squeakfoundation.org