On Mon, Jun 6, 2016 at 9:34 PM, Clément Bera <bera.clement@gmail.com> wrote:
Hi Ben,
Firstly be careful because atomic usually means that the thread cannot be interrupted by a concurrent native thread, so an atomic operation has to be guaranteed at processor level.
Assuming that by atomic you mean that the operation's process cannot be preempted by another green thread, yes there is big problems with non local returns.
Yes, I meant at the image level, run by the single-thread VM.
Two main issues: - NLR can trigger unwind blocks, which can switch to another process. - NLR can fail, triggering the cannotReturn: call-back, which can switch to another process.
These would presumably could only occur after the #ensure: had been invoked? .
I understand generally that an inlined #ifTrue is atomic. For example, here after the primitive returns, no interruption can occur before the #ensure is invoked... self primitiveWaitAcquire ifTrue: [mutuallyExclusiveBlock ensure: [self release]].
The exact answer is that #ifTrue: can be interrupted if self primitiveWaitAcquire is not a boolean, and can't be interrupted if it's a boolean. It's nice to mark in a comment if part of the method's code has not to be interrupted to avoid issues years later.
Good tip.
But I want to know whether a non-local return could have an impact on that atomicity. For example ... self primitiveWaitAcquire ifTrue: [ ^ mutuallyExclusiveBlock ensure: [self release]]
In your code there is no non local return because ifTrue: is inlined by the Bytecode compiler.
I'm not clear on your meaning. If there were additional code following those two lines, then they would not be executed, so it would still be a NLR ? I took my understanding from [1]. And in the bytecode below it seems #returnTop is associated with the NLRs - so the NLR is still there(?), but having looked at the bytecode I can now see it has no impact, I think either with or without inlining makes no difference wrt NLR. [1] https://silversmalltalk.wordpress.com/2011/02/02/implementing-smalltalks-non...
Try to compile it and look at the bytecode you will see.
Good idea. So I share a little analysis... The bytecode for these two lines is identical, a blah: [ [ self foo ] ensure: [ self bar ] ] a blah: [ ^ [ self foo ] ensure: [ self bar ] ] except for bytecode 49, respectively... "49 <7D> blockReturn" "49 <7C> returnTop" "29 <10> pushTemp: 0" "30 <8F 00 00 10> closureNumCopied: 0 numArgs: 0 bytes 34 to 49" "34 <8F 00 00 03> closureNumCopied: 0 numArgs: 0 bytes 38 to 40" "38 <70> self" "39 <D0> send: foo" "40 <7D> blockReturn" "41 <8F 00 00 03> closureNumCopied: 0 numArgs: 0 bytes 45 to 47" "45 <70> self" "46 <D1> send: bar" "47 <7D> blockReturn" "48 <E2> send: ensure:" "49 <7C> returnTop" "50 <E3> send: blah:" "51 <87> pop" "52 <78> returnSelf" Changing #blah: to #ifTrue: these two lines a ifTrue: [ [ self foo ] ensure: [ self bar ] ] a ifTrue: [ ^ [ self foo ] ensure: [ self bar ] ] are the same except for bytecode 49, respectively... "47 <87> pop" "47 <7C> returnTop" "29 <10> pushTemp: 0" "30 <AC 10> jumpFalse: 48" "32 <8F 00 00 03> closureNumCopied: 0 numArgs: 0 bytes 36 to 38" "36 <70> self" "37 <D0> send: foo" "38 <7D> blockReturn" "39 <8F 00 00 03> closureNumCopied: 0 numArgs: 0 bytes 43 to 45" "43 <70> self" "44 <D1> send: bar" "45 <7D> blockReturn" "46 <E2> send: ensure:" "47 <87> pop" "48 <78> returnSelf" And between #blah: and #ifTrue: the difference is removal of... "30 <8F 00 00 10> closureNumCopied: 0 numArgs: 0 bytes 34 to 49" "49 <7D> blockReturn" "50 <E3> send: blah:" and replaced them with... "30 <AC 10> jumpFalse: 48" It took me a while to guess how to follow the execution path. I had assumed it would just go top to bottom, but that didn't make sense for foo to be executed before blah. Can you confirm.. I guess the trick (for #blah) is... 1. At 29, push the temp 2. skip the closureNumCopied bytes 34 to 49, 3. At 50, send #blah. 4. next is bytecode 34 skips bytecodes 38 to 40, 5. and bytecode 41 skips bytecodes 45 to 47 6. At 48 send #ensure:. versus #ifTrue: bytecocde... 1. At 29, push the temp 2. skip closureNumCopied bytes 36 to 38" 3. skip closureNumCopied: bytes 43 to 45" 4. At 46, send #ensure: So its clear (IIUC) that with #IfTrue: the #ensure is the first send: after the push temp, but the non-inlined #blah: is an extra send between which could be interrupted and prevent the #ensure from being executed.
Else as I said at the beginning of the mail, a non local return can imply a process switch if that was your question.
Yes, but a process switch is okay after the send of #ensure: just not before. thx. cheers -ben
On Mon, Jun 6, 2016 at 2:46 PM, Ben Coman <btc@openinworld.com> wrote:
I understand generally that an inlined #ifTrue is atomic. For example, here after the primitive returns, no interruption can occur before the #ensure is invoked... self primitiveWaitAcquire ifTrue: [mutuallyExclusiveBlock ensure: [self release]].
But I want to know whether a non-local return could have an impact on that atomicity. For example ... self primitiveWaitAcquire ifTrue: [ ^ mutuallyExclusiveBlock ensure: [self release]]
cheers -ben