Why become should fail on readonly objects?

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Why become should fail on readonly objects?

Denis Kudriashov
 
Hi.

Look at following example:

p1 := 10@20.
p2 := 40@50.
p1 becomeForward: p2.
p1 == p2 "==> true"

It shows that become operation do not modifies p1 instance. It just replaces value in all references.
Now if p1 will be read only object then become will fail:

p1 := 10@20.
p2 := 40@50.
p1 beReadOnlyObject.
p1 becomeForward: p2. "==> fail by modification error"

My question is why this logic is valid? As simple user I do not see how become modifies the state of receiver p1.

And what I expect is failing on another case which surprisingly do not fail:

p1 := 10@20.
p2 := 40@50.
array := {  p1. p2}.
array beReadOnlyObject.
p1 becomeForward: p2.
array = {40@50. 40@50} "==true"

Here become operation modifies state of read only object. But it not fails.

Best regards,
Denis
Reply | Threaded
Open this post in threaded view
|

Re: Why become should fail on readonly objects?

Eliot Miranda-2
 
Hi Denis,

On Wed, Jan 10, 2018 at 2:46 AM, Denis Kudriashov <[hidden email]> wrote:
 
Hi.

Look at following example:

p1 := 10@20.
p2 := 40@50.
p1 becomeForward: p2.
p1 == p2 "==> true"

It shows that become operation do not modifies p1 instance. It just replaces value in all references.
Now if p1 will be read only object then become will fail:

p1 := 10@20.
p2 := 40@50.
p1 beReadOnlyObject.
p1 becomeForward: p2. "==> fail by modification error"

My question is why this logic is valid? As simple user I do not see how become modifies the state of receiver p1.

Well, the reason is to prevent changing literals.  Let's say you have a method like

Object>>printOn: aStream
"Append to the argument, aStream, a sequence of characters that  
identifies the receiver."

| title |
title := self class name.
aStream
nextPutAll: (title first isVowel ifTrue: ['an '] ifFalse: ['a ']);
nextPutAll: title

Without read-only literals there is nothing to stop the programmer from making the mistake of doing something like
    ((Object>>#printOn:) literalAt: 4) at: 1 put: $A
which would cause Object new to print as 'An Object', not 'an Object'.  Once literals are read-only then this can't happen; the at:put: will fail.

But one can use becomeForward: or become: in exactly the same way.  Unless become is illegal for read-only objects, there is nothing to stop the programmer from making the mistake of doing
    ((Object>>#printOn:) literalAt: 4) becomeForward: 'An '
    ((Object>>#printOn:) literalAt: 4) become: 'An '

And what I expect is failing on another case which surprisingly do not fail:

p1 := 10@20.
p2 := 40@50.
array := {  p1. p2}.
array beReadOnlyObject.
p1 becomeForward: p2.
array = {40@50. 40@50} "==true"

Here become operation modifies state of read only object. But it not fails.

The reason here is to do with instance migration on class definition.  We want becomeForward: to update any and all instances of a class when we shape change a class.  Further, the only way the VM can prevent this is by scanning the entire heap looking for any reference to the receiver of becomeForward: from a read-only object.  And we want becomeForward: to be fast; the last thing we want is to introduce a full heap scan when we introduce read-only objects.  So we're being pragmatic; the definitions here, that two-way become fails if either is a read-only object, and that one-way become fails if the receiver is read-only, work well with the way the system implements instance migration, and allow us to implement become in the presence of read-only objects without a full heap scan.

This matches the VW implementation which makes the same choices for the same reasons.  It's not ideal, but neither is having the VM catch your last case.  Instead it's a workable compromise.  HTH


Best regards,
Denis

Likewise.  Happy New Year!

_,,,^..^,,,_
best, Eliot
Reply | Threaded
Open this post in threaded view
|

Re: Why become should fail on readonly objects?

Denis Kudriashov
 
Hi Eliot.

Thank's for details.

2018-01-11 2:57 GMT+01:00 Eliot Miranda <[hidden email]>:
 
Hi Denis,

On Wed, Jan 10, 2018 at 2:46 AM, Denis Kudriashov <[hidden email]> wrote:
 
Hi.

Look at following example:

p1 := 10@20.
p2 := 40@50.
p1 becomeForward: p2.
p1 == p2 "==> true"

It shows that become operation do not modifies p1 instance. It just replaces value in all references.
Now if p1 will be read only object then become will fail:

p1 := 10@20.
p2 := 40@50.
p1 beReadOnlyObject.
p1 becomeForward: p2. "==> fail by modification error"

My question is why this logic is valid? As simple user I do not see how become modifies the state of receiver p1.

Well, the reason is to prevent changing literals.  Let's say you have a method like

Object>>printOn: aStream
"Append to the argument, aStream, a sequence of characters that  
identifies the receiver."

| title |
title := self class name.
aStream
nextPutAll: (title first isVowel ifTrue: ['an '] ifFalse: ['a ']);
nextPutAll: title

Without read-only literals there is nothing to stop the programmer from making the mistake of doing something like
    ((Object>>#printOn:) literalAt: 4) at: 1 put: $A
which would cause Object new to print as 'An Object', not 'an Object'.  Once literals are read-only then this can't happen; the at:put: will fail.

But one can use becomeForward: or become: in exactly the same way.  Unless become is illegal for read-only objects, there is nothing to stop the programmer from making the mistake of doing
    ((Object>>#printOn:) literalAt: 4) becomeForward: 'An '
    ((Object>>#printOn:) literalAt: 4) become: 'An '

And what I expect is failing on another case which surprisingly do not fail:

p1 := 10@20.
p2 := 40@50.
array := {  p1. p2}.
array beReadOnlyObject.
p1 becomeForward: p2.
array = {40@50. 40@50} "==true"

Here become operation modifies state of read only object. But it not fails.

The reason here is to do with instance migration on class definition.  We want becomeForward: to update any and all instances of a class when we shape change a class.  Further, the only way the VM can prevent this is by scanning the entire heap looking for any reference to the receiver of becomeForward: from a read-only object.  And we want becomeForward: to be fast; the last thing we want is to introduce a full heap scan when we introduce read-only objects.  So we're being pragmatic; the definitions here, that two-way become fails if either is a read-only object, and that one-way become fails if the receiver is read-only, work well with the way the system implements instance migration, and allow us to implement become in the presence of read-only objects without a full heap scan.

This matches the VW implementation which makes the same choices for the same reasons.  It's not ideal, but neither is having the VM catch your last case.  Instead it's a workable compromise.  HTH


Best regards,
Denis

Likewise.  Happy New Year!

_,,,^..^,,,_
best, Eliot


Reply | Threaded
Open this post in threaded view
|

Re: Why become should fail on readonly objects?

Bert Freudenberg
In reply to this post by Eliot Miranda-2
 
On Thu, Jan 11, 2018 at 2:57 AM, Eliot Miranda <[hidden email]> wrote:
 
Hi Denis,

On Wed, Jan 10, 2018 at 2:46 AM, Denis Kudriashov <[hidden email]> wrote:
 
Now if p1 will be read only object then become will fail:

p1 := 10@20.
p2 := 40@50.
p1 beReadOnlyObject.
p1 becomeForward: p2. "==> fail by modification error"

My question is why this logic is valid? As simple user I do not see how become modifies the state of receiver p1.

Well, the reason is to prevent changing literals.

​I don't find this reason compelling. If the CompiledMethod itself was read-only​, then yes, trying to change a literal in it should be an error, just like trying to change any slot in any read-only object.

Let's say you have a method like

Object>>printOn: aStream
"Append to the argument, aStream, a sequence of characters that  
identifies the receiver."

| title |
title := self class name.
aStream
nextPutAll: (title first isVowel ifTrue: ['an '] ifFalse: ['a ']);
nextPutAll: title

Without read-only literals there is nothing to stop the programmer from making the mistake of doing something like
    ((Object>>#printOn:) literalAt: 4) at: 1 put: $A
which would cause Object new to print as 'An Object', not 'an Object'.  Once literals are read-only then this can't happen; the at:put: will fail.

​Agreed. Literals should be read-only, they should never be modified.​

But one can use becomeForward: or become: in exactly the same way. 

​No you can not. You can only replace the whole literal, but you can not change it. 

Unless become is illegal for read-only objects, there is nothing to stop the programmer from making the mistake of doing
    ((Object>>#printOn:) literalAt: 4) becomeForward: 'An '
    ((Object>>#printOn:) literalAt: 4) become: 'An '

​True. But again, unless the CompiledMethod is read-only, I don't see a reason why this sho​uld fail any more than

    (Object>>#printOn:) literalAt: 4
 put
: 'An '
 
And what I expect is failing on another case which surprisingly do not fail:

p1 := 10@20.
p2 := 40@50.
array := {  p1. p2}.
array beReadOnlyObject.
p1 becomeForward: p2.
array = {40@50. 40@50} "==true"

Here become operation modifies state of read only object. But it not fails.

​I would expect this to fail, too.​

The reason here is to do with instance migration on class definition.  We want becomeForward: to update any and all instances of a class when we shape change a class.  Further, the only way the VM can prevent this is by scanning the entire heap looking for any reference to the receiver of becomeForward: from a read-only object.  And we want becomeForward: to be fast; the last thing we want is to introduce a full heap scan when we introduce read-only objects.  So we're being pragmatic; the definitions here, that two-way become fails if either is a read-only object, and that one-way become fails if the receiver is read-only, work well with the way the system implements instance migration, and allow us to implement become in the presence of read-only objects without a full heap scan.

This matches the VW implementation which makes the same choices for the same reasons.  It's not ideal, but neither is having the VM catch your last case.  Instead it's a workable compromise.  HTH

​Okay. If this is the semantics needed for a fast VM, that's a reason I can accept. It is just surprising that the semantics are both more restrictive (no become of a read-only object) and less restrictive (become can modify parts of a read-only object) than 

Given this behavior of the VM, wouldn't it make sense to recursively make all sub-objects read-only when making an object read-only? That would ensure that parts cannot become'd either.

- Bert -​
Reply | Threaded
Open this post in threaded view
|

Re: Why become should fail on readonly objects?

Nicolas Cellier
 


2018-01-13 20:04 GMT+01:00 Bert Freudenberg <[hidden email]>:
 
On Thu, Jan 11, 2018 at 2:57 AM, Eliot Miranda <[hidden email]> wrote:
 
Hi Denis,

On Wed, Jan 10, 2018 at 2:46 AM, Denis Kudriashov <[hidden email]> wrote:
 
Now if p1 will be read only object then become will fail:

p1 := 10@20.
p2 := 40@50.
p1 beReadOnlyObject.
p1 becomeForward: p2. "==> fail by modification error"

My question is why this logic is valid? As simple user I do not see how become modifies the state of receiver p1.

Well, the reason is to prevent changing literals.

​I don't find this reason compelling. If the CompiledMethod itself was read-only​, then yes, trying to change a literal in it should be an error, just like trying to change any slot in any read-only object.

Let's say you have a method like

Object>>printOn: aStream
"Append to the argument, aStream, a sequence of characters that  
identifies the receiver."

| title |
title := self class name.
aStream
nextPutAll: (title first isVowel ifTrue: ['an '] ifFalse: ['a ']);
nextPutAll: title

Without read-only literals there is nothing to stop the programmer from making the mistake of doing something like
    ((Object>>#printOn:) literalAt: 4) at: 1 put: $A
which would cause Object new to print as 'An Object', not 'an Object'.  Once literals are read-only then this can't happen; the at:put: will fail.

​Agreed. Literals should be read-only, they should never be modified.​

But one can use becomeForward: or become: in exactly the same way. 

​No you can not. You can only replace the whole literal, but you can not change it. 


One scenario in st80 was:

foo
    ^'bar' writeStream

baz
    ^(self foo) nextPutAll: 'baz'; contents

But WriteStream was using become: to grow the target collection...
So the method foo was later pointing on a 'barbaz', and next execution of baz not in agreement with source code...

We can indeed make the CompiledMethod readOnly, but then, we have to scan the heap at each become:
So bye bye fast (lazy) become:...

Also beware, I think that our Squeak Environment rely on become: for shared variable bindings...
CompiledMethod directly points to these...

Unless become is illegal for read-only objects, there is nothing to stop the programmer from making the mistake of doing
    ((Object>>#printOn:) literalAt: 4) becomeForward: 'An '
    ((Object>>#printOn:) literalAt: 4) become: 'An '

​True. But again, unless the CompiledMethod is read-only, I don't see a reason why this sho​uld fail any more than

    (Object>>#printOn:) literalAt: 4
 put
: 'An '
 
And what I expect is failing on another case which surprisingly do not fail:

p1 := 10@20.
p2 := 40@50.
array := {  p1. p2}.
array beReadOnlyObject.
p1 becomeForward: p2.
array = {40@50. 40@50} "==true"

Here become operation modifies state of read only object. But it not fails.

​I would expect this to fail, too.​

The reason here is to do with instance migration on class definition.  We want becomeForward: to update any and all instances of a class when we shape change a class.  Further, the only way the VM can prevent this is by scanning the entire heap looking for any reference to the receiver of becomeForward: from a read-only object.  And we want becomeForward: to be fast; the last thing we want is to introduce a full heap scan when we introduce read-only objects.  So we're being pragmatic; the definitions here, that two-way become fails if either is a read-only object, and that one-way become fails if the receiver is read-only, work well with the way the system implements instance migration, and allow us to implement become in the presence of read-only objects without a full heap scan.

This matches the VW implementation which makes the same choices for the same reasons.  It's not ideal, but neither is having the VM catch your last case.  Instead it's a workable compromise.  HTH

​Okay. If this is the semantics needed for a fast VM, that's a reason I can accept. It is just surprising that the semantics are both more restrictive (no become of a read-only object) and less restrictive (become can modify parts of a read-only object) than 

Given this behavior of the VM, wouldn't it make sense to recursively make all sub-objects read-only when making an object read-only? That would ensure that parts cannot become'd either.

But we have so many cycles in the graph, if we touch a class (then an environment), the whole graph is frozen...

 
- Bert -​


Reply | Threaded
Open this post in threaded view
|

Re: Why become should fail on readonly objects?

Tobias Pape
 

On 13.01.2018, at 21:26, Nicolas Cellier <[hidden email]> wrote:

CompiledMethod directly points to these...

Do they?
Reply | Threaded
Open this post in threaded view
|

Re: Why become should fail on readonly objects?

Nicolas Cellier
 
Hi Tobias,
I don't mean changing the value of the binding.
I mean Environment operations like bind:to: or undeclare:from:
But i see it's a becomeForward: not become: so it would fall in exceptional cases described by Eliot for mutating instances...

2018-01-13 21:38 GMT+01:00 Tobias Pape <[hidden email]>:
 

On 13.01.2018, at 21:26, Nicolas Cellier <[hidden email]> wrote:

CompiledMethod directly points to these...

Do they?


Reply | Threaded
Open this post in threaded view
|

Re: Why become should fail on readonly objects?

Tobias Pape
 

> On 13.01.2018, at 23:03, Nicolas Cellier <[hidden email]> wrote:
>
> Hi Tobias,
> I don't mean changing the value of the binding.
> I mean Environment operations like bind:to: or undeclare:from:
> But i see it's a becomeForward: not become: so it would fall in exceptional cases described by Eliot for mutating instances...
>

Yeah, I thought that bindings (or associations then) were a device to make #become: less painful on things that tangentially touch CompiledMethods..

> 2018-01-13 21:38 GMT+01:00 Tobias Pape <[hidden email]>:
>  
>
>> On 13.01.2018, at 21:26, Nicolas Cellier <[hidden email]> wrote:
>>
>> CompiledMethod directly points to these...
>
> Do they?
> <Bildschirmfoto 2018-01-13 um 21.36.33.PNG>
>
>